多線程與多進程(4)

原文:http://blog.csdn.net/luoweifu/article/details/46835437

做者:luoweifu 

轉載請標名出處

建立線程

在Windows平臺,Windows API提供了對多線程的支持。前面進程和線程的概念中咱們提到,一個程序至少有一個線程,這個線程稱爲主線程(main thread),若是咱們不顯示地建立線程,那咱們產的程序就是隻有主線程的間線程程序。
下面,咱們看看Windows中線程相關的操做和方法:java

CreateThread與CloseHandle

CreateThread用於建立一個線程,其函數原型以下:ios

HANDLE WINAPI CreateThread(
    LPSECURITY_ATTRIBUTES   lpThreadAttributes, //線程安全相關的屬性,常置爲NULL
    SIZE_T                  dwStackSize,        //新線程的初始化棧在大小,可設置爲0
    LPTHREAD_START_ROUTINE  lpStartAddress,     //被線程執行的回調函數,也稱爲線程函數
    LPVOID                  lpParameter,        //傳入線程函數的參數,不需傳遞參數時爲NULL
    DWORD                   dwCreationFlags,    //控制線程建立的標誌
    LPDWORD                 lpThreadId          //傳出參數,用於得到線程ID,若是爲NULL則不返回線程ID
);

  

**說明:**lpThreadAttributes:指向SECURITY_ATTRIBUTES結構的指針,決定返回的句柄是否可被子進程繼承,若是爲NULL則表示返回的句柄不能被子進程繼承。編程

dwStackSize :線程棧的初始化大小,字節單位。系統分配這個值對windows

lpStartAddress:指向一個函數指針,該函數將被線程調用執行。所以該函數也被稱爲線程函數(ThreadProc),是線程執行的起始地址,線程函數是一個回調函數,由操做系統在線程中調用。線程函數的原型以下:安全

DWORD WINAPI ThreadProc(LPVOID lpParameter);    //lpParameter是傳入的參數,是一個空指針

lpParameter:傳入線程函數(ThreadProc)的參數,不需傳遞參數時爲NULL多線程

dwCreationFlags:控制線程建立的標誌,有三個類型,0:線程建立後當即執行線程;CREATE_SUSPENDED:線程建立後進入就緒狀態,直到線程被喚醒時才調用;STACK_SIZE_PARAM_IS_A_RESERVATION:dwStackSize 參數指定線程初始化棧的大小,若是STACK_SIZE_PARAM_IS_A_RESERVATION標誌未指定,dwStackSize將會設爲系統預留的值。函數

返回值:若是線程建立成功,則返回這個新線程的句柄,不然返回NULL。若是線程建立失敗,可經過GetLastError函數得到錯誤信息。post

BOOL WINAPI CloseHandle(HANDLE hObject);        //關閉一個被打開的對象句柄

可用這個函數關閉建立的線程句柄,若是函數執行成功則返回true(非0),若是失敗則返回false(0),若是執行失敗可調用GetLastError.函數得到錯誤信息。測試

【Demo1】:建立一個最簡單的線程

 1 #include "stdafx.h"
 2 #include <windows.h>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 //線程函數
 8 DWORD WINAPI ThreadProc(LPVOID lpParameter)
 9 {
10     for (int i = 0; i < 5; ++ i)
11     {
12         cout << "子線程:i = " << i << endl;
13         Sleep(100);
14     }
15     return 0L;
16 }
17 
18 int main()
19 {
20     //建立一個線程
21     HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
22     //關閉線程
23     CloseHandle(thread);
24 
25     //主線程的執行路徑
26     for (int i = 0; i < 5; ++ i)
27     {
28         cout << "主線程:i = " << i << endl;
29         Sleep(100);
30     }
31 
32     return 0;
33 }

結果以下:
主線程:i = 0
子線程:i = 0
主線程:i = 1
子線程:i = 1
子線程:i = 2
主線程:i = 2
子線程:i = 3
主線程:i = 3
子線程:i = 4
主線程:i = 4spa

【Demo2】:在線程函數中傳入參數

 1 #include "stdafx.h"
 2 #include <windows.h>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 #define NAME_LINE   40
 8 
 9 //定義線程函數傳入參數的結構體
10 typedef struct __THREAD_DATA
11 {
12     int nMaxNum;
13     char strThreadName[NAME_LINE];
14 
15     __THREAD_DATA() : nMaxNum(0)
16     {
17         memset(strThreadName, 0, NAME_LINE * sizeof(char));
18     }
19 }THREAD_DATA;
20 
21 
22 
23 //線程函數
24 DWORD WINAPI ThreadProc(LPVOID lpParameter)
25 {
26     THREAD_DATA* pThreadData = (THREAD_DATA*)lpParameter;
27 
28     for (int i = 0; i < pThreadData->nMaxNum; ++ i)
29     {
30         cout << pThreadData->strThreadName << " --- " << i << endl;
31         Sleep(100);
32     }
33     return 0L;
34 }
35 
36 int main()
37 {
38     //初始化線程數據
39     THREAD_DATA threadData1, threadData2;
40     threadData1.nMaxNum = 5;
41     strcpy(threadData1.strThreadName, "線程1");
42     threadData2.nMaxNum = 10;
43     strcpy(threadData2.strThreadName, "線程2");
44 
45 //建立第一個子線程
46     HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &threadData1, 0, NULL);
47     //建立第二個子線程
48     HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &threadData2, 0, NULL);
49     //關閉線程
50     CloseHandle(hThread1);
51     CloseHandle(hThread2);
52 
53     //主線程的執行路徑
54     for (int i = 0; i < 5; ++ i)
55     {
56         cout << "主線程 === " << i << endl;
57         Sleep(100);
58     }
59 
60     system("pause");
61     return 0;
62 }

結果:

主線程 === 線程1 — 0
0
線程2 — 0
線程1 — 1
主線程 === 1
線程2 — 1
主線程 === 2
線程1 — 2
線程2 — 2
主線程 === 3
線程2 — 3
線程1 — 3
主線程 === 4
線程2 — 4
線程1 — 4
線程2 — 5
請按任意鍵繼續… 線程2 — 6
線程2 — 7
線程2 — 8
線程2 — 9


CreateMutex、WaitForSingleObject、ReleaseMutex

從【Demo2】中能夠看出,雖然建立的子線程都正常執行起來了,但輸出的結果並非咱們預期的效果。咱們預期的效果是每輸出一條語句後自動換行,但結果卻並不是都是這樣。這是由於在線程執行時沒有作同步處理,好比第一行的輸出,主線程輸出「主線程 ===」後時間片已用完,這時輪到子線程1輸出,在子線程輸出「線程1 —」後時間片也用完了,這時又輪到主線程執行輸出「0」,以後又輪到子線程1輸出「0」。因而就出現了「主線程 === 線程1 — 0 0」的結果。

主線程:cout << 「主線程 === 」 << i << endl;
子線程:cout << pThreadData->strThreadName << 」 — 」 << i << endl;

爲避免出現這種狀況,咱們對線程作一些簡單的同步處理,這裏咱們用互斥量(Mutex),關於互斥量(Mutex)的概念,請看《編程思想之多線程與多進程(2)——線程優先級與線程安全》一文;更多C++線程同步的處理,請看下一節。

在使用互斥量進行線程同步時會用到如下幾個函數:

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,它的做用是等待一個或全部指定的對象,直到全部的對象處於非佔用的狀態,或超出設定的時間間隔。
hHandle:要等待的指定對象的句柄。dwMilliseconds:超時的間隔,以毫秒爲單位;若是dwMilliseconds爲非0,則等待直到dwMilliseconds時間間隔用完或對象變爲非佔用的狀態,若是dwMilliseconds 爲INFINITE則表示無限等待,直到等待的對象處於非佔用的狀態。

BOOL WINAPI ReleaseMutex(HANDLE hMutex);

  

說明:釋放所擁有的互斥量鎖對象,hMutex爲釋放的互斥量的句柄。

【Demo3】:線程同步

 1 #include "stdafx.h"
 2 #include <windows.h>
 3 #include <iostream>
 4 
 5 #define NAME_LINE   40
 6 
 7 //定義線程函數傳入參數的結構體
 8 typedef struct __THREAD_DATA
 9 {
10     int nMaxNum;
11     char strThreadName[NAME_LINE];
12 
13     __THREAD_DATA() : nMaxNum(0)
14     {
15         memset(strThreadName, 0, NAME_LINE * sizeof(char));
16     }
17 }THREAD_DATA;
18 
19 HANDLE g_hMutex = NULL;     //互斥量
20 
21 //線程函數
22 DWORD WINAPI ThreadProc(LPVOID lpParameter)
23 {
24     THREAD_DATA* pThreadData = (THREAD_DATA*)lpParameter;
25 
26     for (int i = 0; i < pThreadData->nMaxNum; ++ i)
27     {
28         //請求得到一個互斥量鎖
29         WaitForSingleObject(g_hMutex, INFINITE);
30         cout << pThreadData->strThreadName << " --- " << i << endl;
31         Sleep(100);
32         //釋放互斥量鎖
33         ReleaseMutex(g_hMutex);
34     }
35     return 0L;
36 }
37 
38 int main()
39 {
40     //建立一個互斥量
41     g_hMutex = CreateMutex(NULL, FALSE, NULL);
42 
43     //初始化線程數據
44     THREAD_DATA threadData1, threadData2;
45     threadData1.nMaxNum = 5;
46     strcpy(threadData1.strThreadName, "線程1");
47     threadData2.nMaxNum = 10;
48     strcpy(threadData2.strThreadName, "線程2");
49 
50 
51     //建立第一個子線程
52     HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &threadData1, 0, NULL);
53     //建立第二個子線程
54     HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &threadData2, 0, NULL);
55     //關閉線程
56     CloseHandle(hThread1);
57     CloseHandle(hThread2);
58 
59     //主線程的執行路徑
60     for (int i = 0; i < 5; ++ i)
61     {
62         //請求得到一個互斥量鎖
63         WaitForSingleObject(g_hMutex, INFINITE);
64         cout << "主線程 === " << i << endl;
65         Sleep(100);
66         //釋放互斥量鎖
67         ReleaseMutex(g_hMutex);
68     }
69 
70     system("pause");
71     return 0;
72 } 

結果:

主線程 === 0
線程1 — 0
線程2 — 0
主線程 === 1
線程1 — 1
線程2 — 1
主線程 === 2
線程1 — 2
線程2 — 2
主線程 === 3
線程1 — 3
線程2 — 3
主線程 === 4
線程1 — 4
請按任意鍵繼續… 線程2 — 4
線程2 — 5
線程2 — 6
線程2 — 7
線程2 — 8
線程2 — 9

爲進一步理解線程同步的重要性和互斥量的使用方法,咱們再來看一個例子。

買火車票是你們春節回家最爲關注的事情,咱們就簡單模擬一下火車票的售票系統(爲使程序簡單,咱們就抽出最簡單的模型進行模擬):有500張從北京到贛州的火車票,在8個窗口同時出售,保證系統的穩定性和數據的原子性。

【Demo4】:模擬火車售票系統

SaleTickets.h :

 1 #include "stdafx.h"
 2 #include <windows.h>
 3 #include <iostream>
 4 #include <strstream> 
 5 #include <string>
 6 
 7 using namespace std;
 8 
 9 #define NAME_LINE   40
10 
11 //定義線程函數傳入參數的結構體
12 typedef struct __TICKET
13 {
14     int nCount;
15     char strTicketName[NAME_LINE];
16 
17     __TICKET() : nCount(0)
18     {
19         memset(strTicketName, 0, NAME_LINE * sizeof(char));
20     }
21 }TICKET;
22 
23 typedef struct __THD_DATA
24 {
25     TICKET* pTicket;
26     char strThreadName[NAME_LINE];
27 
28     __THD_DATA() : pTicket(NULL)
29     {
30         memset(strThreadName, 0, NAME_LINE * sizeof(char));
31     }
32 }THD_DATA;
33 
34 
35  //基本類型數據轉換成字符串
36 template<class T>
37 string convertToString(const T val)
38 {
39     string s;
40     std::strstream ss;
41     ss << val;
42     ss >> s;
43     return s;
44 }
45 
46 //售票程序
47 DWORD WINAPI SaleTicket(LPVOID lpParameter);

 

SaleTickets.cpp :

 1 #include "stdafx.h"
 2 #include <windows.h>
 3 #include <iostream>
 4 #include "SaleTickets.h"
 5 
 6 using namespace std;
 7 
 8 extern HANDLE g_hMutex;
 9 
10 //售票程序
11 DWORD WINAPI SaleTicket(LPVOID lpParameter)
12 {
13 
14     THD_DATA* pThreadData = (THD_DATA*)lpParameter;
15     TICKET* pSaleData = pThreadData->pTicket;
16     while(pSaleData->nCount > 0)
17     {
18         //請求得到一個互斥量鎖
19         WaitForSingleObject(g_hMutex, INFINITE);
20         if (pSaleData->nCount > 0)
21         {
22             cout << pThreadData->strThreadName << "出售第" << pSaleData->nCount -- << "的票,";
23             if (pSaleData->nCount >= 0) {
24                 cout << "出票成功!剩餘" << pSaleData->nCount << "張票." << endl;
25             } else {
26                 cout << "出票失敗!該票已售完。" << endl;
27             }
28         }
29         Sleep(10);
30         //釋放互斥量鎖
31         ReleaseMutex(g_hMutex);
32     }
33 
34     return 0L;
35 }

 

測試程序:

 1 //售票系統
 2 void Test2()
 3 {
 4     //建立一個互斥量
 5     g_hMutex = CreateMutex(NULL, FALSE, NULL);
 6 
 7     //初始化火車票
 8     TICKET ticket;
 9     ticket.nCount = 100;
10     strcpy(ticket.strTicketName, "北京-->贛州");
11 
12     const int THREAD_NUMM = 8;
13     THD_DATA threadSale[THREAD_NUMM];
14     HANDLE hThread[THREAD_NUMM];
15     for(int i = 0; i < THREAD_NUMM; ++ i)
16     {
17         threadSale[i].pTicket = &ticket;
18         string strThreadName = convertToString(i);
19 
20         strThreadName = "窗口" + strThreadName;
21 
22         strcpy(threadSale[i].strThreadName, strThreadName.c_str());
23 
24         //建立線程
25         hThread[i] = CreateThread(NULL, NULL, SaleTicket, &threadSale[i], 0, NULL);
26 
27         //請求得到一個互斥量鎖
28         WaitForSingleObject(g_hMutex, INFINITE);
29         cout << threadSale[i].strThreadName << "開始出售 " << threadSale[i].pTicket->strTicketName << " 的票..." << endl;
30         //釋放互斥量鎖
31         ReleaseMutex(g_hMutex);
32 
33         //關閉線程
34         CloseHandle(hThread[i]);
35     }
36 
37     system("pause");
38 }

 

結果:

窗口0開始出售 北京–>贛州 的票…
窗口0出售第100的票,出票成功!剩餘99張票.
窗口1開始出售 北京–>贛州 的票…
窗口1出售第99的票,出票成功!剩餘98張票.
窗口0出售第98的票,出票成功!剩餘97張票.
窗口2開始出售 北京–>贛州 的票…
窗口2出售第97的票,出票成功!剩餘96張票.
窗口1出售第96的票,出票成功!剩餘95張票.
窗口0出售第95的票,出票成功!剩餘94張票.
窗口3開始出售 北京–>贛州 的票…
窗口3出售第94的票,出票成功!剩餘93張票.
窗口2出售第93的票,出票成功!剩餘92張票.
窗口1出售第92的票,出票成功!剩餘91張票.
窗口0出售第91的票,出票成功!剩餘90張票.
窗口4開始出售 北京–>贛州 的票…
窗口4出售第90的票,出票成功!剩餘89張票.
窗口3出售第89的票,出票成功!剩餘88張票.
窗口2出售第88的票,出票成功!剩餘87張票.
窗口1出售第87的票,出票成功!剩餘86張票.
窗口0出售第86的票,出票成功!剩餘85張票.
窗口5開始出售 北京–>贛州 的票…
窗口5出售第85的票,出票成功!剩餘84張票.
窗口4出售第84的票,出票成功!剩餘83張票.
窗口3出售第83的票,出票成功!剩餘82張票.
窗口2出售第82的票,出票成功!剩餘81張票.



若是您有什麼疑惑和想法,請在評論處給予反饋,您的反饋就是最好的測評師!因爲本人技術和能力有限,若是本博文有錯誤或不足之處,敬請諒解並給出您寶貴的建議!

相關文章
相關標籤/搜索