原文:https://blog.csdn.net/luoweifu/article/details/46835437
作者:luoweifu
轉載請標名出處
《編程思想之多線程與多進程(1)——以操作系統的角度述說線程與進程》一文詳細講述了線程、進程的關係及在操作系統中的表現,《編程思想之多線程與多進程(2)——線程優先級與線程安全》一文講了線程安全(各種同步鎖)和優先級,這是多線程學習必須瞭解的基礎。本文將接着講一下C++中多線程程序的開發.這裏主要講Windows平臺線程的用法,創建線程要調用windows API的CreateThread方法。
創建線程
在Windows平臺,Windows API提供了對多線程的支持。前面進程和線程的概念中我們提到,一個程序至少有一個線程,這個線程稱爲主線程(main thread),如果我們不顯示地創建線程,那我們產的程序就是隻有主線程的間線程程序。
下面,我們看看Windows中線程相關的操作和方法:
CreateThread與CloseHandle
CreateThread用於創建一個線程,其函數原型如下:
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
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
**說明:**lpThreadAttributes:指向SECURITY_ATTRIBUTES結構的指針,決定返回的句柄是否可被子進程繼承,如果爲NULL則表示返回的句柄不能被子進程繼承。
dwStackSize :線程棧的初始化大小,字節單位。系統分配這個值對
lpStartAddress:指向一個函數指針,該函數將被線程調用執行。因此該函數也被稱爲線程函數(ThreadProc),是線程執行的起始地址,線程函數是一個回調函數,由操作系統在線程中調用。線程函數的原型如下:
DWORD WINAPI ThreadProc(LPVOID lpParameter); //lpParameter是傳入的參數,是一個空指針
- 1
lpParameter:傳入線程函數(ThreadProc)的參數,不需傳遞參數時爲NULL
dwCreationFlags:控制線程創建的標誌,有三個類型,0:線程創建後立即執行線程;CREATE_SUSPENDED:線程創建後進入就緒狀態,直到線程被喚醒時才調用;STACK_SIZE_PARAM_IS_A_RESERVATION:dwStackSize 參數指定線程初始化棧的大小,如果STACK_SIZE_PARAM_IS_A_RESERVATION標誌未指定,dwStackSize將會設爲系統預留的值。
返回值:如果線程創建成功,則返回這個新線程的句柄,否則返回NULL。如果線程創建失敗,可通過GetLastError函數獲得錯誤信息。
BOOL WINAPI CloseHandle(HANDLE hObject); //關閉一個被打開的對象句柄
- 1
可用這個函數關閉創建的線程句柄,如果函數執行成功則返回true(非0),如果失敗則返回false(0),如果執行失敗可調用GetLastError.函數獲得錯誤信息。
【Demo1】:創建一個最簡單的線程
#include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std;
//線程函數
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 5; ++ i)
{
cout << "子線程:i = " << i << endl;
Sleep(100);
}
return 0L;
}
int main()
{
//創建一個線程
HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
//關閉線程
CloseHandle(thread);
//主線程的執行路徑
for (int i = 0; i < 5; ++ i)
{
cout << "主線程:i = " << i << endl;
Sleep(100);
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
結果如下:
主線程:i = 0
子線程:i = 0
主線程:i = 1
子線程:i = 1
子線程:i = 2
主線程:i = 2
子線程:i = 3
主線程:i = 3
子線程:i = 4
主線程:i = 4
【Demo2】:在線程函數中傳入參數
#include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std;
#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;
//線程函數
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
THREAD_DATA* pThreadData = (THREAD_DATA*)lpParameter;
for (int i = 0; i < pThreadData->nMaxNum; ++ i)
{
cout << pThreadData->strThreadName << " --- " << i << endl;
Sleep(100);
}
return 0L;
}
int main()
{
//初始化線程數據
THREAD_DATA threadData1, threadData2;
threadData1.nMaxNum = 5;
strcpy(threadData1.strThreadName, "線程1");
threadData2.nMaxNum = 10;
strcpy(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)
{
cout << "主線程 === " << i << endl;
Sleep(100);
}
system("pause");
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 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的名稱
);
- 1
- 2
- 3
- 4
- 5
**說明:**lpMutexAttributes也是表示安全的結構,與CreateThread中的lpThreadAttributes功能相同,表示決定返回的句柄是否可被子進程繼承,如果爲NULL則表示返回的句柄不能被子進程繼承。bInitialOwner表示創建Mutex時的當前線程是否擁有Mutex的所有權,若爲TRUE則指定爲當前的創建線程爲Mutex對象的所有者,其它線程訪問需要先ReleaseMutex。lpName爲Mutex的名稱。
DWORD WINAPI WaitForSingleObject(
HANDLE hHandle, //要獲取的鎖的句柄
DWORD dwMilliseconds //超時間隔
);
- 1
- 2
- 3
- 4
**說明:**WaitForSingleObject的作用是等待一個指定的對象(如Mutex對象),直到該對象處於非佔用的狀態(如Mutex對象被釋放)或超出設定的時間間隔。除此之外,還有一個與它類似的函數WaitForMultipleObjects,它的作用是等待一個或所有指定的對象,直到所有的對象處於非佔用的狀態,或超出設定的時間間隔。
hHandle:要等待的指定對象的句柄。dwMilliseconds:超時的間隔,以毫秒爲單位;如果dwMilliseconds爲非0,則等待直到dwMilliseconds時間間隔用完或對象變爲非佔用的狀態,如果dwMilliseconds 爲INFINITE則表示無限等待,直到等待的對象處於非佔用的狀態。
BOOL WINAPI ReleaseMutex(HANDLE hMutex);
- 1
說明:釋放所擁有的互斥量鎖對象,hMutex爲釋放的互斥量的句柄。
【Demo3】:線程同步
#include "stdafx.h"
#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(threadData1.strThreadName, "線程1");
threadData2.nMaxNum = 10;
strcpy(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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 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 :
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <strstream>
#include <string>
using namespace std;
#define NAME_LINE 40
//定義線程函數傳入參數的結構體
typedef struct __TICKET
{
int nCount;
char strTicketName[NAME_LINE];
__TICKET() : nCount(0)
{
memset(strTicketName, 0, NAME_LINE * sizeof(char));
}
}TICKET;
typedef struct __THD_DATA
{
TICKET* pTicket;
char strThreadName[NAME_LINE];
__THD_DATA() : pTicket(NULL)
{
memset(strThreadName, 0, NAME_LINE * sizeof(char));
}
}THD_DATA;
//基本類型數據轉換成字符串
template<class T>
string convertToString(const T val)
{
string s;
std::strstream ss;
ss << val;
ss >> s;
return s;
}
//售票程序
DWORD WINAPI SaleTicket(LPVOID lpParameter);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
SaleTickets.cpp :
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include "SaleTickets.h"
using namespace std;
extern HANDLE g_hMutex;
//售票程序
DWORD WINAPI SaleTicket(LPVOID lpParameter)
{
THD_DATA* pThreadData = (THD_DATA*)lpParameter;
TICKET* pSaleData = pThreadData->pTicket;
while(pSaleData->nCount > 0)
{
//請求獲得一個互斥量鎖
WaitForSingleObject(g_hMutex, INFINITE);
if (pSaleData->nCount > 0)
{
cout << pThreadData->strThreadName << "出售第" << pSaleData->nCount -- << "的票,";
if (pSaleData->nCount >= 0) {
cout << "出票成功!剩餘" << pSaleData->nCount << "張票." << endl;
} else {
cout << "出票失敗!該票已售完。" << endl;
}
}
Sleep(10);
//釋放互斥量鎖
ReleaseMutex(g_hMutex);
}
return 0L;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
測試程序:
//售票系統
void Test2()
{
//創建一個互斥量
g_hMutex = CreateMutex(NULL, FALSE, NULL);
//初始化火車票
TICKET ticket;
ticket.nCount = 100;
strcpy(ticket.strTicketName, "北京-->贛州");
const int THREAD_NUMM = 8;
THD_DATA threadSale[THREAD_NUMM];
HANDLE hThread[THREAD_NUMM];
for(int i = 0; i < THREAD_NUMM; ++ i)
{
threadSale[i].pTicket = &ticket;
string strThreadName = convertToString(i);
strThreadName = "窗口" + strThreadName;
strcpy(threadSale[i].strThreadName, strThreadName.c_str());
//創建線程
hThread[i] = CreateThread(NULL, NULL, SaleTicket, &threadSale[i], 0, NULL);
//請求獲得一個互斥量鎖
WaitForSingleObject(g_hMutex, INFINITE);
cout << threadSale[i].strThreadName << "開始出售 " << threadSale[i].pTicket->strTicketName << " 的票..." << endl;
//釋放互斥量鎖
ReleaseMutex(g_hMutex);
//關閉線程
CloseHandle(hThread[i]);
}
system("pause");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 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張票.
線程和進程相關文章:
編程思想之多線程與多進程(1)——以操作系統的角度述說線程與進程