win32API多線程編程
- 2019 年 10 月 29 日
- 筆記
win32線程API
在Windows平台下可以通過Windows的線程庫來實現多線程編程。
對於多線程程序可以使用Visual Studio調試工具進行調試,也可以使用多核芯片廠家的線程分析調試工具進行調試。
Win32 API(了解Windows,代碼小,效率高)
- Windows操作系統為內核以及應用程序之間提供的接口
- 將內核提供的功能進行函數封裝
- 應用程序通過調用相關的函數獲得相應的系統功能
_beginthread
- _beginthread(函數名,棧大小,參數指針)
- Win32 函數庫中提供了操作多線程的函數, 包括創建線程、管理線程、終止線程、線程同步等接口。
線程函數(線程開始執行的函數)
DWORD WINAPI ThreadFunc (LPVOID
lpvThreadParm );
線程創建
HANDLE CreateThread (
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId ); - 第一個參數lpThreadAtt,是一個指向SECURITY- ATTRIBUTES結構的指針,該結構制定了線程的安全屬性,缺省為 NULL。
第二個參數dwStackSize,是棧的大小,一般設置為0。
第三個參數lpFun是新線程開始執行時,線程函數的入口地址。它必須是將要被新線程執行的函數地址,不能為NULL。
第四個參數lpParameter,是線程函數定義的參數。可以通過這個參數傳送值,包括指針或者NULL 。
第五個參數dwCreationFlags,控制線程創建的附加標誌,可以設置兩種值。0表示線程在被創建後就會立即開始執行;如果該參數為CREATE_SUSPENDED,則系統產生線程後,該線程處於掛起狀態,並不馬上執行,直至函數ResumeThread被調用;
第六個參數lpThreadID,為指向32位變量的指針,該參數接受所創建線程的ID號。如果創建成功則返回線程的ID,否則返回NULL。 - CreateThread不會執行C運行時數據塊, 因此在C運行時庫的應用程序中,不能使用CreateThread創建線程,
微軟提供了另外的創建線程的方法:創建線程用process.h頭文件中聲明的C執行時期鏈接庫函數_beginthread。
語法:
hThread = _beginthread (
void( __cdecl start_address )( void ),
unsigned stack_size, void *arglist) ;
線程函數的語法:
void __cdecl ThreadProc (void * pParam) ;
#include "stdio.h" #include <windows.h> #include <process.h> #include <iostream> #include <fstream> using namespace std; //使用_beginthread函數創建線程的例子。 void ThreadFunc1(PVOID param) { while (1) { Sleep(1000); cout << " This is ThreadFunc1 " << endl; } } void ThreadFunc2(PVOID param) { while (1) { Sleep(1000); cout << " This is ThreadFunc2 " << endl; } } //可以多次執行本程序,查看兩個線程函數執行的順序。 int main() { int i = 0; _beginthread(ThreadFunc1, 0, NULL); _beginthread(ThreadFunc2, 0, NULL); Sleep(5000); cout << "end" << endl; return 0; }
創建執行掛起終止
- CreateThread(NULL, 0, FunOne, (void*)&input, CREATE_SUSPENDED, NULL);
- 安全屬性 棧大小 線程函數 參數指針 附加標誌 ID號(32位int指針)
- 附加標誌為0 創建即可執行
- 附加標誌為CREATE_SUSPENDED 創建就要掛起
- ResumeThread(句柄) 掛起計數器-1,為0則進行
- SuspendThread(句柄) 掛起計數器+1
- TerminateThread(hand1, 1) 第二個參數為exitcode
- 調用SetThreadPriority函數設置線程的相對優先級,例如
Bool SetThreadPriority (HANDLE hPriority , int nPriority)
參數hPriority 指向待設置的線程句柄 - nPriority 是線程的相對優先級,可以是以下的值:
空閑:THREAD – PRIORITY- IDLE 15
最低線程:THREAD – PRIORITY- LOWEST 2
低於正常線程:THREAD – PRIORITY- BELOW- NORMAL 1
正常線程:THREAD – PRIORITY- NORMAL 0
高於正常線程:THREAD – PRIORITY- ABOVE – NORMAL -1
最高線程:THREAD – PRIORITY- HIGHEST -2
關鍵時間:THREAD – PRIORITY- TIME – CRITICAL -15 -
掛起與恢複函數原型
DWORD SuspendThread(HANDLE hThread);
掛起指定的線程(慎用,不處理同步對象)
如果函數執行成功,則線程的執行被終止
每次調用SuspendThread() 函數,線程將掛起計數器的值增1DWORD ResumeThread(HANDLE hThread);
結束線程的掛起狀態來執行這個線程
每次調用ResumeThread() 函數,線程將掛起計數器的值減1
若掛起計數器的值為0,則不會再減
#include "stdio.h" #include <windows.h> #include <iostream> using namespace std; //簡單的多線程創建、執行、掛起、終止的程序例子。 DWORD WINAPI FunOne(LPVOID param) { while (true) { Sleep(1000); cout << "hello! "; //cout<<"hello! "<<*((int*)param); } return 0; } DWORD WINAPI FunTwo(LPVOID param) { while (true) { Sleep(1000); cout << "world! "; } return 0; } //注意創建線程函數的第五個參數的運用。 //輸入1和2數字,可以控制線程的啟動和終止。 //注意連續輸入1和2數字線程的運行情況。 //線程的終止函數。 int main(int argc, char* argv[]) { int input = 0; //創建線程。這裡第四個參數值設為NULL也可以,因為線程函數里沒有使用輸入參數。 HANDLE hand1 = CreateThread(NULL, 0, FunOne, (void*)&input, CREATE_SUSPENDED, NULL); HANDLE hand2 = CreateThread(NULL, 0, FunTwo, (void*)&input, CREATE_SUSPENDED, NULL); while (true) { cin >> input; if (input == 1) { //恢複線程 ResumeThread(hand1); ResumeThread(hand2); } if (input == 2) { //掛起線程 SuspendThread(hand1); SuspendThread(hand2); } if (input == 0) { //終止線程 TerminateThread(hand1, 1); TerminateThread(hand2, 1); } if (input == 9) return 0; }; return 0; }
利用全局變量實現同步
這樣做可能會有一個問題,主線程結束時其他線程也就跟着結束了
- 全局變量
進程中的所有線程均可以訪問所有的全局變量,因而全局變量成為Win32多線程通信的最簡單方式。
int var; //全局變量
UINT ThreadFunction ( LPVOID pParam {
while (var)
{
…… //線程處理
}
return 0; }
var是一個全局變量,任何線程均可以訪問和修改。線程間可以利用此特性達到線程同步的目的。
#include "stdio.h" #include <windows.h> #include <iostream> using namespace std; //使用全局變量同步線程的例子。 //全局變量。 int globalvar = false; DWORD WINAPI ThreadFunc(LPVOID pParam) { cout << " ThreadFunc " << endl; Sleep(100); //修改全局變量的值。 globalvar = true; return 0; } DWORD WINAPI ThreadFunc1(LPVOID pParam) { while (1) cout << " ThreadFunc111 " << endl; return 0; } DWORD WINAPI ThreadFunc2(LPVOID pParam) { while (1) cout << " ThreadFunc222 " << endl; return 0; } //這種方式可能存在一些問題。 int main() { HANDLE hthread1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL); HANDLE hthread2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL); HANDLE hthread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL); if (!hthread) { cout << "Thread Create Error ! " << endl; CloseHandle(hthread); } bool b =SetThreadPriority(hthread,15); cout<<GetThreadPriority(hthread1)<<endl; cout<<GetThreadPriority(hthread2)<<endl; cout<<GetThreadPriority(hthread)<<endl; //循環判斷全局變量的值。 while (!globalvar) cout << "Thread while" << endl; cout << "Thread exit" << endl; return 0; }
Thread while ThreadFunc111 ThreadFunc111 ThreadFunc111 ThreadFunc111 ThreadFunc222 ThreadFunc222 ThreadFunc222 ThreadFunc222 Thread while Thread while Thread while Thread while ThreadFunc222 ThreadFunc222 ThreadFunc111 Thread while Thread while ThreadFunc222 ThreadFunc111 Thread exit ThreadFunc111 ThreadFunc222 ThreadFunc222 ThreadFunc111 ThreadFunc111 ThreadFunc222 ThreadFunc111
利用事件實現同步
-
事件是WIN32提供的最靈活的線程間同步方式。
事件存在兩種狀態:
激髮狀態(signaled or true)
未激髮狀態(unsignal or false)
事件可分為兩類:
手動設置:這種對象只能用程序來手動設置,在需要該事件或者事件發生時,採用SetEvent及ResetEvent來進行設置。
SetEvent只有一個參數,該參數指定了事件對象的句柄值,若事件成功激發,返回TRUE;
ResetEvent函數將事件對象恢復到最初的非激髮狀態,只有一個參數,成功後返回真
自動恢復:一旦事件發生並被處理後,自動恢復到沒有事件狀態,不需要再次設置。 - CreateEvent(NULL, FALSE, FALSE, NULL);
- 第一個參數還是安全,默認為NULL,二參代表事件類型,true為手動清除信號,false為自動清除
- 三參:事件的初始狀態 四參:事件的名稱
- setEvent(句柄)
- WaitForSingleObject(evRead, INFINITE);等待某個事件
WaitForSingleObject (evFinish, INFINITE)
參數1:等待對象
參數2:等待時間
返回值:WAIT_OBJECT_0(激發)
WAIT_TIMEOUT(超時)
WAIT_FAILED(錯誤) - WaitForMultipleObjects(2 ,evFin ,TRUE ,INFINITE)
參數1:等待的句柄數
參數2:等待的句柄數組
參數3:確定是否等待所有句柄激發後才返回
參數4:等待時間
返回值: WAIT_OBJECT_I(激發事件在句柄數組中的索引) -
例子:有三個線程
主線程、讀線程ReadThread、寫線程WriteThread
讀線程ReadThread必須在寫線程WriteThread 的寫操作完成之後才能進行讀操作
主線程必須在讀線程ReadThread 的讀操作完成後才結束
定義兩個事件對象evRead,evFinish
evRead由寫線程WriteThread用於通知讀線程ReadThread 進行讀操作
evFinish由讀線程ReadThread用於通知主線程讀操作已經結束
#include "stdio.h" #include <windows.h> #include <iostream> using namespace std; //**使用事件機制同步線程的例子。 //兩個事件。 HANDLE evRead, evFin; HANDLE evWrite; void ReadThread(LPVOID param) { //等待讀事件。 WaitForSingleObject(evRead, INFINITE); cout << "Reading" << endl; //ResetEvent(evRead);//evRead為手動恢復類型時打開 SetEvent(evWrite); WaitForSingleObject(evRead, INFINITE); cout << "Reading11111" << endl; //激活結束事件。 SetEvent(evFin); } void WriteThread(LPVOID param) { cout << "Writing" << endl; //激活讀事件。 SetEvent(evRead); WaitForSingleObject(evWrite, INFINITE); cout << "Writing11111" << endl; SetEvent(evRead); } int main(int argc, char* argv[]) { //創建兩個事件,注意事件參數的含義。 evRead = CreateEvent(NULL, FALSE, FALSE, NULL); evFin = CreateEvent(NULL, FALSE, FALSE, NULL); evWrite = CreateEvent(NULL, FALSE, FALSE, NULL); //evRead = CreateEvent (NULL ,TRUE ,FALSE ,NULL) ;//修改第二個參數為TRUE //evFin = CreateEvent (NULL ,TRUE ,FALSE ,NULL) ;//修改第二個參數為TRUE //evWrite = CreateEvent (NULL ,TRUE ,FALSE ,NULL) ;//修改第二個參數為TRUE _beginthread(ReadThread, 0, NULL); _beginthread(WriteThread, 0, NULL); //等待結束事件。 WaitForSingleObject(evFin, INFINITE); cout << "The Program is End" << endl; return 0; }
臨界區
- 防止多個線程同時執行一個特定代碼段的機制
適用於多個線程操作之間沒有先後順序,但要求互斥的同步
多個線程訪問同一個臨界區的原則:
一次最多只能一個線程停留在臨界區內
不能讓一個線程無限地停留在臨界區內,否則其它線程將不能進入該臨界區
定義臨界區變量的方法如下:
CRITICAL_SECTION gCriticalSection;
通常情況下,CRITICAL_SECTION結構體應該被定義為全局變量,便於進程中的所有線程可以方便地按照變量名來引用該結構體。 - 初始化臨界區
VOID WINAPI InitializeCriticalSection (
LPCRITICAL_SECTION lpCriticalSection );
刪除臨界區
VOID WINAPI DeleteCriticalSection (
LPCRITICAL_SECTION lpCriticalSection );
進入臨界區
VOID WINAPI EnterCriticalSection (
LPCRITICAL_SECTION lpCriticalSection );
執行該語句時,程序會判斷cs對象是否已被鎖定,若沒鎖定則線程進入臨界區,並將cs置為鎖定狀態;否則,線程線程被阻塞以等待cs解鎖。
離開臨界區
VOID WINAPI LeaveCriticalSection (
LPCRITICAL_SECTION lpCriticalSection );
線程執行該語句後,程序自動將cs解鎖,並喚醒等待cs解鎖的線程 - 使用臨界區編程的一般方法是:
void WriteData()
{
EnterCriticalSection(&gCriticalSection);
//do something
LeaveCriticalSection(&gCriticalSection);
}
例子
假如一個銀行系統有兩個線程執行取款任務,一個使用存摺在櫃檯取款,一個使用銀行卡在ATM取款。若不加控制,很可能賬戶餘額不足兩次取款的總額,但還可以把錢取走。
#include "stdio.h" #include <windows.h> #include <iostream> #include <process.h> #include <iostream> #include <fstream> using namespace std; //**使用臨界區機制同步線程。 int total = 100; HANDLE evFin[2]; CRITICAL_SECTION cs;//臨界區。 //可以去掉臨界區機制,查看是否出現錯誤。(需要輔助sleep函數) void WithdrawThread1(LPVOID param) { EnterCriticalSection(&cs);//進入臨界區 if (total - 90 >= 0) {//Sleep(100); total -= 90; cout << "You withdraw 90" << endl; } else cout << "You do not have that much money" << endl; LeaveCriticalSection(&cs);//退出臨界區 SetEvent(evFin[0]); } void WithdrawThread2(LPVOID param) { EnterCriticalSection(&cs);//進入臨界區 if (total - 20 >= 0) { total -= 20; cout << "You withdraw 20" << endl; } else cout << "You do not have that much money" << endl; LeaveCriticalSection(&cs);//退出臨界區 //LeaveCriticalSection(&cs) ; SetEvent(evFin[1]); } int main(int argc, char* argv[]) { evFin[0] = CreateEvent(NULL, FALSE, FALSE, NULL); evFin[1] = CreateEvent(NULL, FALSE, FALSE, NULL); InitializeCriticalSection(&cs);//初始化臨界區。 _beginthread(WithdrawThread1, 0, NULL);//創建線程順序影響執行的結果。 _beginthread(WithdrawThread2, 0, NULL); //_beginthread(WithdrawThread1 , 0 , NULL) ; int id = WaitForMultipleObjects(2, evFin, TRUE, INFINITE);//等待兩個事件都激活。 cout << "句柄數組中的索引:" << id << endl; DeleteCriticalSection(&cs);//刪除臨界區 cout << total << endl; return 0; }
先1後2
先2後1
互斥量
- 通常用於協調多個線程或進程的活動,通過對資源「鎖定」和「取消鎖定」,來控制對共享資源的訪問。
當一個互斥量被一個線程鎖定了,其他試圖對其加鎖的線程就會被阻塞;當對互斥量加鎖的線程解除鎖定後,則被阻塞的線程中的一個會得到互斥量。
互斥量的作用是保證每次只能有一個線程獲得互斥量
使用CreateMutex函數創建:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
安全屬性 是否手動清除(true為手動) 名稱 - 相關的API:
CreateMutex 創建一個互斥對象,返回對象句柄;
OpenMutex 打開並返回一個已存在的互斥對象的句柄,使之後續訪問;
ReleaseMutex 釋放對互斥對象的佔用,使之成為可用;
使用互斥量的一般方法是:
void Writedata()
{
WaitForSingleObject(hMutex,…);
…//do something
ReleaseMutex(hMutex);
}
#include "stdio.h" #include <windows.h> #include <iostream> #include <process.h> #include <iostream> #include <fstream> using namespace std; //**互斥量的使用方法。 #define THREAD_INSTANCE_NUMBER 3 LONG g_fResourceInUse = FALSE; LONG g_lCounter = 0; DWORD ThreadProc(void* pData) { int ThreadNumberTemp = (*(int*)pData); HANDLE hMutex; if ((hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "Mutex.Test")) == NULL) { cout << "Open Mutex error!" << endl; } //cout << "ThreadProc is running hMutexxxxxxx!!" << ThreadNumberTemp <<endl; WaitForSingleObject(hMutex, INFINITE);//獲取互斥量。 cout << "ThreadProc is running!!" << ThreadNumberTemp << endl; cout << "ThreadProc gets the mutex-" << ThreadNumberTemp << endl; ReleaseMutex(hMutex);//釋放互斥量。 CloseHandle(hMutex); return 0; } int main(int argc, char* argv[]) { int i; DWORD ID[THREAD_INSTANCE_NUMBER]; HANDLE h[THREAD_INSTANCE_NUMBER]; HANDLE hMutex; if ((hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "Mutex.Test")) == NULL) { if ((hMutex = CreateMutex(NULL, FALSE, "Mutex.Test")) == NULL) //注意第二個參數,當前線程是否擁有創建的鎖 { cout << "Create Mutex error!" << endl; return 0; } } //ReleaseMutex(hMutex); //CreateMutex函數第二參數為TRUE時打開 //獲取信號量的位置不同,將產生不同的結果。 //WaitForSingleObject(hMutex,INFINITE); for (i = 0; i < THREAD_INSTANCE_NUMBER; i++) { WaitForSingleObject(hMutex, INFINITE);//獲取互斥量。本線程可重複獲取,但解鎖時需釋放相同的次數。 h[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (void*)&ID[i], 0, &(ID[i])); //WaitForSingleObject(hMutex,INFINITE);//演示重複獲取互斥量 if (h[i] == NULL) cout << "CreateThread error" << ID[i] << endl; else cout << "CreateThread: " << ID[i] << endl; ReleaseMutex(hMutex); //Sleep(1000); } //ReleaseMutex(hMutex);//ReleaseMutex(hMutex);ReleaseMutex(hMutex);//演示釋放重複獲取的互斥量 WaitForMultipleObjects(THREAD_INSTANCE_NUMBER, h, TRUE, INFINITE); cout << "Close the Mutex Handle! " << endl; CloseHandle(hMutex); return 0; }
信號量
- 信號量是一個核心對象,擁有一個計數器,可用來管理大量有限的系統資源
當計數值大於零時,信號量為有信號狀態
當計數值為零時,信號量處於無信號狀態
創建信號量
HANDLE CreateSemaphore (PSECURITY_ATTRIBUTE psa,
LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName);
安全屬性 初始數量 最大數量 信號量名稱 - 釋放信號量
BOOL WINAPI ReleaseSemaphore(HANDLE hSemaphore,
LONG lReleaseCount, //信號量的當前資源數增加lReleaseCount
LPLONG lpPreviousCount);
打開信號量
HANDLE OpenSemaphore (DWORD fdwAccess,
BOOL bInherithandle, PCTSTR pszName );
// exa7.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <windows.h> #include <iostream> using namespace std; //**使用信號量機制同步線程。 #define THREAD_INSTANCE_NUMBER 3 DWORD foo(void * pData) { int ThreadNumberTemp = (*(int*) pData); HANDLE hSemaphore; if ((hSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "Semaphore.Test")) == NULL) { cout << "Open Semaphore error!" << endl; } WaitForSingleObject(hSemaphore,INFINITE);//獲取信號量。 cout << "foo is running!!!" << ThreadNumberTemp << endl; cout << "foo gets the semaphore-" << ThreadNumberTemp<< endl; ReleaseSemaphore(hSemaphore, 1, NULL);//釋放一個單位的信號量 CloseHandle(hSemaphore); return 0; } int main(int argc, char* argv[]) { int i; DWORD ThreadID[THREAD_INSTANCE_NUMBER]; HANDLE hThread[THREAD_INSTANCE_NUMBER]; HANDLE hSemaphore; if ((hSemaphore = CreateSemaphore(NULL,1,1, "Semaphore.Test")) == NULL ) { cout << "Create Semaphore error!" << endl; return 0; } //與互斥量一樣,這裡獲取信號量的位置不同,會產生不同的結果。 for (i=0;i<THREAD_INSTANCE_NUMBER;i++) { WaitForSingleObject(hSemaphore,INFINITE);//獲取信號量。不可重入。 hThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) foo, (void *)&ThreadID[i], 0, &(ThreadID[i])); if (hThread[i] == NULL) cout << "CreateThread error" << ThreadID[i] << endl; else cout << "CreateThread: " << ThreadID[i] << endl; ReleaseSemaphore(hSemaphore, 1, NULL); } WaitForMultipleObjects(THREAD_INSTANCE_NUMBER,hThread,TRUE,INFINITE); cout << "Close the Semaphore Handle! " << endl; CloseHandle(hSemaphore); return 0; }