接下來我主要講一下自己在學習windows核心編程中對于臨界區線程同步方式的使用。
臨界區線程同步在windows核心編程中被稱為關鍵段線程同步,以下統稱關鍵段關鍵段是一小段代碼,它在執行之前需要獨占對一些資源的訪問權。缺點:能且只能用在一個進程中的多線程同步??赡芟萑胨梨i,因為我們無法為進入關鍵段的線程設置最大等待時間。接下來我介紹一些關鍵段線程同步的使用先看一個事例代碼
[html] view plain copyint g_nSum = 0; CRITICAL_SECTION g_cs; DWord WINAPI FirstThread(PVOID pvParam) { EnterCriticalSection(&g_cs); g_nSum = 0; for (int n = 0; n < 10000; ++n) { g_nSum += n; } LeaveCriticalSection(&g_cs); return g_nSum; } DWORD WINAPI SecondThread(PVOID pvParam) { EnterCriticalSection(&g_cs); g_nSum = 0; for (int n = 0; n < 10000; ++n) { g_nSum += n; } LeaveCriticalSection(&g_cs); return g_nSum; } 在使用關鍵段(CRITICAL_SECTION)時,只有兩個必要條件:1、想要訪問資源的線程必須知道用來保護資源的CRITICAL_SECTION對象地址。CRITICAL_SECTION對象可以作為全局對象來分配,也可以作為局部對象來分配,或者從堆中動態地分配。2、如何線程在試圖訪問被保護的資源之前,必須對CRITICAL_SECTION結構的內部成員進行初始化。關鍵段線程同步常用函數介紹[html] view plain copy//1、首先我們要分配一個CRITICAL_SECTION對象,并進行初始化(使用關鍵段同步的線程必須調用此函數) void InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //2、當知道線程將不再需要訪問共享資源時,我們應該調用下邊的函數來清理CRITICAL_SECTION結構 void DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //3、在對保護的資源進行訪問之前,必須調用下面的函數 void EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //可以對上邊的函數多次調用,表示調用線程被獲準訪問的次數 //4、也可以用下邊的函數代替EnterCriticalSection BOOL TryEnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //通過返回值判斷當前線程是否獲準訪問資源,線程永遠不會進入等待狀態,如果 //返回TRUE表示該線程獲準并正在訪問資源,離開時必須調用LeaveCriticalSection() //5、在代碼完成對資源的訪問后,必須調用以下函數,釋放訪問權限 void LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection ) //轉載請注明文章來自:http://blog.csdn.net/windows_nt 以上訪問方式(EnterCriticalSection方式)可能會使調用線程切換到等待狀態,這意味著線程必須從用戶模式切換到內核模式,這個切換開銷非常大。為了提高關鍵段的性能,Microsoft把旋轉鎖合并到了關鍵段中。因此,當調用EnterCriticalSection的時候,它會用一個旋轉鎖不斷地循環,嘗試在一段時間內獲得對資源的訪問權,只有當嘗試失敗時,線程才會切換到內核模式并進入等待狀態。[html] view plain copy//1、為了在使用關鍵段的時候同時使用旋轉鎖,我們必須調用下面的函數來初始化關鍵段 BOOL InitializeCriticalSectionAndSpinCount( LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount ) //第二個參數dwSpinCount表示我們希望旋轉鎖循環的次數。 //2、我們也可以調用下面的函數來改變關鍵段的旋轉次數 DWORD SetCriticalSectionSpinCount( LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount ) //如果主機只有一個處理器,函數會忽略dwSpinCount參數3、等待一個mutex時,你可以指定“結束等待”的世間長度,但對于critical section則不行。
造成以上差別的主要原因是:mutex是內核對象,critical section非內核對象。以下是mutex和critical section的相關函數比較:
臨界區 | 互斥器 |
CRITICAL_SECTIONInitializeCriticalSection() | CreateMutex()OpenMutex() |
EnterCriticalSection() | WaitForSingleObject()WaitForMultipleObjects()MsgWaitForMultipleObjects() |
LeaveCriticalSection() | ReleaseMutex() |
DeleteCriticalSection() | CloseHandle() |
在一個適當的程序中,線程絕對不應該在它即將結束前還擁有一個mutex,因為這意味著線程沒有能夠適當地清除其資源。不幸的是,我們并不身處一個完美的世界,有時候,因為某種理由,線程可能沒有在結束前調用ReleaseMutex()。為了解決這個問題,mutex有一個非常重要的特性。這性質在各種同步機制中是獨一無二的,如果線程擁有一個mutex而在結束前沒有調用ReleaseMutex(),mutex不會被摧毀,取而代之的是,該mutex會被視為“未被擁有”以及“未被激發”,而下一個等待中的線程會被以WAIT_ABANDONED_0通知。無論線程是因為ExitThread()而結束,或是因當掉而結束,這種情況都存在。
任何時候只要你想鎖住超過一個以上的同步對象,你就有死鎖的潛在病因。如果總是在相同時間把所有對象都鎖住,問題可去矣。事例如下,存在潛在死鎖的可能:
[html] view plain copyvoid SwapLists(List* list1, List* list2) { EnterCriticalSection(list1->critical_sec); EnterCriticalSection(list2->critical_sec); //list1,list2數據交換 LeaveCriticalSection(list1->critical_sec); LeaveCriticalSection(list2->critical_sec); } 正確的做法:[html] view plain copyvoid SwapLists(List* list1, List* list2) { HANDLE arrHandles[2]; arrHandles[0] = list1->hMutex; arrHandles[1] = list2->hMutex; WaitForMultipleObjects(2, arrHandles, TRUE, INFINITE); //list1,list2數據交換 ReleaseMutex(arrHandles[0]); ReleaseMutex(arrHandles[1]); } 三、前邊講過了互斥器線程同步-----windows核心編程-互斥器(Mutexes),這章我來介紹一下信號量(semaphore)線程同步。
理論上說,mutex是semaphore的一種退化。如果你產生一個semaphore并令最大值為1,那就是一個mutex。也因此,mutex又常被稱為binary semaphore。如果某個線程擁有一個binary semaphore,那么就沒有其他線程能夠獲得其擁有權。但是在win32中,這兩種東西的擁有權的意義完全不同,所以它們不能夠交換使用,semaphore不像mutex,它并沒有所謂的“wait abandoned”狀態可以被其他線程偵測到。每當一個鎖定動作成功,semaphore的現值就會減1,你可以使用任何一種wait...()函數來要求鎖定一個semaphore。如果semaphore的現值不為0,wait...()函數會立刻返回,這和mutex很像,當沒有任何線程擁有mutex,wait...()函數會立刻返回。注意,如果鎖定成功,你也不會收到semaphore的擁有權。因為可以有一個以上的線程同時鎖定一個semaphore。所以談semaphore的擁有權并沒有太多實際意義。在semaphore身上并沒有所謂“獨占鎖定”這種事情。也因為沒有所有權的觀念,一個線程可以反復調用wait...()函數以產生新的鎖定。這和mutex絕不相同:擁有mutex的線程不論再調用多少次wait...()函數,也不會被阻塞住。與mutex不同的是,調用ReleaseSemaphore()的那個線程,并不一定就得是調用wait...()的那個線程。任何線程都可以在任何時間調用ReleaseSemaphore(),解除被任何線程鎖定的semaphore。
以下是我對三種同步方式中,常用到的函數的總結。
臨界區 | 互斥器 | 信號量 |
CRITICAL_SECTIONInitializeCriticalSection() | CreateMutex()OpenMutex() | CreateSemaphore |
EnterCriticalSection() | WaitForSingleObject()WaitForMultipleObjects()MsgWaitForMultipleObjects() | WaitForSingleObject()WaitForMultipleObjects()MsgWaitForMultipleObjects()... |
LeaveCriticalSection() | ReleaseMutex() | ReleaseSemaphore() |
DeleteCriticalSection() | CloseHandle() | CloseHandle() |
事件線程同步----- window核心編程-內核對象線程同步
四、
上一章講了關鍵字(臨界區)線程同步,使用關鍵字線程同步,我們很容易陷入死鎖的情形,這是因為我們無法為進入關鍵段指定一個最長等待時間。
本章將討論如何使用內核對象來對線程同步。我們也將看到,與用戶模式下的同步機制(關鍵段同步)相比,內核對象的用途要廣泛的多。實際上,內核對象唯一的缺點就是它們的性能。內核對象包括進程、線程以及作業,幾乎所有這些內核對象都可以用來進行同步。對線程同步來書,這些內核對象中的每一種要么處于觸發狀態,要么處于未觸發狀態。Microsoft為每種對象創建了一些規則,規定如何在這兩種狀態之間進行轉換。例如,進程內核對象在創建的時候總是處于未觸發狀態。當進程終止的時候,操作系統會自動使進程內核對象變成觸發狀態。當進程內核對象被觸發后,它將永遠保持這種狀態,再也不會變回到未觸發狀態。在進程內核對象的內部有一個布爾變量,當系統創建內核對象的時候會把這個變量的值初始化為false(未觸發)。當進程終止的時候,操作系統會自動把相應的內核對象中的這個布爾值設為true,表示該對象已經被觸發。
下邊講一些內核同步中用到的函數。等該函數使一個線程自愿進入等待狀態,直到指定的內核對象被觸發為止。DWORD waitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);//hObject:內核對象句柄//dwMilliseconds等待時間ms為單位,INFINITE為一直等待,只到內核對象被觸發為止。DWORD WaitForMultipleObjects( DWORD nCount, CONST HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds );//waitForSingleObject和WaitForMultipleObjects相似,唯一的不同之處在于它允許調用線程同時檢查多個內核對象的觸發狀態/*函數的返回值告訴調用方函數為什么它得以繼續運行。如果給bWaitAll傳的是FALSE,那么只要任何一個對象被觸發,函數就會立即返回。這時的返回值是WAIT_OBJECT_0和(WAIT_OBJECT_0+dwCount-1)之間的任何一個值。換句話說,如果返回值既不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么我們應該把返回值減去WAIT_OBJECT_0。得到的數值是我們在第二個參數中傳的句柄數組的一個索引,用來告訴我們被觸發的是那個對象。*/
//下面的事例代碼可以更清晰的對此進行解釋[html] view plain copyHANDLE h[3];//我的博客:<a href="http://blog.csdn.net/windows_nt">http://blog.csdn.net/windows_nt</a> h[0] = hPRocess1; h[1] = hProcess2; h[2] = hProcess3; DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000); switch(dw) { case WAIT_FAILED: //Bad call to function (invalid handle) break; case WAIT_TIMEOUT: //None of the objects became signaled within 5000 milliseconds break; case WAIT_OBJECT_0 + 0: //h[0] signaled, hProcess1 terminated break; case WAIT_OBJECT_0 + 1: //h[1] signaled, hProcess2 terminated break; case WAIT_OBJECT_0 + 2: //h[2] signaled, hProcess3 terminated break; }LPCSTR lpName );//事件內核對象的名字,可以為空
//新版本
HANDLE CreateEventEx(LPSECURITY_ATTRIBUTES psa, //安全屬性PCTSTR pszName, //名字DWORD dwFlags, //是否觸發DWORD dwDesiredaccess)
//dwDesiredAccess:允許我們指定在創建事件時返回的句柄對事件有何種訪問權限。這是一種創建事件句柄的新方法,它可以減少權限,相比較而言,CreateEvent()總是被授予全部權限。但CreateEventEx()更有用的地方在于它允許我們以減少權限的方式來打開一個已經存在的事件,而CreateEvent()總是要求全部權限。
//下邊這個例子展示了如何使用事件內核對象來對線程進行同步。[html] view plain copy//Create a global handle to a manual-reset, nonsignaled event. HANDLE g_hEvent; int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) { //Create the manual-reset, nonsignaled event g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //Spawn 3 new threads HANDLE hThread[3]; DWORD dwThread; hThread[0] = _beginthread(NULL, 0, wordCount, NULL, 0, &dwThread); hThread[1] = _beginthread(NULL, 0, SpellCheck, NULL, 0, &dwThread); hThread[2] = _beginthread(NULL, 0, GrammarCheck, NULL, 0, &dwThread); OpenFileAndReadContentsIntoMemory(...); //allow all 3 threads to access the memory SetEvent(g_hEvent); } DWORD WINAPI wordCount(PVOID pvParam) { //Wait until the file's data is in memory waitForSingleObject(g_hEvent, INFINITE); //access the memory block. return 0; } DWORD WINAPI SpellCheck(PVOID pvParam) { //Wait until the file's data is in memory waitForSingleObject(g_hEvent, INFINITE); //access the memory block. return 0; } DWORD WINAPI GrammarCheck(PVOID pvParam) { //Wait until the file's data is in memory waitForSingleObject(g_hEvent, INFINITE); //access the memory block. return 0; }下邊是我自己寫的一個事例片段,很簡單
[html] view plain copyCString strName = _T(""); UINT CBcgTestDlg::ThreadWorkFunc(LPVOID lPvoid) { for (int n = 0; n < 10000; n++) { strName = _T("http://blog.csdn.net/windows_nt"); strName = strName + _T("/n"); TRACE(strName); } return 0; } void CBcgTestDlg::OnOK() { CWinThread* pThread = NULL; for (int n = 0; n < 10000; ++n) { if (pThread) { WaitForSingleObject(pThread->m_hThread, INFINITE); delete pThread; } pThread = AfxBeginThread(ThreadWorkFunc, NULL, 0, CREATE_SUSPENDED, NULL); if (pThread) { pThread->m_bAutoDelete = FALSE; pThread->ResumeThread(); } } } 注意線程函數可以為類函數,但必須是靜態函數新聞熱點
疑難解答