C++多線程編程--轉載
多線程在編程中有相當重要的地位,我們在實際開發時或者找工作面試時總能遇到多線程的問題,對多線程的理解程度從一個側面反映了程序員的編程水平。
其實C++語言本身並沒有提供多線程機制(當然目前C++ 11新特性中,已經可以使用std::thread來創建線程了,因為還沒有系統地了解過,所以這裏不提了。),但Windows系統為我們提供了相關API,我們可以使用他們來進行多線程編程。
創建線程的API函數
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD:線程安全相關的屬性,常置為NULL SIZE_T dwStackSize,//initialstacksize:新線程的初始化棧的大小,可設置為0 LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction:被線程執行的回調函數,也稱為線程函數 LPVOID lpParameter,//threadargument:傳入線程函數的參數,不需傳遞參數時為NULL DWORD dwCreationFlags,//creationoption:控制線程創建的標誌 LPDWORD lpThreadId//threadidentifier:傳出參數,用於獲得線程ID,如果為NULL則不返回線程ID ) /* lpThreadAttributes:指向SECURITY_ATTRIBUTES結構的指針,決定返回的句柄是否可被子進程繼承,如果為NULL則表示返回的句柄不能被子進程繼承。 dwStackSize:設置初始棧的大小,以字節為單位,如果為0,那麽默認將使用與調用該函數的線程相同的棧空間大小。 任何情況下,Windows根據需要動態延長堆棧的大小。 lpStartAddress:指向線程函數的指針,函數名稱沒有限制,但是必須以下列形式聲明: DWORD WINAPI 函數名 (LPVOID lpParam) ,格式不正確將無法調用成功。 lpParameter:向線程函數傳遞的參數,是一個指向結構的指針,不需傳遞參數時,為NULL。 dwCreationFlags:控制線程創建的標誌,可取值如下: (1)CREATE_SUSPENDED(0x00000004):創建一個掛起的線程(就緒狀態),直到線程被喚醒時才調用 (2)0:表示創建後立即激活。 (3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize參數指定初始的保留堆棧的大小, 如果STACK_SIZE_PARAM_IS_A_RESERVATION標誌未指定,dwStackSize將會設為系統預留的值 lpThreadId:保存新線程的id 返回值:函數成功,返回線程句柄,否則返回NULL。如果線程創建失敗,可通過GetLastError函數獲得錯誤信息。 */ BOOL WINAPI CloseHandle(HANDLE hObject); //關閉一個被打開的對象句柄 /*可用這個函數關閉創建的線程句柄,如果函數執行成功則返回true(非0),如果失敗則返回false(0), 如果執行失敗可調用GetLastError.函數獲得錯誤信息。 */
多線程編程實例1:
1 #include <iostream> 2 #include <windows.h> 3 using namespace std; 4 5 DWORD WINAPI Fun(LPVOID lpParamter) 6 { 7 for (int i = 0; i < 10; i++) 8 cout << "A Thread Fun Display!" << endl; 9 return 0L; 10 } 11 12 int main() 13 { 14 HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL); 15 CloseHandle(hThread); 16 for (int i = 0; i < 10; i++) 17 cout << "Main Thread Display!" << endl; 18 return 0; 19 }
運行結果:
可以看到主線程(main函數)和我們自己的線程(Fun函數)是隨機交替執行的。可以看到Fun函數其實只運行了六次,這是因為主線程運行完之後將所占資源都釋放掉了,使得子線程還沒有運行完。看來主線程執行得有點快,讓它sleep一下吧。
使用函數Sleep來暫停線程的執行。
1 VOID WINAPI Sleep( 2 __in DWORD dwMilliseconds 3 );
dwMilliseconds表示千分之一秒,所以 Sleep(1000); 表示暫停1秒。
多線程編程實例2:
1 #include <iostream> 2 #include <windows.h> 3 using namespace std; 4 5 DWORD WINAPI Fun(LPVOID lpParamter) 6 { 7 for (int i = 0; i < 10; i++) 8 { 9 cout << "A Thread Fun Display!" << endl; 10 Sleep(200); 11 } 12 13 return 0L; 14 } 15 16 int main() 17 { 18 HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL); 19 CloseHandle(hThread); 20 for (int i = 0; i < 10; i++) 21 { 22 cout << "Main Thread Display!" << endl; 23 Sleep(500); 24 } 25 26 return 0; 27 }
運行結果:
程序是每當Fun函數和main函數輸出內容後就會輸出換行,但是我們看到的確是有的時候程序輸出換行了,有的時候確沒有輸出換行,甚至有的時候是輸出兩個換行。這是怎麽回事?下面我們把程序改一下看看。
多線程編程實例3:
1 #include <iostream> 2 #include <windows.h> 3 using namespace std; 4 5 DWORD WINAPI Fun(LPVOID lpParamter) 6 { 7 for (int i = 0; i < 10; i++) 8 { 9 //cout << "A Thread Fun Display!" << endl; 10 cout << "A Thread Fun Display!\n"; 11 Sleep(200); 12 } 13 14 return 0L; 15 } 16 17 int main() 18 { 19 HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL); 20 CloseHandle(hThread); 21 for (int i = 0; i < 10; i++) 22 { 23 //cout << "Main Thread Display!" << endl; 24 cout << "Main Thread Display!\n"; 25 Sleep(500); 26 } 27 28 return 0; 29 }
運行結果:
這時候,正如我們預期的,正確地輸出了我們想要輸出的內容並且格式也是正確的。在這裏,我們可以把屏幕看成是一個資源,這個資源被兩個線程所共用,加入當Fun函數輸出了Fun Display!後,將要輸出endl(也就是清空緩沖區並換行,在這裏我們可以不用理解什麽是緩沖區),但此時,main函數卻得到了運行的機會,此時Fun函數還沒有來得及輸出換行(時間片用完),就把CPU讓給了main函數,而這時main函數就直接在Fun Display!後輸出Main Display!。
另一種情況就是“輸出兩個換行”,這種情況就是比如輸出Main Display!並輸出endl後,時間片用完,輪到子線程占用CPU,子進程上一次時間片用完時停在了Fun Display!,下一次時間片過來時,剛好開始輸出endl,此時就會“輸出兩個換行”。
那麽為什麽我們把實例2改成實例3就可以正確的運行呢?原因在於,多個線程雖然是並發運行的,但是有一些操作(比如輸出一整段內容)是必須一氣呵成的,不允許打斷的,所以我們看到實例2和實例3的運行結果是不一樣的。它們之間的差異就是少了endl,而多了一個換行符\n。
那麽,是不是實例2的代碼我們就不可以讓它正確的運行呢?答案當然是否定的,下面我就來講一下怎樣才能讓實例2的代碼可以正確運行。這涉及到多線程的同步問題。對於一個資源被多個線程共用會導致程序的混亂,我們的解決方法是只允許一個線程擁有對共享資源的獨占,這裏我們用互斥量(Mutex)來進行線程同步。
在使用互斥量進行線程同步時,會用到以下幾個函數:
HANDLE WINAPI CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, //線程安全相關的屬性,常置為NULL BOOL bInitialOwner, //創建Mutex時的當前線程是否擁有Mutex的所有權 LPCTSTR lpName //Mutex的名稱 ); /* MutexAttributes:也是表示安全的結構,與CreateThread中的lpThreadAttributes功能相同,表示決定返回的句柄是否可被子進程繼承,如果為NULL則表示返回的句柄不能被子進程繼承。 bInitialOwner:表示創建Mutex時的當前線程是否擁有Mutex的所有權,若為TRUE則指定為當前的創建線程為Mutex對象的所有者,其它線程訪問需要先ReleaseMutex lpName:Mutex的名稱 */
DWORD WINAPI WaitForSingleObject( HANDLE hHandle, //要獲取的鎖的句柄 DWORD dwMilliseconds //超時間隔 ); /* WaitForSingleObject:等待一個指定的對象(如Mutex對象),直到該對象處於非占用的狀態(如Mutex對象被釋放)或超出設定的時間間隔。除此之外,還有一個與它類似的函數WaitForMultipleObjects,它的作用是等待一個或所有指定的對象,直到所有的對象處於非占用的狀態,或超出設定的時間間隔。 hHandle:要等待的指定對象的句柄。 dwMilliseconds:超時的間隔,以毫秒為單位;如果dwMilliseconds為非0,則等待直到dwMilliseconds時間間隔用完或對象變為非占用的狀態,如果dwMilliseconds 為INFINITE則表示無限等待,直到等待的對象處於非占用的狀態。 */
BOOL WINAPI ReleaseMutex(HANDLE hMutex); //說明:釋放所擁有的互斥量鎖對象,hMutex為釋放的互斥量句柄
多線程實例4:
1 #include <iostream> 2 #include <windows.h> 3 using namespace std; 4 5 HANDLE hMutex = NULL;//互斥量 6 //線程函數 7 DWORD WINAPI Fun(LPVOID lpParamter) 8 { 9 for (int i = 0; i < 10; i++) 10 { 11 //請求一個互斥量鎖 12 WaitForSingleObject(hMutex, INFINITE); 13 cout << "A Thread Fun Display!" << endl; 14 Sleep(100); 15 //釋放互斥量鎖 16 ReleaseMutex(hMutex); 17 } 18 return 0L;//表示返回的是long型的0 19 20 } 21 22 int main() 23 { 24 //創建一個子線程 25 HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL); 26 hMutex = CreateMutex(NULL, FALSE,"screen"); 27 //關閉線程 28 CloseHandle(hThread); 29 //主線程的執行路徑 30 for (int i = 0; i < 10; i++) 31 { 32 //請求獲得一個互斥量鎖 33 WaitForSingleObject(hMutex,INFINITE); 34 cout << "Main Thread Display!" << endl; 35 Sleep(100); 36 //釋放互斥量鎖 37 ReleaseMutex(hMutex); 38 } 39 return 0; 40 }
運行結果:
C++多線程編程--轉載