多線程在編程中有至關重要的地位,咱們在實際開發時或者找工做面試時總能遇到多線程的問題,對多線程的理解程度從一個側面反映了程序員的編程水平。ios
其實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 }
運行結果:windows
能夠看到主線程(main函數)和咱們本身的線程(Fun函數)是隨機交替執行的。能夠看到Fun函數其實只運行了六次,這是由於主線程運行完以後將所佔資源都釋放掉了,使得子線程尚未運行完。看來主線程執行得有點快,讓它sleep一下吧。安全
使用函數Sleep來暫停線程的執行。多線程
1 VOID WINAPI Sleep( 2 __in DWORD dwMilliseconds 3 );
dwMilliseconds表示千分之一秒,因此 Sleep(1000); 表示暫停1秒。併發
多線程編程實例2:ide
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 }
運行結果: