避免堆争用
由 MFC 和 ATL 提供的默认字符串管理器是全局堆顶上的简单包装。该全局堆是完全线程安全的,即多线程可同时从中分配和释放内存而不会损坏堆。为了确保线程的安全,堆必须将对它的访问进行序列化。实现这一点时通常会用到临界区或类似的锁定机制。每当两个线程试图同时访问堆时,将阻塞其中的一个线程,直至完成另一个线程的请求。对于许多应用程序,这种情况很少发生,并且堆锁定机制带来的对性能的影响可以忽略不计。但是,如果应用程序从堆锁定的多线程争用中频繁访问堆,那么可能造成应用程序的运行速度慢于从单线程访问(即使是在带有多个 CPU 的计算机上运行也是如此)。
使用 CStringT 的应用程序更加容易受堆争用的影响,因为在 CStringT 对象上的操作会频繁要求对字符串缓冲区进行重新分配。
减轻线程间堆争用的一个方式是为每个线程从专用的、本地线程堆分配字符串。只要用特定线程分配器分配的字符仅用于该线程中,那么该分配器就不需要线程安全。以下示例阐释了一个线程过程,该线程过程为自身分配一个专用的非线程安全堆以供该线程上的字符串使用:
DWORD_PTR WINAPI WorkerThreadProc( void* pContext )
{
// 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 n = 1;
for( int nPower = 0; nPower < 10; nPower++ )
{
// Use the thread's string manager, instead of the default
CString strPower( &stringMgr );
strPower.Format( "%d", n );
printf( "%s\n", LPCSTR( strPower ) );
n *= nBase;
}
return( 0 );
}
使用同样的线程过程可以运行多线程,但是由于每个线程都有自已的堆,所以线程之间不存在争用。此外,由于每个堆都不是线程安全的,因此带来了性能上的明显提升,即使只运行线程的一个副本也如此。对于不使用昂贵的互锁操作来避免并发访问的堆,可以使用这种方法。
对于更为复杂的线程过程,较方便的做法是将指向线程的字符串管理器的指针存储在线程本地存储 (TLS) 槽中。这使得其他由线程过程调用的函数能够访问该线程的字符串管理器。

浙公网安备 33010602011771号