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 }

 

  運行結果:

  

發佈了17 篇原創文章 · 獲贊 19 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章