多線程程序的基礎流程
1、聲明線程入口函數
DWORD WINAPI ThreadProc(
LPVOID lpParameter
); //注意這裏ThreadProc這個名字是可以按自己的要求修改的;
2、在主函數中爲線程入口函數創建線程
HANDLE WINAPI CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全性
SIZE_T dwStackSize, // 初始棧的大小
LPTHREAD_START_ROUTINE lpStartAddress, // 線程入口函數的地址
LPVOID lpParameter, //線程入口函數的形參
DWORD dwCreationFlags, //線程創建後是否立即運行
LPDWORD lpThreadId
);
3、實現線程入口函數
以下就是用多線程實現的一個賣票程序
//用到了創建線程等windows的函數一次要包含相應頭文件
#include<Windows.h>
#include<iostream>
//這裏需要注意若不加使用標準命名空間的話會使得I/O函數無法識別
using namespace std;
//建立一個不含線程同步的基礎多線程買票程序
//共有100z張可供銷售的車票
int tickets=100;
int index=0;
//聲明線程入口函數
DWORD WINAPI Fun1Proc(LPVOID);
DWORD WINAPI Fun2Proc(LPVOID);
void main()
{
HANDLE hThread1; //用於保存線程的句柄
HANDLE hThread2; //用於保存線程的句柄
//創建線程
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
//關閉句柄
CloseHandle(hThread1);
CloseHandle(hThread2);
//使主線程掛起4秒,以便其他線程獲取執行權限
Sleep(4000);
}
//線程1的入口函數
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(TRUE)
{
if(tickets>0)
{
//Sleep(1);
cout<<"Thread1 sells "<<tickets--<<endl;
}
else
break;
}
return 0;
}
//線程2入口函數
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(TRUE)
{
if(tickets>0)
{
//Sleep(1);
cout<<"Thread2 sells "<<tickets--<<endl;
}
else
break;
}
return 0;
}
上面的程序雖然可以使用多線程的運行,但這裏面存在着一個潛在的隱患,就是當票數還剩一張時,由於tickets>1是由於大於0因此進入if條件語句,而若線程1執行到Sleep(1)初時他的時間片到點了,換成線程2執行。此時由於沒有執行cout語句因此tickets仍然爲1,而線程2執行後ticket是變爲0。此時線程1開始從掛起處開始執行,雖然對此事沒有票了,但由於是在掛起處執行的因此會繼續執行後面的語句造成出現存票爲0但仍然可以買票的bug。
爲了解決上述的問題,需要對線程進行同步,線程同步有四種方式:事件對象(Event)、互斥對象(Mutex)、關鍵代碼段(CriticalSection)
1、互斥對象
建立互斥對象句柄
//聲明線程入口函數
DWORD WINAPI Fun1Proc(LPVOID);
DWORD WINAPI Fun2Proc(LPVOID);
//建立互斥對象句柄
HANDLE hMutex;
在主線程中創建互斥對象
hMutex=CreateMutex(NULL,FALSE,NULL); //創建互斥對象
//創建線程
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
在線程過程函數中,等待互斥對象有信號,在有信號後執行,最後釋放互斥對象
while(TRUE)
{
//等待互斥對象變爲有信號;
WaitForSingleObject(hMutex,INFINITE);
if(tickets>0)
{
Sleep(1);
cout<<"Thread1 sells "<<tickets--<<endl;
}
else
break;
//釋放互斥對象,使互斥對象變爲有信號狀態
ReleaseMutex(hMutex);
}
2、事件對象
創建事件對象句柄
//建立事件對象句柄
HANDLE hEvent;
在主線程中創建事件對象,初始爲自動重置和有信號狀態,並在最後關閉句柄
hEvent=CreateEvent(NULL,FALSE,TRUE,NULL);//創建互斥對象
//創建線程
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
//關閉句柄
CloseHandle(hThread1);
CloseHandle(hThread2);
//使主線程掛起4秒,以便其他線程獲取執行權限
Sleep(4000);
CloseHandle(hEvent);
在線程過程函數中實現
while(TRUE)
{
//等待事件對象所有權;
WaitForSingleObject(hEvent,INFINITE);
//將事件設置爲有信號狀態
ResetEvent(hEvent);
if(tickets>0)
{
Sleep(1);
cout<<"Thread1 sells "<<tickets--<<endl;
SetEvent(hEvent);
}
else
break;
//將事件設置爲無信號狀態
SetEvent(hEvent);
}
3、關鍵代碼段
創建關鍵代碼段句柄
CRITICAL_SECTION g_cs;
在主線程中初始化關鍵代碼段
//初始化關鍵代碼段
InitializeCriticalSection(&g_cs);
在線程過程函數中進入關鍵代碼段,執行並離開關鍵代碼段,進入與離開必須成套執行,否則其他線程就無法執行
while(TRUE)
{
//進入關鍵代碼段
EnterCriticalSection(&g_cs);
if(tickets>0)
{
Sleep(1);
cout<<"Thread1 sells "<<tickets--<<endl;
//離開關鍵代碼段
LeaveCriticalSection(&g_cs);
}
else
{
//離開關鍵代碼段
LeaveCriticalSection(&g_cs);
break;
}
}