ヒープ競合の回避
MFC と ATL によって提供される既定の文字列マネージャーは、グローバル ヒープ上の単純なラッパーです。 このグローバル ヒープは完全にスレッドセーフです。つまり、複数のスレッドで、ヒープを破損することなく、同時にメモリを割り当て、解放できます。 スレッド セーフの提供に役立つように、ヒープではそれ自体へのアクセスをシリアル化する必要があります。 これは通常、クリティカル セクションまたは同様のロック メカニズムを使用して行われます。 2 つのスレッドが同時にヒープにアクセスしようとするたびに、一方のスレッドの要求が終了するまで、もう一方のスレッドがブロックされます。 多くのアプリケーションでは、この状況はほとんど発生しません。ヒープのロック メカニズムのパフォーマンスへの影響はごくわずかです。 しかし、複数のスレッドからヒープに頻繁にアクセスするアプリケーションでは、ヒープのロックの競合が発生すると、(複数の CPU を搭載したコンピューターでも) シングル スレッドの場合よりもアプリケーションの実行速度が低下する可能性があります。
CStringT を使用するアプリケーションでは特にヒープ競合の影響を受けやすくなります。これは、CStringT
オブジェクトに対する操作で頻繁に文字列バッファーの割り当て解除が必要になるためです。
スレッド間のヒープ競合を軽減する 1 つの方法は、各スレッドでプライベートなスレッド ローカル ヒープから文字列を割り当てることです。 特定のスレッドのアロケーターで割り当てられた文字列が、そのスレッドでのみ使用されている限り、アロケーターはスレッド セーフである必要はありません。
例
次の例は、スレッドの文字列に使用するために独自のプライベートな非スレッド セーフ ヒープを割り当てるスレッド プロシージャを示しています。
DWORD WINAPI WorkerThreadProc(void* pBase)
{
// Declare a non-thread-safe heap just for this thread:
CWin32Heap stringHeap(HEAP_NO_SERIALIZE, 0, 0);
// Declare a string manager that uses the thread's heap:
CAtlStringMgr stringMgr(&stringHeap);
int nBase = *((int*)pBase);
int n = 1;
for(int nPower = 0; nPower < 10; nPower++)
{
// Use the thread's string manager, instead of the default:
CString strPower(&stringMgr);
strPower.Format(_T("%d"), n);
_tprintf_s(_T("%s\n"), strPower);
n *= nBase;
}
return(0);
}
Comments
この同じスレッド プロシージャを使用して複数のスレッドを実行できますが、各スレッドには独自のヒープがあるため、スレッド間で競合は発生しません。 さらに、各ヒープがスレッド セーフではないという事実により、スレッドの 1 つのコピーだけが実行されている場合でも、パフォーマンスが大幅に向上します。 これは、同時アクセスから保護するために、コストの高いインタロック操作を使用しないヒープの結果です。
より複雑なスレッド プロシージャの場合は、スレッド ローカル ストレージ (TLS) スロットにスレッドの文字列マネージャーへのポインターを格納すると便利な場合があります。 これにより、スレッド プロシージャによって呼び出された他の関数がスレッドの文字列マネージャーにアクセスできます。