Windows執行緒同步的方法
Summary:
對於多執行緒程式設計,一個很重要的問題就是解決由資料共享引起的資料競爭的問題,通過一定的執行緒同步的方法能避免資料競爭。在Win32多執行緒中,同步方法包括使用者態同步方式:InterLock、CriticalSection、SRWLock和核心態同步方式:Event、Semaphore、Mutex等。
本文主要目的是收集這些同步的方法和涉及到API的含義和使用,可能不會涉及到更深入的比如何時使用何種方式更好以及為什麼,更深入的問題,單獨在以後積累並記錄。
一、資料競爭的例子
在分析同步的方法之前,先給出要解決的資料競爭的例子:
[cpp] view plaincopy- #include "stdafx.h"
- #include
- #include
- #include
- long g = 0;
- #define THREAD_COUNT 10 // 執行緒數
- #define ACCESS_TIMES 10000000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性
- void __cdecl ThreadProc(void *para)
- {
- printf("sub thread started\n");
- for (int i = 0;i < ACCESS_TIMES;i++)
- g = g + 1;
- printf("sub thread finished\n");
- _endthread(); // 可以省略,隱含會呼叫。
- }
- int main(int argc, char* argv[])
- {
- HANDLE hThread[THREAD_COUNT];
- for(int i =0;i < THREAD_COUNT;i++)
- hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL);
- for(int i = 0;i < THREAD_COUNT;i++)
- WaitForSingleObject(hThread[i],INFINITE);
- // 檢查結果
- if (g == ACCESS_TIMES*THREAD_COUNT)
- printf("Correct Result!\n");
- else
- printf("Error Result!\n");
- }
#include "stdafx.h" #include #include #include long g = 0; #define THREAD_COUNT 10 // 執行緒數 #define ACCESS_TIMES 10000000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性 void __cdecl ThreadProc(void *para) { printf("sub thread started\n"); for (int i = 0;i < ACCESS_TIMES;i++) g = g + 1; printf("sub thread finished\n"); _endthread(); // 可以省略,隱含會呼叫。 } int main(int argc, char* argv[]) { HANDLE hThread[THREAD_COUNT]; for(int i =0;i < THREAD_COUNT;i++) hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL); for(int i = 0;i < THREAD_COUNT;i++) WaitForSingleObject(hThread[i],INFINITE); // 檢查結果 if (g == ACCESS_TIMES*THREAD_COUNT) printf("Correct Result!\n"); else printf("Error Result!\n"); }
如果程式輸出Error Result,說明發生了資料競爭。
二、使用者態同步
需要注意的是,由於使用者態到核心態的轉換需要一定的開銷,所以如果能在使用者態進行同步控制,儘量選擇使用者態的方式。
(1)InterLocked原子操作
InterLocked是一系列的方法,提供了原子操作的實現。原子操作比較容易理解,直接呼叫對應的API就能進行簡單的同步了,但是這種方式,顯然只能適用於簡單的預定義的一些操作方法,比如自增、自減、加法等,具體有哪些方法,可以參考MSDN(http://msdn.microsoft.com/zh-cn/site/ms684122)
對於上面的資料競爭的例子,顯然可以使用自增的原子操作來解決了,對於自增操作,MS提供了InterlockedIncrement()和InterlockedIncrement64()來完成原子操作,分別操作32位和64位的整數(long,long long),引數為要進行自增的變數的地址。只需要將上述程式碼中,“g=g+1"修改為:
[cpp] view plaincopyprint?- InterlockedIncrement(&g); // g = g + 1;
InterlockedIncrement(&g); // g = g + 1;
即可解決資料競爭問題。
(2)Critical Section臨界區
百度百科:不論是硬體臨界資源,還是軟體臨界資源,多個程序必須互斥地對它進行訪問。每個程序中訪問臨界資源的那段程式碼稱為臨界區(Critical Section)(臨界資源是一次僅允許一個程序使用的共享資源)。每次只准許一個程序進入臨界區,進入後不允許其他程序進入。不論是硬體臨界資源,還是軟體臨界資源,多個程序必須互斥地對它進行訪問。
與臨界區相關的函式有:
EnterCriticalSection LeaveCriticalSectionInitializeCriticalSectionDeleteCriticalSectionTryEnterCriticalSection
EnterCriticalSection和LeaveCriticalSection是一對操作,表示進入和離開臨界區,同步控制一段程式碼的訪問,即在這兩個函式之間呼叫的程式碼,每次只允許一個執行緒執行。
InitializeCriticalSection和DeleteCriticalSection也是一對操作,分別是初始化和刪除臨界區(變數),在使用臨界區的時候,需要定義一個臨界區變數,代表一個臨界區資源。所以,一個程式可以使用多個臨界區變數來進行不同的程式碼段的保護。在呼叫EnterCriticalSection的時候,如果臨界區被其它執行緒佔有,那麼當前執行緒會等待資源,知道其它執行緒退出臨界區。TryEnterCriticalSection函式就是用來嘗試進入臨界區,如果無法進入臨界區(臨界區被佔有),那麼返回FALSE,不會阻塞執行緒。
下面是使用臨界區解決上述問題的程式碼:
[cpp] view plaincopyprint?- #include "stdafx.h"
- #include
- #include
- #include
- long g = 0;
- CRITICAL_SECTION g_cs; // 定義臨界區
- #define THREAD_COUNT 10 // 執行緒數
- #define ACCESS_TIMES 10000000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性
- void __cdecl ThreadProc(void *para)
- {
- printf("sub thread started\n");
- for (int i = 0;i < ACCESS_TIMES;i++)
- {
- EnterCriticalSection(&g_cs); // 進入臨界區
- g = g + 1;
- LeaveCriticalSection(&g_cs); // 退出臨界區
- }
- printf("sub thread finished\n");
- _endthread(); // 可以省略,隱含會呼叫。
- }
- int main(int argc, char* argv[])
- {
- InitializeCriticalSection(&g_cs); // 初始化臨界區
- HANDLE hThread[THREAD_COUNT];
- for(int i =0;i < THREAD_COUNT;i++)
- hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL);
- for(int i = 0;i < THREAD_COUNT;i++)
- WaitForSingleObject(hThread[i],INFINITE);
- DeleteCriticalSection(&g_cs); // 刪除臨界區
- // 檢查結果
- if (g == ACCESS_TIMES*THREAD_COUNT)
- printf("Correct Result!\n");
- else
- printf("Error Result!\n");
- }
#include "stdafx.h" #include #include #include long g = 0; CRITICAL_SECTION g_cs; // 定義臨界區 #define THREAD_COUNT 10 // 執行緒數 #define ACCESS_TIMES 10000000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性 void __cdecl ThreadProc(void *para) { printf("sub thread started\n"); for (int i = 0;i < ACCESS_TIMES;i++) { EnterCriticalSection(&g_cs); // 進入臨界區 g = g + 1; LeaveCriticalSection(&g_cs); // 退出臨界區 } printf("sub thread finished\n"); _endthread(); // 可以省略,隱含會呼叫。 } int main(int argc, char* argv[]) { InitializeCriticalSection(&g_cs); // 初始化臨界區 HANDLE hThread[THREAD_COUNT]; for(int i =0;i < THREAD_COUNT;i++) hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL); for(int i = 0;i < THREAD_COUNT;i++) WaitForSingleObject(hThread[i],INFINITE); DeleteCriticalSection(&g_cs); // 刪除臨界區 // 檢查結果 if (g == ACCESS_TIMES*THREAD_COUNT) printf("Correct Result!\n"); else printf("Error Result!\n"); }可見,臨界區能保護一個程式碼塊,使用起來比原子操作更靈活。
(3)SRWLOCK讀寫鎖
讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高併發性,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數為實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。
說明:在win32中,自旋鎖是核心定義,且只能在核心狀態下使用的一種鎖機制。所以一般的程式不會用到,需要一定的要求。而SRWLOCK讀寫鎖是使用者態下的讀寫鎖。
從使用上,SRWLOCK和臨界區的使用非常類似,讀寫鎖和臨界區的主要區別是,讀寫鎖對共享資源的訪問區分了讀者和寫者分離的功能。所以,SRWLOCK比臨界區多了一些方法。
AcquireSRWLockShared
AcquireSRWLockExclusive
ReleaseSRWLockShared
ReleaseSRWLockExclusive
InitializeSRWLock
TryAcquireSRWLockExclusive
TryAcquireSRWLockShared
SleepConditionVariableSRW
其中,AcquireSRWLockShared和AcquireSRWLockExclusive表示獲取讀鎖和獲取寫鎖(共享鎖和排他鎖)。ReleaseSRWLockShared和ReleaseSRWLockExclusive表示釋放讀鎖和寫鎖。和臨界區一樣,InitializeSRWLock是初始化。但是,SRWLock沒有提供刪除讀寫鎖的方法,不需要刪除。TryAcquireSRWLockExclusive和TryAcquireSRWLockShared也是用於非阻塞的方式獲取讀鎖和寫鎖,失敗返回FALSE。SleepConditionVariableSRW在下面再介紹。需要說明的是:TryAcquireSRWLockExclusive和TryAcquireSRWLockShared是Win7才開始提供支援的,詳細資訊參考MSDN。
下面是使用讀寫鎖解決上面的資料競爭的問題:(說明:這裡只需要獲取寫鎖,更多的程式可能會需要同時使用讀鎖和寫鎖)
[cpp] view plaincopyprint?- #include "stdafx.h"
- #include
- #include
- #include
- long g = 0;
- SRWLOCK g_srw; // 定義讀寫鎖
- #define THREAD_COUNT 10 // 執行緒數
- #define ACCESS_TIMES 10000000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性
- void __cdecl ThreadProc(void *para)
- {
- printf("sub thread started\n");
- for (int i = 0;i < ACCESS_TIMES;i++)
- {
- AcquireSRWLockExclusive(&g_srw); // 獲取寫鎖
- g = g + 1;
- ReleaseSRWLockExclusive(&g_srw); // 釋放寫鎖
- }
- printf("sub thread finished\n");
- _endthread(); // 可以省略,隱含會呼叫。
- }
- int main(int argc, char* argv[])
- {
- InitializeSRWLock(&g_srw); // 初始化讀寫鎖
- HANDLE hThread[THREAD_COUNT];
- for(int i =0;i < THREAD_COUNT;i++)
- hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL);
- for(int i = 0;i < THREAD_COUNT;i++)
- WaitForSingleObject(hThread[i],INFINITE);
- // 檢查結果
- if (g == ACCESS_TIMES*THREAD_COUNT)
- printf("Correct Result!\n");
- else
- printf("Error Result!\n");
- }
#include "stdafx.h" #include #include #include long g = 0; SRWLOCK g_srw; // 定義讀寫鎖 #define THREAD_COUNT 10 // 執行緒數 #define ACCESS_TIMES 10000000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性 void __cdecl ThreadProc(void *para) { printf("sub thread started\n"); for (int i = 0;i < ACCESS_TIMES;i++) { AcquireSRWLockExclusive(&g_srw); // 獲取寫鎖 g = g + 1; ReleaseSRWLockExclusive(&g_srw); // 釋放寫鎖 } printf("sub thread finished\n"); _endthread(); // 可以省略,隱含會呼叫。 } int main(int argc, char* argv[]) { InitializeSRWLock(&g_srw); // 初始化讀寫鎖 HANDLE hThread[THREAD_COUNT]; for(int i =0;i < THREAD_COUNT;i++) hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL); for(int i = 0;i < THREAD_COUNT;i++) WaitForSingleObject(hThread[i],INFINITE); // 檢查結果 if (g == ACCESS_TIMES*THREAD_COUNT) printf("Correct Result!\n"); else printf("Error Result!\n"); }
讀寫鎖的適用情況:讀寫鎖適合於對資料結構的讀次數比寫次數多得多的情況。更多相關問題,參考關於讀寫鎖的特性等的分析介紹。
(4)Condition Variable條件變數
條件變數一開始是在Linux中有,Window平臺是從Vista才開始支援條件變數(所以XP不支援)。
條件變數是利用執行緒間共享的全域性變數進行同步的一種機制,主要包括兩個動作:一個執行緒等待"條件變數的條件成立"而掛起;另一個執行緒使"條件成立"(給出條件成立訊號)。為了防止競爭,條件變數的使用總是和一個互斥鎖結合在一起。其實,在windows下,條件變數的使用總是和讀寫鎖或臨界區結合使用(因為Linux下沒有臨界區,所以這裡只說了互斥鎖)。
和條件變數相關的函式有:
Condition variable function
InitializeConditionVariable
SleepConditionVariableCS
SleepConditionVariableSRW
WakeAllConditionVariable
WakeConditionVariable
具體使用說明可以參考MSDN,由於條件變數和這裡的資料競爭的例子很難結合起來,這裡就不舉例了,而且它單獨是無法完成同步的,所以這裡也沒必要單獨作為一種同步的方法來說明,關於條件變數的使用場合和使用方法,參考其它內容。
三、核心態同步
上面介紹的都是使用者態的同步方法,win32多執行緒還提供了一些核心態同步的方式。從效能上來說,核心態同步方式比使用者態更低,原因是使用者態到核心態的轉換是有開銷的。但是核心態的優點在於是可以跨程序同步的,所以不僅僅是執行緒同步方式,也是一種程序同步方式。
(1)核心物件和狀態
在瞭解核心態同步之前,首先需要了解很重要的兩個函式:WaitForSingleObject和WaitForMultipleObjects。
1. 核心物件
核心物件只是核心分配的一個記憶體塊,並且只能由該核心訪問。該記憶體塊是一種資料結構,它的成員負責維護該物件的各種資訊。有些資料成員(如安全性描述符、使用計數等)在所有物件型別中是相同的,但大多數資料成員屬於特定的物件型別。例如,程序物件有一個程序I D 、一個基本優先順序和一個退出程式碼,而檔案物件則擁有一個位元組位移、一個共享模式和一個開啟模式。
參考:
總之,這裡要提到的核心態同步的物件,都是屬於核心物件,包括程序物件和執行緒物件也是屬於核心物件。另外要知道的是,核心物件使用相應的建立函式建立,返回都是控制代碼,即HANDLE物件。
2. 核心同步物件
在Windows NT核心物件中,提供了五種核心同步物件(Kernel Dispatcher Object),為:Event(事件)、Semaphore(訊號燈/訊號量)、Mutex(互斥)、Timer(定時器)、Thread(執行緒)。
3. 核心物件的狀態
在任何時刻,任何物件都處於兩種狀態中的一種:訊號態或非訊號態(參考上面的連結的說明,沒有找到官方證實這句話,但是至少對於核心同步物件,所有的物件應該都是有這兩個狀態的)。有時候,這兩個狀態稱為受信狀態(signaled state)和非受信狀態(nonsignaled state)或者通知狀態和未通知狀態。
到了這裡,我們就可以討論WaitForSingleObject了。WaitForSingleObject的引數是一個核心物件控制代碼,它的作用是:Waits until the specified object is in the signaled state or the time-out interval elapses。即等待指定的物件處於受信狀態或者出現超時,等待,表明如果執行WaitForSingleObject的時候,物件處於非受信狀態,那麼當前執行緒處於阻塞狀態。當然,WaitForMultipleObjects的作用就是等待多個狀態了。
說明,WaitForSingleObject對於某些核心物件是由副作用的,比如對於訊號量,等待成功會使得訊號量減1。
Change notification
Console input
Event
Memory resource notification
Mutex
Process
Semaphore
Thread
Waitable timer
其中加粗的幾個核心物件是多執行緒程式設計中會遇到的(三個核心態同步物件和一個執行緒物件)。理解了signaled state和nonsignaled state之後,下面的三種核心態同步方式就很容易理解了。
(2)Event事件
事件核心物件包括兩種:人工重置的事件和自動重置的事件。
當人工重置事件得到通知時,等待該事件的所有執行緒成為可排程執行緒;它沒有成功等待副作用。
當自動重置的事件得到通知時,等待該事件的執行緒中只有一個執行緒變為可排程執行緒。其成功等待的副作用是該物件自動重置為未通知狀態。
事件核心物件通過CreateEvent建立,初始可以是通知或未通知狀態。
人工事件一般用於一個執行緒通知另一個執行緒或者一個執行緒通知多個執行緒進行某一操作。自動事件適用於保護資源在同一時間只能有一個執行緒可以訪問,它能保證只有一個執行緒被啟用。
事件物件分為命名事件物件和未命名物件(named and unnamed event objects)。
和事件物件相關的函式有:CreateEvent/OpenEvent、ResetEvent、SetEvent、PulseEvent等。
其中,CreateEvent建立或開啟一個事件物件,其中,四個引數分別為安全屬性(核心物件都有安全屬性),是否為人工重置事件,初始狀態(TRUE表示signaled,FALSE表示nonsignled),事件物件的名字,如果為NULL,建立一個未命名事件物件。如果指定name的事件已經存在,則獲得EVENT_ALL_ACCESS的訪問許可權,第一個引數部分有效,後兩個引數忽略。OpenEvent用於開啟已存在的事件(所以一般用CreateEvent即可)。ResetEvent/SetEvent分別設定事件的狀態為nonsignaled和signaled。PulseEvent根據MSDN不可信,所以不推薦使用(相當於reset然後set的功能,但是並不可靠)。
總之,對於事件來說,常用的方法是CreateEvent,ResetEvent,SetEvent,然後利用WaitForSingleObject來等待事件(變成singled狀態)。
仍然針對上面的資料競爭的例子,使用事件解決的方法是:
[cpp] view plaincopyprint?- #include "stdafx.h"
- #include
- #include
- #include
- long g = 0;
- HANDLE g_event; // 事件控制代碼
- #define THREAD_COUNT 10 // 執行緒數
- #define ACCESS_TIMES 100000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性
- void __cdecl ThreadProc(void *para)
- {
- printf("sub thread started\n");
- for (int i = 0;i < ACCESS_TIMES;i++)
- {
- WaitForSingleObject(g_event, INFINITE); // 等待事件
- ResetEvent(g_event); // 重置事件,讓其他執行緒繼續等待(相當於獲取鎖)
- g = g + 1;
- SetEvent(g_event); // 設定事件,讓其他執行緒可以獲取事件(相當於釋放鎖)
- }
- printf("sub thread finished\n");
- _endthread(); // 可以省略,隱含會呼叫。
- }
- int main(int argc, char* argv[])
- {
- g_event = CreateEvent(NULL, FALSE, FALSE, NULL); // 建立事件核心物件
- SetEvent(g_event);
- HANDLE hThread[THREAD_COUNT];
- for(int i = 0;i < THREAD_COUNT;i++)
- hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL);
- for(int i = 0;i < THREAD_COUNT;i++)
- WaitForSingleObject(hThread[i],INFINITE);
- // 檢查結果
- if (g == ACCESS_TIMES*THREAD_COUNT)
- printf("Correct Result!\n");
- else
- printf("Error Result!\n");
- }
#include "stdafx.h" #include #include #include long g = 0; HANDLE g_event; // 事件控制代碼 #define THREAD_COUNT 10 // 執行緒數 #define ACCESS_TIMES 100000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性 void __cdecl ThreadProc(void *para) { printf("sub thread started\n"); for (int i = 0;i < ACCESS_TIMES;i++) { WaitForSingleObject(g_event, INFINITE); // 等待事件 ResetEvent(g_event); // 重置事件,讓其他執行緒繼續等待(相當於獲取鎖) g = g + 1; SetEvent(g_event); // 設定事件,讓其他執行緒可以獲取事件(相當於釋放鎖) } printf("sub thread finished\n"); _endthread(); // 可以省略,隱含會呼叫。 } int main(int argc, char* argv[]) { g_event = CreateEvent(NULL, FALSE, FALSE, NULL); // 建立事件核心物件 SetEvent(g_event); HANDLE hThread[THREAD_COUNT]; for(int i = 0;i < THREAD_COUNT;i++) hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL); for(int i = 0;i < THREAD_COUNT;i++) WaitForSingleObject(hThread[i],INFINITE); // 檢查結果 if (g == ACCESS_TIMES*THREAD_COUNT) printf("Correct Result!\n"); else printf("Error Result!\n"); }
說明:實際執行就會發現,核心態事件物件同步的方法比使用者態的方法的效率低很多,如果這裡的ACCESS_TIMES如果太大,執行時間相比使用者態的方法多很多。當然,再次強調,這裡都是用這一個例子,只是為了分析各種方法實現同步的實現,實際應用,顯然是有所取捨的,不同的同步方法有不同的合適的使用情景。
(3)Semaphore訊號量
訊號量用來對資源進行計數。它包含兩個32位值,一個表示能夠使用的最大資源數量,一個表示當前可用的資源數量。
訊號量的使用規則為:如果當前資源數量大於0,發出訊號量訊號;如果當前資源數量是0,不發出訊號量訊號;不允許當前資源數量為負值
當前資源數量不能大於最大訊號數量。
當呼叫等待函式時,它會檢查訊號量的當前資源數量。如果它的值大於0,那麼計數器減1,呼叫執行緒處於可排程狀態。如果當前資源是0,則呼叫函式的執行緒進入等待狀態。當另一個執行緒對訊號量的當前資源通過ReleaseSemaphore進行遞增時,系統會記住該等待執行緒,並將其變為可排程狀態。
對於訊號量,相關的函式有:CreateSemaphore/OpenSemaphore、ReleaseSemaphore。WaitForSingleObject對於訊號量,成功等待的副作用是使得訊號量減1。從某種角度理解,事件相當於最大計數為1的訊號量。
下面的例子是使用最大計數為1的訊號量來解決上面的資料競爭問題:
[cpp] view plaincopyprint?- #include "stdafx.h"
- #include
- #include
- #include
- long g = 0;
- HANDLE g_semaphore; // 訊號量物件控制代碼
- #define THREAD_COUNT 10 // 執行緒數
- #define ACCESS_TIMES 100000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性
- void __cdecl ThreadProc(void *para)
- {
- printf("sub thread started\n");
- for (int i = 0;i < ACCESS_TIMES;i++)
- {
- WaitForSingleObject(g_semaphore, INFINITE); // 等待訊號量
- // 獲取訊號量後計數會減1
- g = g + 1;
- ReleaseSemaphore(g_semaphore,1, NULL); // 訊號量加1
- }
- printf("sub thread finished\n");
- _endthread(); // 可以省略,隱含會呼叫。
- }
- int main(int argc, char* argv[])
- {
- g_semaphore = CreateSemaphore(NULL, 0, 1, NULL); // 建立訊號量,初始計數為0,最大計數為1
- ReleaseSemaphore(g_semaphore,1, NULL); // 將計數設定為1
- HANDLE hThread[THREAD_COUNT];
- for(int i = 0;i < THREAD_COUNT;i++)
- hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL);
- for(int i = 0;i < THREAD_COUNT;i++)
- WaitForSingleObject(hThread[i],INFINITE);
- // 檢查結果
- if (g == ACCESS_TIMES*THREAD_COUNT)
- printf("Correct Result!\n");
- else
- printf("Error Result!\n");
- }
#include "stdafx.h" #include #include #include long g = 0; HANDLE g_semaphore; // 訊號量物件控制代碼 #define THREAD_COUNT 10 // 執行緒數 #define ACCESS_TIMES 100000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性 void __cdecl ThreadProc(void *para) { printf("sub thread started\n"); for (int i = 0;i < ACCESS_TIMES;i++) { WaitForSingleObject(g_semaphore, INFINITE); // 等待訊號量 // 獲取訊號量後計數會減1 g = g + 1; ReleaseSemaphore(g_semaphore,1, NULL); // 訊號量加1 } printf("sub thread finished\n"); _endthread(); // 可以省略,隱含會呼叫。 } int main(int argc, char* argv[]) { g_semaphore = CreateSemaphore(NULL, 0, 1, NULL); // 建立訊號量,初始計數為0,最大計數為1 ReleaseSemaphore(g_semaphore,1, NULL); // 將計數設定為1 HANDLE hThread[THREAD_COUNT]; for(int i = 0;i < THREAD_COUNT;i++) hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL); for(int i = 0;i < THREAD_COUNT;i++) WaitForSingleObject(hThread[i],INFINITE); // 檢查結果 if (g == ACCESS_TIMES*THREAD_COUNT) printf("Correct Result!\n"); else printf("Error Result!\n"); }
(4)Mutex互斥(互斥物件,互斥體)
互斥器保證執行緒擁有對單個資源的互斥訪問權。互斥物件類似於關鍵程式碼區(臨界區),但它是一個核心物件。互斥器不同於其他核心物件,它有一個“執行緒所有權”的概念。它如果被某個執行緒等待成功,就屬於該執行緒。
由於和臨界區和讀寫鎖很類似,使用也是很類似的。和Mute相關的函式主要有:CreateMutex/OpenMutex,ReleaseMutex。很顯然,Create是建立,Open是開啟已存在的命名互斥物件,ReleaseMutex是釋放互斥物件。幾乎和臨界區的函式一樣,當然,用WaitForSingleObject等待互斥體,類似於進入臨界區的操作了。
程式碼如下:
[cpp] view plaincopyprint?- #include "stdafx.h"
- #include
- #include
- #include
- long g = 0;
- HANDLE g_mutex; // 互斥物件控制代碼
- #define THREAD_COUNT 10 // 執行緒數
- #define ACCESS_TIMES 100000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性
- void __cdecl ThreadProc(void *para)
- {
- printf("sub thread started\n");
- for (int i = 0;i < ACCESS_TIMES;i++)
- {
- WaitForSingleObject(g_mutex, INFINITE); // 等待互斥物件
- g = g + 1;
- ReleaseMutex(g_mutex); // 釋放互斥物件
- }
- printf("sub thread finished\n");
- _endthread(); // 可以省略,隱含會呼叫。
- }
- int main(int argc, char* argv[])
- {
- g_mutex = CreateMutex(NULL, FALSE, NULL); // 建立互斥核心物件
- HANDLE hThread[THREAD_COUNT];
- for(int i = 0;i < THREAD_COUNT;i++)
- hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL);
- for(int i = 0;i < THREAD_COUNT;i++)
- WaitForSingleObject(hThread[i],INFINITE);
- // 檢查結果
- if (g == ACCESS_TIMES*THREAD_COUNT)
- printf("Correct Result!\n");
- else
- printf("Error Result!\n");
- }
#include "stdafx.h" #include #include #include long g = 0; HANDLE g_mutex; // 互斥物件控制代碼 #define THREAD_COUNT 10 // 執行緒數 #define ACCESS_TIMES 100000 // 訪問共享變數的次數,增大其值,增加資料競爭發生的可能性 void __cdecl ThreadProc(void *para) { printf("sub thread started\n"); for (int i = 0;i < ACCESS_TIMES;i++) { WaitForSingleObject(g_mutex, INFINITE); // 等待互斥物件 g = g + 1; ReleaseMutex(g_mutex); // 釋放互斥物件 } printf("sub thread finished\n"); _endthread(); // 可以省略,隱含會呼叫。 } int main(int argc, char* argv[]) { g_mutex = CreateMutex(NULL, FALSE, NULL); // 建立互斥核心物件 HANDLE hThread[THREAD_COUNT]; for(int i = 0;i < THREAD_COUNT;i++) hThread[i] = (HANDLE)_beginthread(ThreadProc,0,NULL); for(int i = 0;i < THREAD_COUNT;i++) WaitForSingleObject(hThread[i],INFINITE); // 檢查結果 if (g == ACCESS_TIMES*THREAD_COUNT) printf("Correct Result!\n"); else printf("Error Result!\n"); }
總結:這裡介紹了使用者態和核心態的同步物件和基本函式的使用。這裡只是為了演示其使用和理解其概念,針對實際應用,需要根據實際的case選擇合適的方法進行同步。
相關文章: