ヒープ競合の回避
更新 : 2007 年 11 月
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);
}
説明
複数のスレッドが同じスレッド プロシージャを使用して稼働できますが、各スレッドは固有のヒープを持つため、スレッド間で競合は発生しません。また、各ヒープがスレッドセーフでないため、スレッドのコピーが 1 つしか実行されていない場合でも、パフォーマンスが顕著に向上します。パフォーマンスの向上は、ヒープを同時アクセスから保護するためにコストの高い相互ロック操作を使用しないことによるものです。
より複雑なスレッド プロシージャでは、スレッドの文字列マネージャへのポインタをスレッド ローカル ストレージ (TLS: Thread Local Storage) スロットに格納すると便利です。これにより、スレッド プロシージャから呼び出されるほかの関数がスレッドの文字列マネージャにアクセスできます。