1. 程式人生 > >工作問題積累(二十三)CString在多執行緒下的安全問題

工作問題積累(二十三)CString在多執行緒下的安全問題

       這個問題之前沒碰到過,也沒聽過,有個朋友在群裡提了這個問題,沒人解答,對於問題不知道答案,我是睡不著覺的。

       這篇博文對於我對CString安全問題的理解提到了很大的幫助,在網上查了很多相關問題,但是光看一篇文章還是不能完全明白,我想弄明白:(1.)導致安全問題的根源在哪裡?為什麼會出現?

【問題分析】:        

       參考上面博文中的CString的根原始碼,正如裡面所說,在Debug和Release模式下對於CString物件處理方式是不一樣的,Debug模式下是new出來的,而Release模式下是定長記憶體。

        裡面演示了由於執行緒A沒能夠執行LeaveCriticalSection(&m_protect);執行緒B再次喚醒時就會發生堵塞。但是為什麼會堵塞?我們得查一下EnterCriticalSection(&m_protect);這個函式的功能是什麼

函式 EnterCriticalSection 和 LeaveCriticalSection 宣告如下:
 



WINBASEAPIVOIDWINAPIEnterCriticalSection(    __inout LPCRITICAL_SECTION lpCriticalSection); 

是多執行緒中用來確保同一時刻只有一個執行緒操作被保護的資料的操作函式,相關的多執行緒資料操作函式還有:
 


InitializeCriticalSection(&cs);//初始化臨界區
EnterCriticalSection(&cs);//進入臨界區//操作資料MyMoney*=10;//所有訪問MyMoney變數的程式都需要這樣寫Enter.. 
Leave...LeaveCriticalSection(&cs);//離開臨界區
DeleteCriticalSection(&cs);//刪除臨界區 


多個執行緒操作相同的資料時,一般是需要按順序訪問的,否則會引導資料錯亂,無法控制資料,變成隨機變數。

為解決這個問題,就需要引入互斥變數,讓每個執行緒都按順序地訪問變數。這樣就需要使用EnterCriticalSection和LeaveCriticalSection函式。

這個過程實際上是通過限制有且只有一個函式進入CriticalSection變數來實現程式碼段同步的。簡單地說,對於同一個 CRITICAL_SECTION,當一個執行緒執行了EnterCriticalSection而沒有執行LeaveCriticalSection的時 候,其它任何一個執行緒都無法完全執行EnterCriticalSection而不得不處於等待狀態。

CFixedAlloc::Alloc使用CPlex::Create完成空間申請,不過最顯眼的是整個分配過程都在關鍵程式碼段內。與之對應的是空間的釋放也同樣在關鍵程式碼段內。在沒看到原始碼前,我怎麼也想不到CString會在這個地方使用了關鍵程式碼段。這意味著只要使用了CString,各個執行緒即便毫無關聯,也會由此隱晦的聯絡起來。想像一下這種情況,兩個執行緒都使用了CString物件,由於某種原因,執行緒A在CString的分配(或者釋放)的過程中發生了意外,沒有能夠呼叫LeaveCriticalSection,那麼執行緒B在使用CString的時候就會被“意外”的阻塞。

【解決辦法】:

修改前:

CString strstate;    
strstate.Format("正在解密,請稍後... (共 %d 張地圖)",p->m_countmap);

修改後:

CWin32Heap stringHeap( HEAP_NO_SERIALIZE, 0, 0 );
CAtlStringMgr stringMgr( &stringHeap );
CString strstate(&stringMgr );
strstate.Format("正在解密,請稍後... (共 %d 張地圖)",p->m_countmap);

為字串資料自定義記憶體分配方案的最簡單的方式是使用 ATL 提供的 CAtlStringMgr 類,但您需要自己提供記憶體分配例程。

CAtlStringMgr 的建構函式採用單一引數:即指向 IAtlMemMgr 物件的指標。IAtlMemMgr 是提供到堆的一般介面的抽象基類。通過IAtlMemMgr 介面,CAtlStringMgr 分配、重新分配和釋放用於儲存字串資料的記憶體。您既可以自已實現IAtlMemMgr 介面,也可以使用由 ATL 提供的五個記憶體管理器類之一。ATL 提供的記憶體管理器只包裝現有的記憶體分配功能:

  • CCRTHeap   包裝標準 CRT 堆功能(malloc、free 和 realloc)
  • CWin32Heap   使用 HeapAlloc、HeapFree 和HeapRealloc 包裝 Win32 堆控制代碼
  • CLocalHeap   包裝 Win32 API:LocalAlloc、LocalFree 和LocalRealloc
  • CGlobalHeap   包裝 Win32 API:GlobalAlloc、GlobalFree 和 GlobalRealloc
  • CComHeap   包裝 COM 任務分配器 API:CoTaskMemAlloc、CoTaskMemFree 和 CoTaskMemRealloc

要進行字串記憶體管理,最有用的類是 CWin32Heap,因為它使您能夠建立多個獨立的堆。例如,如果使用僅用於字串的獨立堆,可進行以下操作:

//Declare a thread-safe, growable, private heap with initial size 0
CWin32Heap g_stringHeap( 0, 0, 0 );
// Declare a string manager that uses the private heap
CAtlStringMgr g_stringMgr( &g_stringHeap );