系統API函數實現多線程及線程同步
1、線程的創建
須包含頭文件:#include <windows.h>
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
lpThreadAttributes:指向SECURITY_ATTRIBUTES結構體的指針,一般設為NULL,讓該線程使用默認的安全性;如果希望所有的子進程能夠繼承該線程對象的句柄,就必須設定一個SECURITY_ATTRIBUTES結構體,將它的bInheritHandle成員初始化為TRUE;
dwStackSize:設置線程初始化棧的大小,即線程可以將多少地址空間用於它自己的棧,以字節為單位;如果設為0,將使用與調用該函數的線程相同的棧空間大小;
lpStartAddress:指向新線程的入口函數的起始地址;
lpParameter:可通過該參數給新線程傳遞參數,參數值既可以是一個數值,也可以是一個指向其他信息的指針;
dwCreationFlags:設置新線程的附加標記,如果為CREATE_SUSPENDED,線程創建後處於暫停狀態,直到程序調用了ResumeThread函數為止;如果為0,線程在創建之後就立即運行;
lpThreadId:指向一個變量,用來接收新線程的ID,可以為NULL;
新線程的入口函數:
DWORD WINAPI ThreadProc( LPVOID lpParameter );
ThreadProc--為新線程的函數名稱,可以改為自定義的線程名稱;
2、線程同步
線程同步的方式包括三種:互斥對象、事件對象、關鍵代碼段
比較:
1.互斥對象和事件對象都屬於內核對象,利用內核對象進行線程同步時,速度較慢,但利用內核對象可以在多個進程中的各個線程間進行同步;
2.關鍵代碼段工作在用戶模式下,同步速度較快,但使用關鍵代碼段時容易進入死鎖狀態,因為在等待進入關鍵代碼段時無法設定超時值;
2.1互斥對象
2.1.1利用互斥對象實現線程同步
1)創建互斥對象
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
lpMutexAttributes:指向SECURITY_ATTRIBUTES結構的指針,可以設為NULL,讓互斥對象使用默認的安全性;
bInitialOwner:如果為TRUE,則創建這個互斥對象的線程獲得該對象的所有權;如果為FALSE,該線程將不具有所創建的互斥對象的所有權;
lpName:指定互斥對象的名稱,如果為NULL,則創建一個匿名的互斥對象;
返回值:如果該函數調用成功,返回所創建的互斥對象的句柄;如果創建的是命名的互斥對象,且在創建之前已存在該命名的互斥對象,則該函數將返回已存在的這個互斥對象的句柄,調用GetLastError函數將返回ERROR_ALREADY_EXISTS;
2)請求互斥對象的所有權
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
hHandle:所請求的對象的句柄;一旦獲得互斥對象的信號狀態,則該函數返回;如果未獲得互斥對象的信號狀態,則該函數會一直等待,線程會暫停;
dwMilliseconds:指定等待的時間間隔,單位為毫秒;如果指定的時間間隔已過,函數也會返回;如果設為0,該函數將測試該對象的狀態並立即返回;如果設為INFINITE,該函數會一直等到互斥對象的信號才會返回;
返回值:
WAIT_OBJECT_0 --所請求的對象是有信號狀態;
WAIT_TIMEOUT --指定的時間間隔已過,所請求的對象無信號;
WAIT_ABANDONED --所請求的對象是一個互斥對象,先前擁有該對象的線程在終止前未釋放該對象,將該對象的所有權授予當前調用線程,並將該互斥對象設為無信號狀態;
3)釋放指定對象的所有權
BOOL ReleaseMutex( HANDLE hMutex );
hMutex:需要釋放的互斥對象的句柄;
返回值:函數調用成功返回非0值,調用失敗返回0;
註意:互斥對象誰擁有誰釋放,請求幾次就需要相應地釋放幾次;
#include <WINDOWS.H> #include <IOSTREAM.H> DWORD WINAPI Fun1Proc(LPVOID lpParameter); //線程1的入口函數聲明 DWORD WINAPI Fun2Proc(LPVOID lpParameter); //線程2的入口函數聲明 int index=0; int tickets=100; HANDLE hMutex; void main() { HANDLE hThread1; HANDLE hThread2; //創建互斥對象 hMutex=CreateMutex(NULL,FALSE,NULL); //創建線程 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); } //線程1的入口函數 DWORD WINAPI Fun1Proc(LPVOID lpParameter){ while(1) { WaitForSingleObject(hMutex,INFINITE); if(tickets>0){ Sleep(1); cout << "Thread01 sell ticket:" << tickets-- << endl; }else{ break; } ReleaseMutex(hMutex); } return 0; } //線程2的入口函數 DWORD WINAPI Fun2Proc(LPVOID lpParameter){ while(1) { WaitForSingleObject(hMutex,INFINITE); if(tickets>0){ Sleep(1); cout << "Thread02 sell ticket:" << tickets-- <<endl; }else{ break; } ReleaseMutex(hMutex); } return 0; }
2.1.2利用命名互斥對象來保證程序只有一個實例運行
調用CreateMutex函數創建一個命名的互斥對象,再調用GetLastError函數判斷其返回值,如果返回ERROR_ALREADY_EXISTS,則表明已存在該命名的互斥對象,因此判斷已有該應用程序的一個實例在運行了;如果返回的不是ERROR_ALREADY_EXISTS,則表明這個互斥對象為新創建的,因而可以判斷當前啟動的是該應用程序的第一個實例運行;
void main() { HANDLE hThread1; HANDLE hThread2; //創建命名互斥對象 hMutex=CreateMutex(NULL,TRUE,"tickets"); if(hMutex){ if(ERROR_ALREADY_EXISTS == GetLastError()){ cout << "只可以運行一個程序實例" << endl; return; } } //創建線程 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); WaitForSingleObject(hMutex,INFINITE); ReleaseMutex(hMutex); ReleaseMutex(hMutex); Sleep(4000); }
2.2事件對象
2.2.1利用事件對象實現線程同步
1)事件對象分兩種類型:人工重置的事件對象、自動重置的事件對象;
當人工重置的事件對象得到通知時,等待該事件對象的所有線程均變為可調度線程,當線程等待到該對象的所有權之後,需調用ResetEvent函數手動地將該事件對象設置為無信號狀態;當自動重置的事件對象得到通知時,等待該事件對象的線程中只有一個線程變為可調度線程,當線程等到該對象的所有權之後,系統會自動將該事件對象設置為無信號狀態;
2)創建事件對象
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
lpEventAttributes:指向SECURITY_ATTRIBUTES結構體的指針,如果設為NULL,則使用默認的安全性;
bManualReset:如果為TRUE,則創建一個人工重置的事件對象;如果為FALSE,則創建一個自動重置的事件對象;
bInitialState:指定事件對象的初始狀態;如果為TRUE,那麽該事件對象初始是有信號狀態;如果為FALSE,初始是無信號狀態;
lpName:指定事件對象的名稱;如果為NULL,將創建一個匿名的事件對象;
3)設置事件對象狀態
BOOL SetEvent(HANDLE hEvent); //將指定的事件對象設置為有信號的狀態
hEvent:指定將要設置其狀態的事件對象的句柄;
返回值:函數調用成功返回非0值,調用失敗返回0;
4)重置事件對象狀態
BOOL ResetEvent(HANDLE hEvent); //將指定的事件對象設置為無信號的狀態
hEvent:指定將要重置其狀態的事件對象的句柄;
返回值:函數調用成功返回非0值,調用失敗返回0;
5)請求事件對象
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
#include <WINDOWS.H> #include <IOSTREAM.H> DWORD WINAPI Fun1Proc(LPVOID lpParameter); //線程1入口函數聲明 DWORD WINAPI Fun2Proc(LPVOID lpParameter); //線程2入口函數聲明 int tickets=100; HANDLE g_hEvent; void main() { HANDLE hThread1; HANDLE hThread2; //創建自動重置的事件對象 g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL); SetEvent(g_hEvent); //將事件對象設為有信號狀態 //創建線程 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); //關閉事件對象句柄 CloseHandle(g_hEvent); } //線程1的入口函數 DWORD WINAPI Fun1Proc(LPVOID lpParameter) { while(1) { //請求事件對象 WaitForSingleObject(g_hEvent,INFINITE); if(tickets>0){ Sleep(1); cout << "Thread01 sell ticket:" << tickets-- << endl; //當線程請求到自動重置的事件對象後會將該事件對象設置為無信號狀態 //在線程訪問完成後須調用SetEvent函數將該事件對象設為有信號狀態 SetEvent(g_hEvent); }else{ SetEvent(g_hEvent); break; } } return 0; } //線程2的入口函數 DWORD WINAPI Fun2Proc(LPVOID lpParameter) { while(1) { //請求事件對象 WaitForSingleObject(g_hEvent,INFINITE); if(tickets>0){ Sleep(1); cout << "Thread02 sell ticket:" << tickets-- << endl; SetEvent(g_hEvent); }else{ SetEvent(g_hEvent); break; } } return 0; }
2.2.2利用事件對象來保證程序只有一個實例運行
調用CreateEvent函數創建一個命名事件對象,再調用GetLastError函數判斷其返回值,如果返回ERROR_ALREADY_EXISTS,則表明已存在該命名事件對象,因此判斷已有該應用程序的一個實例在運行了;如果返回的不是ERROR_ALREADY_EXISTS,則表明這個事件對象為新創建的,因而可以判斷當前啟動的是該應用程序的第一個實例運行;
void main() { HANDLE hThread1; HANDLE hThread2; //創建自動重置的命名事件對象 g_hEvent=CreateEvent(NULL,FALSE,FALSE,"tickets"); if(g_hEvent){ if(ERROR_ALREADY_EXISTS == GetLastError()){ cout << "只可以運行一個程序實例" << endl; return; } } SetEvent(g_hEvent); //將事件對象設為有信號狀態 //創建線程 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); //關閉事件對象句柄 CloseHandle(g_hEvent); }
2.3關鍵代碼段
2.3.1利用關鍵代碼段實現線程同步
關鍵代碼段--也稱為臨界區,工作在用戶模式下;它是指一段代碼,在代碼能夠執行前,它必須獨占對某些資源的訪問權;通常把多線程中訪問同一種資源的那部分代碼當作關鍵代碼段;
1)創建關鍵代碼段
VOID InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
lpCriticalSection:指向CRITICAL_SECTION結構體的指針;作為返回值使用,在調用該函數前需構造一個CRITICAL_SECTION結構體類型的對象,然後將該對象地址傳遞給該函數;
2)卸載關鍵代碼段
VOID DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
3)獲得關鍵代碼段的所有權
VOID EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
該函數等待指定的關鍵代碼段的所有權,如果該所有權賦予了調用的線程,則函數返回;否則該函數會一直等待,導致線程等待;
4)釋放關鍵代碼段的所有權
VOID LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
#include <WINDOWS.H> #include <IOSTREAM.H> DWORD WINAPI Fun1Proc(LPVOID lpParameter); DWORD WINAPI Fun2Proc(LPVOID lpParameter); int tickets=100; CRITICAL_SECTION g_cs; //將關鍵代碼段對象定義為全局對象 void main() { HANDLE hThread1; HANDLE hThread2; hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); //創建關鍵代碼段 InitializeCriticalSection(&g_cs); Sleep(4000); //卸載關鍵代碼段 DeleteCriticalSection(&g_cs); } DWORD WINAPI Fun1Proc(LPVOID lpParameter){ while(1) { //獲得關鍵代碼段的所有權 EnterCriticalSection(&g_cs); Sleep(1); if(tickets>0){ Sleep(1); cout << "Thread01 sell ticket:" << tickets-- << endl; //釋放關鍵代碼段的所有權 LeaveCriticalSection(&g_cs); }else{ LeaveCriticalSection(&g_cs); break; } } return 0; } DWORD WINAPI Fun2Proc(LPVOID lpParameter){ while(1) { //獲得關鍵代碼段的所有權 EnterCriticalSection(&g_cs); Sleep(1); if(tickets>0){ Sleep(1); cout << "Thread02 sell ticket:" << tickets-- << endl; //釋放關鍵代碼段的所有權 LeaveCriticalSection(&g_cs); }else{ LeaveCriticalSection(&g_cs); break; } } return 0; }
2.3.2在MFC中使用關鍵代碼段
1.在類的構造函數中調用InitializeCriticalSection函數,在該類的析構函數中調用DeleteCriticalSection函數;
2.在程序中每次調用EnterCriticalSection函數獲得訪問關鍵代碼段中資源的所有權,一定要相應地調用LeaveCriticalSection函數釋放該所有權,否則其他等待的線程無法執行;
系統API函數實現多線程及線程同步