多線程是多任務處理的一種特殊形式,多任務處理允許讓電腦同時運行兩個或兩個以上的程序。一般情況下,分為兩種類型的多任務處理:基於進程和基於線程。
1)基於進程的多任務處理是程序的並發執行。
2)基於線程的多任務處理是同一程序的片段的並發執行。
多線程程序包含可以同時運行的兩個或多個部分。這樣的程序中的每個部分稱為一個線程,每個線程定義了一個單獨的執行路徑。在多任務操作系統中,同時運行的多個任務可能都需要使用同一種資源。比如說,同一個文件,可能一個線程會對其進行寫操作,而另一個線程需要對這個文件進行讀操作,可想而知,如果寫線程還沒有寫結束,而此時讀線程開始了,或者讀線程還沒有讀結束而寫線程開始了,那么最終的結果顯然會是混亂的。為了保護共享資源,在線程里也有這么一把鎖——互斥鎖(mutex),互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態,即上鎖( lock )和解鎖( unlock )。
一、創建線程
在Windows下用C++創建線程需要導入windows.h頭文件,同時調用CreateThread()函數。如下:
#include <windows.h> HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
參數說明:
1)In_opt LPSECURITY_ATTRIBUTES:lpThreadAttributes, {安全設置}
指向 TSecurityAttributes 結構的指針,一般都是置為NULL,這表示沒有訪問限制。
2)In SIZE_T:dwStackSize, {堆棧大小}
分配給線程的堆棧大小,每個線程都有自己獨立的堆棧(也擁有自己的消息隊列)。它們都是進程中的內存區域,主要是存取方式不同(棧:先進后出;堆:先進先出)。
3)In LPTHREAD_START_ROUTINE:lpStartAddress, {入口函數}
線程入口函數的參數是個無類型指針,用它可以指定任何數據。
4)In_opt __drv_aliasesMem LPVOID:lpParameter, {函數參數}
函數指針,新線程建立后將立即執行該函數,函數執行完畢,系統將銷毀此線程從而結束多線程的程序。
5)In DWORD:dwCreationFlags, {啟動選項}
啟動選項)有兩個可選值:0(線程建立后立即執行入口函數);CREATE_SUSPENDED(線程建立后會掛起等待)。
6)Out_opt LPDWORD:lpThreadId {輸出線程id}
輸出線程句柄ID,注意: 1、線程的 ID 是唯一的;而句柄可能不只一個,比如可以用 GetCurrentThread 獲取一個偽句柄、可以用 DuplicateHandle 復制一個句柄等。2、ID 比句柄更輕便,在主線程中 GetCurrentThreadId、MainThreadID獲取的都是主線程的 ID。
返回值:線程句柄 ,"句柄" 類似指針,但通過指針可讀寫對象,通過句柄只是使用對象;有句柄的對象一般都是系統級別的對象(或叫內核對象)。
二、創建互斥量
互斥量和二元信號量類似,資源僅允許一個線程訪問。與二元信號量不同的是,信號量在整個系統中可以被任意線程獲取和釋放,也就是說,同一個信號量可以由一個線程獲取而由另一線程釋放。而互斥量則要求哪個線程獲取了該互斥量鎖就由哪個線程釋放,其它線程越俎代庖釋放互斥量是無效的。
在使用互斥量進行線程同步時會用到以下幾個函數:
HANDLE WINAPI CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, //線程安全相關的屬性,常置為NULL BOOL bInitialOwner, //創建Mutex時的當前線程是否擁有Mutex的所有權 LPCTSTR lpName //Mutex的名稱 );
其中,lpMutexAttributes也是表示安全的結構,與CreateThread中的lpThreadAttributes功能相同,表示決定返回的句柄是否可被子進程繼承,如果為NULL則表示返回的句柄不能被子進程繼承。bInitialOwner表示創建Mutex時的當前線程是否擁有Mutex的所有權,若為TRUE則指定為當前的創建線程為Mutex對象的所有者,其它線程訪問需要先ReleaseMutex。lpName為Mutex的名稱。
DWORD WINAPI WaitForSingleObject( HANDLE hHandle, //要獲取的鎖的句柄 DWORD dwMilliseconds //超時間隔 );
其中,WaitForSingleObject的作用是等待一個指定的對象(如Mutex對象),直到該對象處於非占用的狀態(如Mutex對象被釋放)或超出設定的時間間隔。除此之外,還有一個與它類似的函數WaitForMultipleObjects,它的作用是等待一個或所有指定的對象,直到所有的對象處於非占用的狀態,或超出設定的時間間隔。
BOOL WINAPI ReleaseMutex(HANDLE hMutex);
Handle:要等待的指定對象的句柄。dwMilliseconds:超時的間隔,以毫秒為單位;如果dwMilliseconds為非0,則等待直到dwMilliseconds時間間隔用完或對象變為非占用的狀態,如果dwMilliseconds 為INFINITE則表示無限等待,直到等待的對象處於非占用的狀態。釋放所擁有的互斥量鎖對象,hMutex為釋放的互斥量的句柄。
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr , int *restrict type); //獲取互斥鎖類型 int pthread_mutexattr_settype(const pthread_mutexattr_t *restrict attr , int type); //設置互斥鎖類型
參數type表示互斥鎖的類型,總共有以下四種類型:
1)PTHREAD_MUTEX_NOMAL:標准互斥鎖,第一次上鎖成功,第二次上鎖會失敗並阻塞;
2)PTHREAD_MUTEX_RECURSIVE:遞歸互斥鎖,第一次上鎖成功,第二次上鎖還是會成功,可以理解為內部有一個計數器,每加一次鎖計數器加1,解鎖減1;
3)PTHREAD_MUTEX_ERRORCHECK:檢查互斥鎖,第一次上鎖會成功,第二次上鎖出錯返回錯誤信息,不會阻塞;
4)PTHREAD_MUTEX_DEFAULT:默認互斥鎖,第一次上鎖會成功,第二次上鎖會失敗。
三、采用互斥鎖實現線程同步
#include <windows.h> #include <iostream> #define NAME_LINE 40 //定義線程函數傳入參數的結構體 typedef struct __THREAD_DATA { int nMaxNum; char strThreadName[NAME_LINE]; __THREAD_DATA() : nMaxNum(0) { memset(strThreadName, 0, NAME_LINE * sizeof(char)); } }THREAD_DATA; HANDLE g_hMutex = NULL; //互斥量 //線程函數 DWORD WINAPI ThreadProc(LPVOID lpParameter) { THREAD_DATA* pThreadData = (THREAD_DATA*)lpParameter; for (int i = 0; i < pThreadData->nMaxNum; ++ i) { //請求獲得一個互斥量鎖 WaitForSingleObject(g_hMutex, INFINITE); cout << pThreadData->strThreadName << " --- " << i << endl; Sleep(100); //釋放互斥量鎖 ReleaseMutex(g_hMutex); } return 0L; } int main() { //創建一個互斥量 g_hMutex = CreateMutex(NULL, FALSE, NULL); //初始化線程數據 THREAD_DATA threadData1, threadData2; threadData1.nMaxNum = 5; strcpy_s(threadData1.strThreadName, "線程1"); threadData2.nMaxNum = 10; strcpy_s(threadData2.strThreadName, "線程2"); //創建第一個子線程 HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &threadData1, 0, NULL); //創建第二個子線程 HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &threadData2, 0, NULL); //關閉線程 CloseHandle(hThread1); CloseHandle(hThread2); //主線程的執行路徑 for (int i = 0; i < 5; ++ i) { //請求獲得一個互斥量鎖 WaitForSingleObject(g_hMutex, INFINITE); cout << "主線程 === " << i << endl; Sleep(100); //釋放互斥量鎖 ReleaseMutex(g_hMutex); } system("pause"); return 0; }