避免堆争用

MFC 和 ATL 提供的默认字符串管理器是全局堆顶部的简单包装器。 此全局堆是完全线程安全的,这意味着多个线程可以在不损坏堆的情况下同时分配和释放内存。 为了帮助提供线程安全性,堆必须序列化对自身的访问。 这通常是使用关键节或类似的锁定机制完成的。 每当两个线程尝试同时访问堆时,会阻止其中一个线程,直到另一个线程的请求完成。 对于许多应用程序,这种情况很少发生,堆锁定机制的性能影响很小。 但是,对于经常从多个线程争用堆锁定访问堆的应用程序,可能会导致应用程序运行速度比单线程(甚至具有多个 CPU 的计算机)的速度慢。

使用 CStringT 的应用程序尤其容易受堆争用影响,因为对 CStringT 对象的操作经常需要重新分配字符串缓冲区。

缓解线程之间堆争用的一种方法是让每个线程从专用的线程本地堆分配字符串。 只要使用特定线程分配器分配的字符串仅在该线程中使用,分配器就不需要线程安全。

示例

下面的示例演示了一个线程过程,该线程过程为线程上的字符串分配自己的专用非线程安全堆:

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);
}

注释

可以使用同一个线程过程运行多个线程,但由于每个线程都有自己的堆,因此线程之间不发生争用。 此外,即使只运行线程的一个副本,每个堆都不是线程安全的也可以显著提高性能。 这是堆没有使用昂贵的互锁操作来防止并发访问的结果。

对于更复杂的线程过程,将指向线程字符串管理器的指针存储在线程本地存储 (TLS) 槽中可能更方便。 这允许线程过程调用的其他函数访问线程的字符串管理器。

另请参阅

使用 CStringT 进行内存管理