線程同步有三種方式:
1. 互斥對(duì)象涉及方法:
HANDLE hMutex=CreateMutex(NULL,FALSE,NULL); //第二個(gè)參數(shù)為FALSE,將互斥對(duì)象聲明為空閑狀態(tài)
WaitForSingleObject(hMutex,INFINITE); //第二個(gè)參數(shù)為INFINITE表示一直等待,直到擁有互斥對(duì)象
ReleaseMutex(hMutex); //使用完了,將互斥對(duì)象還給操作系統(tǒng)
具體代碼及各種情況的分析見(jiàn)上一章,這里就不再敘述。
2. 事件對(duì)象:
事件對(duì)象也屬于內(nèi)核對(duì)象,包含一個(gè)使用計(jì)數(shù),一個(gè)用于指明該事件是一個(gè)自動(dòng)重置的事件還是一個(gè)人工重置的事件的布爾值,另一個(gè)用于指明該事件處于可用狀態(tài)還是不可用的布爾值。
有兩種不同類(lèi)型的事件對(duì)象。一種是人工重置的事件,另一種是自動(dòng)重置的事件。當(dāng)人工重置的事件得到通知時(shí),等待該事件的所有線程均變?yōu)榭烧{(diào)度線程。當(dāng)一個(gè)自動(dòng)重置的事件得到通知時(shí),等待該事件的線程中只有一個(gè)線程變?yōu)榭烧{(diào)度線程。所以?xún)?yōu)先選擇自動(dòng)重置的事件。
說(shuō)明:CreateEvent方法第一個(gè)參數(shù)是關(guān)于的安全的結(jié)構(gòu)體,一般設(shè)置為NULL;第二個(gè)參數(shù)表示是人工重置還是自動(dòng)重置,TRUE代表人工重置;第三個(gè)參數(shù)表示當(dāng)前狀態(tài)是可用還是不可用;第四個(gè)參數(shù)表示Event名稱(chēng),NULL的話,默認(rèn)。
HANDLE g_hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
以下代碼會(huì)出現(xiàn)異常:在將CreateEvent的第二個(gè)參數(shù)設(shè)置為人工重置的時(shí)候,因?yàn)榈却撌录乃芯€程均變?yōu)榭烧{(diào)度線程,所以發(fā)現(xiàn)售票實(shí)例程序最終會(huì)出現(xiàn)0。所以最好還是選擇自動(dòng)重置的事件。
#include <windows.h>
#include <iostream.h>
DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
int ticket=100;
HANDLE g_hEvent;
void main()
{
HANDLE handle1=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
HANDLE handle2=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
CloseHandle(handle1);
CloseHandle(handle2);
g_hEvent=CreateEvent(NULL,TRUE,TRUE,NULL);
Sleep(4000);
CloseHandle(g_hEvent);
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
while(TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE); //第二個(gè)參數(shù)為INFINITE表示一直等待,直到擁有互斥對(duì)象
if(ticket>0)
{
Sleep(1);
cout<<"thread1 sale the ticket id is:"<<ticket--<<endl;
}
else
break;
}
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
while(TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);
if(ticket>0)
{
Sleep(1);
cout<<"thread2 sale the ticket id is:"<<ticket--<<endl;
}
else
break;
}
return 0;
}
說(shuō)明:
如果一個(gè)線程循環(huán)內(nèi)部已經(jīng)調(diào)用了WaitForSingleObject(g_hEvent,INFINITE);但是在單個(gè)循環(huán)完成前沒(méi)有調(diào)用SetEvent(g_hEvent)將狀態(tài)設(shè)置成可用的話,下一次進(jìn)入循環(huán)時(shí)再次調(diào)用WaitForSingleObject時(shí)發(fā)現(xiàn)狀態(tài)不可用,所以一直等待,代碼例子將上面的代碼g_hEvent=CreateEvent(NULL,TRUE,TRUE,NULL);修改為g_hEvent=CreateEvent(NULL,FALSE,TRUE,NULL);則會(huì)出現(xiàn)這個(gè)問(wèn)題,其結(jié)果就是僅僅線程1售出了100這張票。如果在循環(huán)退出前調(diào)用SetEvent(g_hEvent);則問(wèn)題可以解決。
綜上所述,涉及到的方法:
HANDLE g_hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
WaitForSingleObject(g_hEvent,INFINITE); //等待事件,如果事件可用,運(yùn)行下面的代碼,并且將事件狀態(tài)設(shè)置為不可用狀態(tài),如果事件不可用,一直等待。
SetEvent(g_hEvent) //將事件設(shè)置為可用的狀態(tài)
ResetEvent(g_hEvent) //將事件設(shè)置為不可用狀態(tài)
一般情況下WaitForSingleObject和SetEvent配對(duì)使用。
3. 關(guān)鍵代碼段:
關(guān)鍵代碼段(臨界區(qū))是指一個(gè)小代碼段,在代碼能夠執(zhí)行前,它必須獨(dú)占對(duì)某些資源的訪問(wèn)權(quán)。
可以將關(guān)鍵代碼段想象成電話亭資源:
CRITICAL_SECTION g_cs;
InitializeCriticalSection(&
g_cs); //
創(chuàng)建電話亭資源,一般放在構(gòu)造函數(shù)中
EnterCriticalSection(&g_cs); //判斷關(guān)鍵資源所有權(quán)是否可用,可用則進(jìn)入
LeaveCriticalSection(&g_cs);使用完關(guān)鍵資源后,釋放所有權(quán)
DeleteCriticalSection(&g_cs); //銷(xiāo)毀電話亭資源,一般放在析構(gòu)函數(shù)中
其中InitializeCriticalSection
和
DeleteCriticalSection配對(duì)使用;
EnterCriticalSection和LeaveCriticalSection配對(duì)使用,中間存放訪問(wèn)共享資源的代碼。
4. 互斥對(duì)象、事件對(duì)象與關(guān)鍵代碼段的比較
互斥對(duì)象和事件對(duì)象屬于內(nèi)核對(duì)象,利用內(nèi)核對(duì)象進(jìn)行線程同步,速度較慢,但利用互斥對(duì)象和事件對(duì)象這樣的內(nèi)核對(duì)象,可以在多個(gè)進(jìn)程中的各個(gè)線程間進(jìn)行同步。
關(guān)鍵代碼段是工作在用戶方式下,同步速度較快,但在使用關(guān)鍵代碼段時(shí),很容易進(jìn)入死鎖狀態(tài),因?yàn)樵诘却M(jìn)入關(guān)鍵代碼段時(shí)無(wú)法設(shè)定超時(shí)值。
5. 死鎖:
哲學(xué)家進(jìn)餐的問(wèn)題:每個(gè)哲學(xué)家手中只有一根筷子,要進(jìn)餐必須有兩根,但誰(shuí)也不愿意先給出自己的那根給別人。大家都處于等待狀態(tài)。
線程1擁有了臨界區(qū)對(duì)象A,等待臨界區(qū)對(duì)象B的擁有權(quán),線程2擁有了臨界區(qū)對(duì)象B,等待臨界區(qū)對(duì)象A的擁有權(quán),就造成了死鎖。
死鎖代碼:
#include <windows.h>
#include <iostream.h>
DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
int ticket=100;
//創(chuàng)建兩個(gè)關(guān)鍵資源
CRITICAL_SECTION g_cs1;
CRITICAL_SECTION g_cs2;
void main()
{
HANDLE handle1=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
HANDLE handle2=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
CloseHandle(handle1);
CloseHandle(handle2);
InitializeCriticalSection(&g_cs1);
InitializeCriticalSection(&g_cs2);
Sleep(4000);
DeleteCriticalSection(&g_cs1);
DeleteCriticalSection(&g_cs2);
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs1);
Sleep(1);
EnterCriticalSection(&g_cs2);
if(ticket>0)
{
Sleep(1);
cout<<"thread1 sale the ticket id is:"<<ticket--<<endl;
}
else
break;
LeaveCriticalSection(&g_cs1);
LeaveCriticalSection(&g_cs2);
}
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs2);
Sleep(1);
EnterCriticalSection(&g_cs1);
if(ticket>0)
{
Sleep(1);
cout<<"thread2 sale the ticket id is:"<<ticket--<<endl;
}
else
break;
LeaveCriticalSection(&g_cs1);
LeaveCriticalSection(&g_cs2);
}
return 0;
}
說(shuō)明:首先線程1得到資源1的所有權(quán),然后睡眠1毫秒,線程1就讓出了執(zhí)行權(quán),這個(gè)時(shí)候線程2得到執(zhí)行權(quán),運(yùn)行,得到資源2的所有權(quán),線程2然后睡眠1毫秒,線程2就讓出了執(zhí)行權(quán),這個(gè)時(shí)候線程1得到執(zhí)行權(quán),線程1繼續(xù)執(zhí)行,想得到資源2的資源,但發(fā)現(xiàn)資源2被線程1所占用,等待。當(dāng)線程1的事件片過(guò)了以后,線程2得到執(zhí)行權(quán),繼續(xù)執(zhí)行,想得到資源1的所有權(quán),但發(fā)現(xiàn)資源1被線程1占用,所以也繼續(xù)等待,這樣線程1和線程2都互相等待,造成死鎖。
聯(lián)系客服