ヒープ競合の回避

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) スロットにスレッドの文字列マネージャーへのポインターを格納すると便利な場合があります。 これにより、スレッド プロシージャによって呼び出された他の関数がスレッドの文字列マネージャーにアクセスできます。

関連項目

CStringT によるメモリ管理