青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

每天早晨叫醒你的不是鬧鐘,而是夢想

  C++博客 :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
  62 Posts :: 0 Stories :: 5 Comments :: 0 Trackbacks

常用鏈接

留言簿(1)

我參與的團(tuán)隊(duì)

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

摘要: 多線程同步技術(shù)是計(jì)算機(jī)軟件開發(fā)的重要技術(shù),本文對多線程的各種同步技術(shù)的原理和實(shí)現(xiàn)進(jìn)行了初步探討。

  關(guān)鍵詞: VC++6.0; 線程同步;臨界區(qū);事件;互斥;信號量;

  閱讀目錄:

  使線程同步
  臨界區(qū)
  管理事件內(nèi)核對象
  信號量內(nèi)核對象
  互斥內(nèi)核對象
  小結(jié)

  正文

  使線程同步

  在程序中使用多線程時(shí),一般很少有多個(gè)線程能在其生命期內(nèi)進(jìn)行完全獨(dú)立的操作。更多的情況是一些線程進(jìn)行某些處理操作,而其他的線程必須對其處理結(jié)果進(jìn)行了解。正常情況下對這種處理結(jié)果的了解應(yīng)當(dāng)在其處理任務(wù)完成后進(jìn)行。

  如果不采取適當(dāng)?shù)拇胧渌€程往往會在線程處理任務(wù)結(jié)束前就去訪問處理結(jié)果,這就很有可能得到有關(guān)處理結(jié)果的錯(cuò)誤了解。例如,多個(gè)線程同時(shí)訪問同一個(gè)全局變量,如果都是讀取操作,則不會出現(xiàn)問題。如果一個(gè)線程負(fù)責(zé)改變此變量的值,而其他線程負(fù)責(zé)同時(shí)讀取變量內(nèi)容,則不能保證讀取到的數(shù)據(jù)是經(jīng)過寫線程修改后的。

  為了確保讀線程讀取到的是經(jīng)過修改的變量,就必須在向變量寫入數(shù)據(jù)時(shí)禁止其他線程對其的任何訪問,直至賦值過程結(jié)束后再解除對其他線程的訪問限制。象這種保證線程能了解其他線程任務(wù)處理結(jié)束后的處理結(jié)果而采取的保護(hù)措施即為線程同步。

  線程同步是一個(gè)非常大的話題,包括方方面面的內(nèi)容。從大的方面講,線程的同步可分用戶模式的線程同步和內(nèi)核對象的線程同步兩大類。用戶模式中線程的同步方法主要有原子訪問和臨界區(qū)等方法。其特點(diǎn)是同步速度特別快,適合于對線程運(yùn)行速度有嚴(yán)格要求的場合。

  內(nèi)核對象的線程同步則主要由事件、等待定時(shí)器、信號量以及信號燈等內(nèi)核對象構(gòu)成。由于這種同步機(jī)制使用了內(nèi)核對象,使用時(shí)必須將線程從用戶模式切換到內(nèi)核模式,而這種轉(zhuǎn)換一般要耗費(fèi)近千個(gè)CPU周期,因此同步速度較慢,但在適用性上卻要遠(yuǎn)優(yōu)于用戶模式的線程同步方式。
  臨界區(qū)

  臨界區(qū)(Critical Section)是一段獨(dú)占對某些共享資源訪問的代碼,在任意時(shí)刻只允許一個(gè)線程對共享資源進(jìn)行訪問。如果有多個(gè)線程試圖同時(shí)訪問臨界區(qū),那么在有一個(gè)線程進(jìn)入后其他所有試圖訪問此臨界區(qū)的線程將被掛起,并一直持續(xù)到進(jìn)入臨界區(qū)的線程離開。臨界區(qū)在被釋放后,其他線程可以繼續(xù)搶占,并以此達(dá)到用原子方式操作共享資源的目的。

  臨界區(qū)在使用時(shí)以CRITICAL_SECTION結(jié)構(gòu)對象保護(hù)共享資源,并分別用EnterCriticalSection()和LeaveCriticalSection()函數(shù)去標(biāo)識和釋放一個(gè)臨界區(qū)。所用到的CRITICAL_SECTION結(jié)構(gòu)對象必須經(jīng)過InitializeCriticalSection()的初始化后才能使用,而且必須確保所有線程中的任何試圖訪問此共享資源的代碼都處在此臨界區(qū)的保護(hù)之下。否則臨界區(qū)將不會起到應(yīng)有的作用,共享資源依然有被破壞的可能。

圖1 使用臨界區(qū)保持線程同步
  下面通過一段代碼展示了臨界區(qū)在保護(hù)多線程訪問的共享資源中的作用。通過兩個(gè)線程來分別對全局變量g_cArray[10]進(jìn)行寫入操作,用臨界區(qū)結(jié)構(gòu)對象g_cs來保持線程的同步,并在開啟線程前對其進(jìn)行初始化。為了使實(shí)驗(yàn)效果更加明顯,體現(xiàn)出臨界區(qū)的作用,在線程函數(shù)對共享資源g_cArray[10]的寫入時(shí),以Sleep()函數(shù)延遲1毫秒,使其他線程同其搶占CPU的可能性增大。如果不使用臨界區(qū)對其進(jìn)行保護(hù),則共享資源數(shù)據(jù)將被破壞(參見圖1(a)所示計(jì)算結(jié)果),而使用臨界區(qū)對線程保持同步后則可以得到正確的結(jié)果(參見圖1(b)所示計(jì)算結(jié)果)。代碼實(shí)現(xiàn)清單附下:

// 臨界區(qū)結(jié)構(gòu)對象
CRITICAL_SECTION g_cs;
// 共享資源 
char g_cArray[10];
UINT ThreadProc10(LPVOID pParam)
{
 
// 進(jìn)入臨界區(qū)
 EnterCriticalSection(&g_cs);
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[i] 
= 'a';
  Sleep(
1);
 }

 
// 離開臨界區(qū)
 LeaveCriticalSection(&g_cs);
 
return 0;
}

UINT ThreadProc11(LPVOID pParam)
{
 
// 進(jìn)入臨界區(qū)
 EnterCriticalSection(&g_cs);
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[
10 - i - 1= 'b';
  Sleep(
1);
 }

 
// 離開臨界區(qū)
 LeaveCriticalSection(&g_cs);
 
return 0;
}

……
void CSample08View::OnCriticalSection() 
{
 
// 初始化臨界區(qū)
 InitializeCriticalSection(&g_cs);
 
// 啟動線程
 AfxBeginThread(ThreadProc10, NULL);
 AfxBeginThread(ThreadProc11, NULL);
 
// 等待計(jì)算完畢
 Sleep(300);
 
// 報(bào)告計(jì)算結(jié)果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}


  在使用臨界區(qū)時(shí),一般不允許其運(yùn)行時(shí)間過長,只要進(jìn)入臨界區(qū)的線程還沒有離開,其他所有試圖進(jìn)入此臨界區(qū)的線程都會被掛起而進(jìn)入到等待狀態(tài),并會在一定程度上影響。程序的運(yùn)行性能。尤其需要注意的是不要將等待用戶輸入或是其他一些外界干預(yù)的操作包含到臨界區(qū)。如果進(jìn)入了臨界區(qū)卻一直沒有釋放,同樣也會引起其他線程的長時(shí)間等待。換句話說,在執(zhí)行了EnterCriticalSection()語句進(jìn)入臨界區(qū)后無論發(fā)生什么,必須確保與之匹配的LeaveCriticalSection()都能夠被執(zhí)行到。可以通過添加結(jié)構(gòu)化異常處理代碼來確保LeaveCriticalSection()語句的執(zhí)行。雖然臨界區(qū)同步速度很快,但卻只能用來同步本進(jìn)程內(nèi)的線程,而不可用來同步多個(gè)進(jìn)程中的線程。

  MFC為臨界區(qū)提供有一個(gè)CCriticalSection類,使用該類進(jìn)行線程同步處理是非常簡單的,只需在線程函數(shù)中用CCriticalSection類成員函數(shù)Lock()和UnLock()標(biāo)定出被保護(hù)代碼片段即可。對于上述代碼,可通過CCriticalSection類將其改寫如下:

// MFC臨界區(qū)類對象
CCriticalSection g_clsCriticalSection;
// 共享資源 
char g_cArray[10];
UINT ThreadProc20(LPVOID pParam)
{
 
// 進(jìn)入臨界區(qū)
 g_clsCriticalSection.Lock();
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[i] 
= 'a';
  Sleep(
1);
 }

 
// 離開臨界區(qū)
 g_clsCriticalSection.Unlock();
 
return 0;
}

UINT ThreadProc21(LPVOID pParam)
{
 
// 進(jìn)入臨界區(qū)
 g_clsCriticalSection.Lock();
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[
10 - i - 1= 'b';
  Sleep(
1);
 }

 
// 離開臨界區(qū)
 g_clsCriticalSection.Unlock();
 
return 0;
}

……
void CSample08View::OnCriticalSectionMfc() 
{
 
// 啟動線程
 AfxBeginThread(ThreadProc20, NULL);
 AfxBeginThread(ThreadProc21, NULL);
 
// 等待計(jì)算完畢
 Sleep(300);
 
// 報(bào)告計(jì)算結(jié)果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}


  管理事件內(nèi)核對象

  在前面講述線程通信時(shí)曾使用過事件內(nèi)核對象來進(jìn)行線程間的通信,除此之外,事件內(nèi)核對象也可以通過通知操作的方式來保持線程的同步。對于前面那段使用臨界區(qū)保持線程同步的代碼可用事件對象的線程同步方法改寫如下:

// 事件句柄
HANDLE hEvent = NULL;
// 共享資源 
char g_cArray[10];
……
UINT ThreadProc12(LPVOID pParam)
{
 
// 等待事件置位
 WaitForSingleObject(hEvent, INFINITE);
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[i] 
= 'a';
  Sleep(
1);
 }

 
// 處理完成后即將事件對象置位
 SetEvent(hEvent);
 
return 0;
}

UINT ThreadProc13(LPVOID pParam)
{
 
// 等待事件置位
 WaitForSingleObject(hEvent, INFINITE);
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[
10 - i - 1= 'b';
  Sleep(
1);
 }

 
// 處理完成后即將事件對象置位
 SetEvent(hEvent);
 
return 0;
}

……
void CSample08View::OnEvent() 
{
 
// 創(chuàng)建事件
 hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 
// 事件置位
 SetEvent(hEvent);
 
// 啟動線程
 AfxBeginThread(ThreadProc12, NULL);
 AfxBeginThread(ThreadProc13, NULL);
 
// 等待計(jì)算完畢
 Sleep(300);
 
// 報(bào)告計(jì)算結(jié)果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}


  在創(chuàng)建線程前,首先創(chuàng)建一個(gè)可以自動復(fù)位的事件內(nèi)核對象hEvent,而線程函數(shù)則通過WaitForSingleObject()等待函數(shù)無限等待hEvent的置位,只有在事件置位時(shí)WaitForSingleObject()才會返回,被保護(hù)的代碼將得以執(zhí)行。對于以自動復(fù)位方式創(chuàng)建的事件對象,在其置位后一被WaitForSingleObject()等待到就會立即復(fù)位,也就是說在執(zhí)行ThreadProc12()中的受保護(hù)代碼時(shí),事件對象已經(jīng)是復(fù)位狀態(tài)的,這時(shí)即使有ThreadProc13()對CPU的搶占,也會由于WaitForSingleObject()沒有hEvent的置位而不能繼續(xù)執(zhí)行,也就沒有可能破壞受保護(hù)的共享資源。在ThreadProc12()中的處理完成后可以通過SetEvent()對hEvent的置位而允許ThreadProc13()對共享資源g_cArray的處理。這里SetEvent()所起的作用可以看作是對某項(xiàng)特定任務(wù)完成的通知。

  使用臨界區(qū)只能同步同一進(jìn)程中的線程,而使用事件內(nèi)核對象則可以對進(jìn)程外的線程進(jìn)行同步,其前提是得到對此事件對象的訪問權(quán)。可以通過OpenEvent()函數(shù)獲取得到,其函數(shù)原型為:

HANDLE OpenEvent(
 DWORD dwDesiredAccess, 
// 訪問標(biāo)志
 BOOL bInheritHandle, // 繼承標(biāo)志
 LPCTSTR lpName // 指向事件對象名的指針
);


  如果事件對象已創(chuàng)建(在創(chuàng)建事件時(shí)需要指定事件名),函數(shù)將返回指定事件的句柄。對于那些在創(chuàng)建事件時(shí)沒有指定事件名的事件內(nèi)核對象,可以通過使用內(nèi)核對象的繼承性或是調(diào)用DuplicateHandle()函數(shù)來調(diào)用CreateEvent()以獲得對指定事件對象的訪問權(quán)。在獲取到訪問權(quán)后所進(jìn)行的同步操作與在同一個(gè)進(jìn)程中所進(jìn)行的線程同步操作是一樣的。

  如果需要在一個(gè)線程中等待多個(gè)事件,則用WaitForMultipleObjects()來等待。WaitForMultipleObjects()與WaitForSingleObject()類似,同時(shí)監(jiān)視位于句柄數(shù)組中的所有句柄。這些被監(jiān)視對象的句柄享有平等的優(yōu)先權(quán),任何一個(gè)句柄都不可能比其他句柄具有更高的優(yōu)先權(quán)。WaitForMultipleObjects()的函數(shù)原型為:

DWORD WaitForMultipleObjects(
 DWORD nCount, 
// 等待句柄數(shù)
 CONST HANDLE *lpHandles, // 句柄數(shù)組首地址
 BOOL fWaitAll, // 等待標(biāo)志
 DWORD dwMilliseconds // 等待時(shí)間間隔
);


  參數(shù)nCount指定了要等待的內(nèi)核對象的數(shù)目,存放這些內(nèi)核對象的數(shù)組由lpHandles來指向。fWaitAll對指定的這nCount個(gè)內(nèi)核對象的兩種等待方式進(jìn)行了指定,為TRUE時(shí)當(dāng)所有對象都被通知時(shí)函數(shù)才會返回,為FALSE則只要其中任何一個(gè)得到通知就可以返回。dwMilliseconds在這里的作用與在WaitForSingleObject()中的作用是完全一致的。如果等待超時(shí),函數(shù)將返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某個(gè)值,則說明所有指定對象的狀態(tài)均為已通知狀態(tài)(當(dāng)fWaitAll為TRUE時(shí))或是用以減去WAIT_OBJECT_0而得到發(fā)生通知的對象的索引(當(dāng)fWaitAll為FALSE時(shí))。如果返回值在WAIT_ABANDONED_0與WAIT_ABANDONED_0+nCount-1之間,則表示所有指定對象的狀態(tài)均為已通知,且其中至少有一個(gè)對象是被丟棄的互斥對象(當(dāng)fWaitAll為TRUE時(shí)),或是用以減去WAIT_OBJECT_0表示一個(gè)等待正常結(jié)束的互斥對象的索引(當(dāng)fWaitAll為FALSE時(shí))。 下面給出的代碼主要展示了對WaitForMultipleObjects()函數(shù)的使用。通過對兩個(gè)事件內(nèi)核對象的等待來控制線程任務(wù)的執(zhí)行與中途退出:

// 存放事件句柄的數(shù)組
HANDLE hEvents[2];
UINT ThreadProc14(LPVOID pParam)

 
// 等待開啟事件
 DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
 
// 如果開啟事件到達(dá)則線程開始執(zhí)行任務(wù)
 if (dwRet1 == WAIT_OBJECT_0)
 
{
  AfxMessageBox(
"線程開始工作!");
  
while (true)
  
{
   
for (int i = 0; i < 10000; i++);
   
// 在任務(wù)處理過程中等待結(jié)束事件 
   DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);
   
// 如果結(jié)束事件置位則立即終止任務(wù)的執(zhí)行
   if (dwRet2 == WAIT_OBJECT_0 + 1)
    
break;
  }

 }

 AfxMessageBox(
"線程退出!");
 
return 0;
}

……
void CSample08View::OnStartEvent() 
{
 
// 創(chuàng)建線程
 for (int i = 0; i < 2; i++)
  hEvents[i] 
= CreateEvent(NULL, FALSE, FALSE, NULL);
  
// 開啟線程
  AfxBeginThread(ThreadProc14, NULL);
  
// 設(shè)置事件0(開啟事件)
  SetEvent(hEvents[0]);
}

void CSample08View::OnEndevent() 
{
 
// 設(shè)置事件1(結(jié)束事件)
 SetEvent(hEvents[1]);
}


  MFC為事件相關(guān)處理也提供了一個(gè)CEvent類,共包含有除構(gòu)造函數(shù)外的4個(gè)成員函數(shù)PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分別相當(dāng)與Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函數(shù)。而構(gòu)造函數(shù)則履行了原CreateEvent()函數(shù)創(chuàng)建事件對象的職責(zé),其函數(shù)原型為:

CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );


  按照此缺省設(shè)置將創(chuàng)建一個(gè)自動復(fù)位、初始狀態(tài)為復(fù)位狀態(tài)的沒有名字的事件對象。封裝后的CEvent類使用起來更加方便,圖2即展示了CEvent類對A、B兩線程的同步過程:


圖2 CEvent類對線程的同步過程示意

  B線程在執(zhí)行到CEvent類成員函數(shù)Lock()時(shí)將會發(fā)生阻塞,而A線程此時(shí)則可以在沒有B線程干擾的情況下對共享資源進(jìn)行處理,并在處理完成后通過成員函數(shù)SetEvent()向B發(fā)出事件,使其被釋放,得以對A先前已處理完畢的共享資源進(jìn)行操作。可見,使用CEvent類對線程的同步方法與通過API函數(shù)進(jìn)行線程同步的處理方法是基本一致的。前面的API處理代碼可用CEvent類將其改寫為:

// MFC事件類對象
CEvent g_clsEvent;
UINT ThreadProc22(LPVOID pParam)
{
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[i] 
= 'a';
  Sleep(
1);
 }

 
// 事件置位
 g_clsEvent.SetEvent();
 
return 0;
}

UINT ThreadProc23(LPVOID pParam)
{
 
// 等待事件
 g_clsEvent.Lock();
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[
10 - i - 1= 'b';
  Sleep(
1);
 }

 
return 0;
}

……
void CSample08View::OnEventMfc() 
{
 
// 啟動線程
 AfxBeginThread(ThreadProc22, NULL);
 AfxBeginThread(ThreadProc23, NULL);
 
// 等待計(jì)算完畢
 Sleep(300);
 
// 報(bào)告計(jì)算結(jié)果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}


  信號量內(nèi)核對象

  信號量(Semaphore)內(nèi)核對象對線程的同步方式與前面幾種方法不同,它允許多個(gè)線程在同一時(shí)刻訪問同一資源,但是需要限制在同一時(shí)刻訪問此資源的最大線程數(shù)目。在用CreateSemaphore()創(chuàng)建信號量時(shí)即要同時(shí)指出允許的最大資源計(jì)數(shù)和當(dāng)前可用資源計(jì)數(shù)。一般是將當(dāng)前可用資源計(jì)數(shù)設(shè)置為最大資源計(jì)數(shù),每增加一個(gè)線程對共享資源的訪問,當(dāng)前可用資源計(jì)數(shù)就會減1,只要當(dāng)前可用資源計(jì)數(shù)是大于0的,就可以發(fā)出信號量信號。但是當(dāng)前可用計(jì)數(shù)減小到0時(shí)則說明當(dāng)前占用資源的線程數(shù)已經(jīng)達(dá)到了所允許的最大數(shù)目,不能在允許其他線程的進(jìn)入,此時(shí)的信號量信號將無法發(fā)出。線程在處理完共享資源后,應(yīng)在離開的同時(shí)通過ReleaseSemaphore()函數(shù)將當(dāng)前可用資源計(jì)數(shù)加1。在任何時(shí)候當(dāng)前可用資源計(jì)數(shù)決不可能大于最大資源計(jì)數(shù)。


圖3 使用信號量對象控制資源

  下面結(jié)合圖例3來演示信號量對象對資源的控制。在圖3中,以箭頭和白色箭頭表示共享資源所允許的最大資源計(jì)數(shù)和當(dāng)前可用資源計(jì)數(shù)。初始如圖(a)所示,最大資源計(jì)數(shù)和當(dāng)前可用資源計(jì)數(shù)均為4,此后每增加一個(gè)對資源進(jìn)行訪問的線程(用黑色箭頭表示)當(dāng)前資源計(jì)數(shù)就會相應(yīng)減1,圖(b)即表示的在3個(gè)線程對共享資源進(jìn)行訪問時(shí)的狀態(tài)。當(dāng)進(jìn)入線程數(shù)達(dá)到4個(gè)時(shí),將如圖(c)所示,此時(shí)已達(dá)到最大資源計(jì)數(shù),而當(dāng)前可用資源計(jì)數(shù)也已減到0,其他線程無法對共享資源進(jìn)行訪問。在當(dāng)前占有資源的線程處理完畢而退出后,將會釋放出空間,圖(d)已有兩個(gè)線程退出對資源的占有,當(dāng)前可用計(jì)數(shù)為2,可以再允許2個(gè)線程進(jìn)入到對資源的處理。可以看出,信號量是通過計(jì)數(shù)來對線程訪問資源進(jìn)行控制的,而實(shí)際上信號量確實(shí)也被稱作Dijkstra計(jì)數(shù)器。

  使用信號量內(nèi)核對象進(jìn)行線程同步主要會用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函數(shù)。其中,CreateSemaphore()用來創(chuàng)建一個(gè)信號量內(nèi)核對象,其函數(shù)原型為:

HANDLE CreateSemaphore(
 LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, 
// 安全屬性指針
 LONG lInitialCount, // 初始計(jì)數(shù)
 LONG lMaximumCount, // 最大計(jì)數(shù)
 LPCTSTR lpName // 對象名指針
);


  參數(shù)lMaximumCount是一個(gè)有符號32位值,定義了允許的最大資源計(jì)數(shù),最大取值不能超過4294967295。lpName參數(shù)可以為創(chuàng)建的信號量定義一個(gè)名字,由于其創(chuàng)建的是一個(gè)內(nèi)核對象,因此在其他進(jìn)程中可以通過該名字而得到此信號量。OpenSemaphore()函數(shù)即可用來根據(jù)信號量名打開在其他進(jìn)程中創(chuàng)建的信號量,函數(shù)原型如下:

HANDLE OpenSemaphore(
 DWORD dwDesiredAccess, 
// 訪問標(biāo)志
 BOOL bInheritHandle, // 繼承標(biāo)志
 LPCTSTR lpName // 信號量名
);


  在線程離開對共享資源的處理時(shí),必須通過ReleaseSemaphore()來增加當(dāng)前可用資源計(jì)數(shù)。否則將會出現(xiàn)當(dāng)前正在處理共享資源的實(shí)際線程數(shù)并沒有達(dá)到要限制的數(shù)值,而其他線程卻因?yàn)楫?dāng)前可用資源計(jì)數(shù)為0而仍無法進(jìn)入的情況。ReleaseSemaphore()的函數(shù)原型為:

BOOL ReleaseSemaphore(
 HANDLE hSemaphore, 
// 信號量句柄
 LONG lReleaseCount, // 計(jì)數(shù)遞增數(shù)量
 LPLONG lpPreviousCount // 先前計(jì)數(shù)
);


  該函數(shù)將lReleaseCount中的值添加給信號量的當(dāng)前資源計(jì)數(shù),一般將lReleaseCount設(shè)置為1,如果需要也可以設(shè)置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在試圖進(jìn)入共享資源的線程函數(shù)入口處,主要用來判斷信號量的當(dāng)前可用資源計(jì)數(shù)是否允許本線程的進(jìn)入。只有在當(dāng)前可用資源計(jì)數(shù)值大于0時(shí),被監(jiān)視的信號量內(nèi)核對象才會得到通知。

  信號量的使用特點(diǎn)使其更適用于對Socket(套接字)程序中線程的同步。例如,網(wǎng)絡(luò)上的HTTP服務(wù)器要對同一時(shí)間內(nèi)訪問同一頁面的用戶數(shù)加以限制,這時(shí)可以為沒一個(gè)用戶對服務(wù)器的頁面請求設(shè)置一個(gè)線程,而頁面則是待保護(hù)的共享資源,通過使用信號量對線程的同步作用可以確保在任一時(shí)刻無論有多少用戶對某一頁面進(jìn)行訪問,只有不大于設(shè)定的最大用戶數(shù)目的線程能夠進(jìn)行訪問,而其他的訪問企圖則被掛起,只有在有用戶退出對此頁面的訪問后才有可能進(jìn)入。下面給出的示例代碼即展示了類似的處理過程:

// 信號量對象句柄
HANDLE hSemaphore;
UINT ThreadProc15(LPVOID pParam)

 
// 試圖進(jìn)入信號量關(guān)口
 WaitForSingleObject(hSemaphore, INFINITE);
 
// 線程任務(wù)處理
 AfxMessageBox("線程一正在執(zhí)行!");
 
// 釋放信號量計(jì)數(shù)
 ReleaseSemaphore(hSemaphore, 1, NULL);
 
return 0;
}

UINT ThreadProc16(LPVOID pParam)

 
// 試圖進(jìn)入信號量關(guān)口
 WaitForSingleObject(hSemaphore, INFINITE);
 
// 線程任務(wù)處理
 AfxMessageBox("線程二正在執(zhí)行!");
 
// 釋放信號量計(jì)數(shù)
 ReleaseSemaphore(hSemaphore, 1, NULL);
 
return 0;
}

UINT ThreadProc17(LPVOID pParam)

 
// 試圖進(jìn)入信號量關(guān)口
 WaitForSingleObject(hSemaphore, INFINITE);
 
// 線程任務(wù)處理
 AfxMessageBox("線程三正在執(zhí)行!");
 
// 釋放信號量計(jì)數(shù)
 ReleaseSemaphore(hSemaphore, 1, NULL);
 
return 0;
}

……
void CSample08View::OnSemaphore() 
{
 
// 創(chuàng)建信號量對象
 hSemaphore = CreateSemaphore(NULL, 22, NULL);
 
// 開啟線程
 AfxBeginThread(ThreadProc15, NULL);
 AfxBeginThread(ThreadProc16, NULL);
 AfxBeginThread(ThreadProc17, NULL);
}



圖4 開始進(jìn)入的兩個(gè)線程


圖5 線程二退出后線程三才得以進(jìn)入

  上述代碼在開啟線程前首先創(chuàng)建了一個(gè)初始計(jì)數(shù)和最大資源計(jì)數(shù)均為2的信號量對象hSemaphore。即在同一時(shí)刻只允許2個(gè)線程進(jìn)入由hSemaphore保護(hù)的共享資源。隨后開啟的三個(gè)線程均試圖訪問此共享資源,在前兩個(gè)線程試圖訪問共享資源時(shí),由于hSemaphore的當(dāng)前可用資源計(jì)數(shù)分別為2和1,此時(shí)的hSemaphore是可以得到通知的,也就是說位于線程入口處的WaitForSingleObject()將立即返回,而在前兩個(gè)線程進(jìn)入到保護(hù)區(qū)域后,hSemaphore的當(dāng)前資源計(jì)數(shù)減少到0,hSemaphore將不再得到通知,WaitForSingleObject()將線程掛起。直到此前進(jìn)入到保護(hù)區(qū)的線程退出后才能得以進(jìn)入。圖4和圖5為上述代脈的運(yùn)行結(jié)果。從實(shí)驗(yàn)結(jié)果可以看出,信號量始終保持了同一時(shí)刻不超過2個(gè)線程的進(jìn)入。

  在MFC中,通過CSemaphore類對信號量作了表述。該類只具有一個(gè)構(gòu)造函數(shù),可以構(gòu)造一個(gè)信號量對象,并對初始資源計(jì)數(shù)、最大資源計(jì)數(shù)、對象名和安全屬性等進(jìn)行初始化,其原型如下:

CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );


  在構(gòu)造了CSemaphore類對象后,任何一個(gè)訪問受保護(hù)共享資源的線程都必須通過CSemaphore從父類CSyncObject類繼承得到的Lock()和UnLock()成員函數(shù)來訪問或釋放CSemaphore對象。與前面介紹的幾種通過MFC類保持線程同步的方法類似,通過CSemaphore類也可以將前面的線程同步代碼進(jìn)行改寫,這兩種使用信號量的線程同步方法無論是在實(shí)現(xiàn)原理上還是從實(shí)現(xiàn)結(jié)果上都是完全一致的。下面給出經(jīng)MFC改寫后的信號量線程同步代碼:

// MFC信號量類對象
CSemaphore g_clsSemaphore(22);
UINT ThreadProc24(LPVOID pParam)

 
// 試圖進(jìn)入信號量關(guān)口
 g_clsSemaphore.Lock();
 
// 線程任務(wù)處理
 AfxMessageBox("線程一正在執(zhí)行!");
 
// 釋放信號量計(jì)數(shù)
 g_clsSemaphore.Unlock();
 
return 0;
}

UINT ThreadProc25(LPVOID pParam)
{
 
// 試圖進(jìn)入信號量關(guān)口
 g_clsSemaphore.Lock();
 
// 線程任務(wù)處理
 AfxMessageBox("線程二正在執(zhí)行!");
 
// 釋放信號量計(jì)數(shù)
 g_clsSemaphore.Unlock();
 
return 0;
}

UINT ThreadProc26(LPVOID pParam)
{
 
// 試圖進(jìn)入信號量關(guān)口
 g_clsSemaphore.Lock();
 
// 線程任務(wù)處理
 AfxMessageBox("線程三正在執(zhí)行!");
 
// 釋放信號量計(jì)數(shù)
 g_clsSemaphore.Unlock();
 
return 0;
}

……
void CSample08View::OnSemaphoreMfc() 
{
 
// 開啟線程
 AfxBeginThread(ThreadProc24, NULL);
 AfxBeginThread(ThreadProc25, NULL);
 AfxBeginThread(ThreadProc26, NULL);
}


  互斥內(nèi)核對象

  互斥(Mutex)是一種用途非常廣泛的內(nèi)核對象。能夠保證多個(gè)線程對同一共享資源的互斥訪問。同臨界區(qū)有些類似,只有擁有互斥對象的線程才具有訪問資源的權(quán)限,由于互斥對象只有一個(gè),因此就決定了任何情況下此共享資源都不會同時(shí)被多個(gè)線程所訪問。當(dāng)前占據(jù)資源的線程在任務(wù)處理完后應(yīng)將擁有的互斥對象交出,以便其他線程在獲得后得以訪問資源。與其他幾種內(nèi)核對象不同,互斥對象在操作系統(tǒng)中擁有特殊代碼,并由操作系統(tǒng)來管理,操作系統(tǒng)甚至還允許其進(jìn)行一些其他內(nèi)核對象所不能進(jìn)行的非常規(guī)操作。為便于理解,可參照圖6給出的互斥內(nèi)核對象的工作模型:


圖6 使用互斥內(nèi)核對象對共享資源的保護(hù)

  圖(a)中的箭頭為要訪問資源(矩形框)的線程,但只有第二個(gè)線程擁有互斥對象(黑點(diǎn))并得以進(jìn)入到共享資源,而其他線程則會被排斥在外(如圖(b)所示)。當(dāng)此線程處理完共享資源并準(zhǔn)備離開此區(qū)域時(shí)將把其所擁有的互斥對象交出(如圖(c)所示),其他任何一個(gè)試圖訪問此資源的線程都有機(jī)會得到此互斥對象。

  以互斥內(nèi)核對象來保持線程同步可能用到的函數(shù)主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥對象前,首先要通過CreateMutex()或OpenMutex()創(chuàng)建或打開一個(gè)互斥對象。CreateMutex()函數(shù)原型為:

HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, 
// 安全屬性指針
 BOOL bInitialOwner, // 初始擁有者
 LPCTSTR lpName // 互斥對象名
);


  參數(shù)bInitialOwner主要用來控制互斥對象的初始狀態(tài)。一般多將其設(shè)置為FALSE,以表明互斥對象在創(chuàng)建時(shí)并沒有為任何線程所占有。如果在創(chuàng)建互斥對象時(shí)指定了對象名,那么可以在本進(jìn)程其他地方或是在其他進(jìn)程通過OpenMutex()函數(shù)得到此互斥對象的句柄。OpenMutex()函數(shù)原型為:

HANDLE OpenMutex(
 DWORD dwDesiredAccess, 
// 訪問標(biāo)志
 BOOL bInheritHandle, // 繼承標(biāo)志
 LPCTSTR lpName // 互斥對象名
);


  當(dāng)目前對資源具有訪問權(quán)的線程不再需要訪問此資源而要離開時(shí),必須通過ReleaseMutex()函數(shù)來釋放其擁有的互斥對象,其函數(shù)原型為:

BOOL ReleaseMutex(HANDLE hMutex);


  其唯一的參數(shù)hMutex為待釋放的互斥對象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函數(shù)在互斥對象保持線程同步中所起的作用與在其他內(nèi)核對象中的作用是基本一致的,也是等待互斥內(nèi)核對象的通知。但是這里需要特別指出的是:在互斥對象通知引起調(diào)用等待函數(shù)返回時(shí),等待函數(shù)的返回值不再是通常的WAIT_OBJECT_0(對于WaitForSingleObject()函數(shù))或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之間的一個(gè)值(對于WaitForMultipleObjects()函數(shù)),而是將返回一個(gè)WAIT_ABANDONED_0(對于WaitForSingleObject()函數(shù))或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之間的一個(gè)值(對于WaitForMultipleObjects()函數(shù))。以此來表明線程正在等待的互斥對象由另外一個(gè)線程所擁有,而此線程卻在使用完共享資源前就已經(jīng)終止。除此之外,使用互斥對象的方法在等待線程的可調(diào)度性上同使用其他幾種內(nèi)核對象的方法也有所不同,其他內(nèi)核對象在沒有得到通知時(shí),受調(diào)用等待函數(shù)的作用,線程將會掛起,同時(shí)失去可調(diào)度性,而使用互斥的方法卻可以在等待的同時(shí)仍具有可調(diào)度性,這也正是互斥對象所能完成的非常規(guī)操作之一。

  在編寫程序時(shí),互斥對象多用在對那些為多個(gè)線程所訪問的內(nèi)存塊的保護(hù)上,可以確保任何線程在處理此內(nèi)存塊時(shí)都對其擁有可靠的獨(dú)占訪問權(quán)。下面給出的示例代碼即通過互斥內(nèi)核對象hMutex對共享內(nèi)存快g_cArray[]進(jìn)行線程的獨(dú)占訪問保護(hù)。下面給出實(shí)現(xiàn)代碼清單:

// 互斥對象
HANDLE hMutex = NULL;
char g_cArray[10];
UINT ThreadProc18(LPVOID pParam)
{
 
// 等待互斥對象通知
 WaitForSingleObject(hMutex, INFINITE);
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[i] 
= 'a';
  Sleep(
1);
 }

 
// 釋放互斥對象
 ReleaseMutex(hMutex);
 
return 0;
}

UINT ThreadProc19(LPVOID pParam)
{
 
// 等待互斥對象通知
 WaitForSingleObject(hMutex, INFINITE);
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[
10 - i - 1= 'b';
  Sleep(
1);
 }

 
// 釋放互斥對象
 ReleaseMutex(hMutex);
 
return 0;
}

……
void CSample08View::OnMutex() 
{
 
// 創(chuàng)建互斥對象
 hMutex = CreateMutex(NULL, FALSE, NULL);
 
// 啟動線程
 AfxBeginThread(ThreadProc18, NULL);
 AfxBeginThread(ThreadProc19, NULL);
 
// 等待計(jì)算完畢
 Sleep(300);
 
// 報(bào)告計(jì)算結(jié)果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}


  互斥對象在MFC中通過CMutex類進(jìn)行表述。使用CMutex類的方法非常簡單,在構(gòu)造CMutex類對象的同時(shí)可以指明待查詢的互斥對象的名字,在構(gòu)造函數(shù)返回后即可訪問此互斥變量。CMutex類也是只含有構(gòu)造函數(shù)這唯一的成員函數(shù),當(dāng)完成對互斥對象保護(hù)資源的訪問后,可通過調(diào)用從父類CSyncObject繼承的UnLock()函數(shù)完成對互斥對象的釋放。CMutex類構(gòu)造函數(shù)原型為:

CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );


  該類的適用范圍和實(shí)現(xiàn)原理與API方式創(chuàng)建的互斥內(nèi)核對象是完全類似的,但要簡潔的多,下面給出就是對前面的示例代碼經(jīng)CMutex類改寫后的程序?qū)崿F(xiàn)清單:

// MFC互斥類對象
CMutex g_clsMutex(FALSE, NULL);
UINT ThreadProc27(LPVOID pParam)
{
 
// 等待互斥對象通知
 g_clsMutex.Lock();
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[i] 
= 'a';
  Sleep(
1);
 }

 
// 釋放互斥對象
 g_clsMutex.Unlock();
 
return 0;
}

UINT ThreadProc28(LPVOID pParam)
{
 
// 等待互斥對象通知
 g_clsMutex.Lock();
 
// 對共享資源進(jìn)行寫入操作
 for (int i = 0; i < 10; i++)
 
{
  g_cArray[
10 - i - 1= 'b';
  Sleep(
1);
 }

 
// 釋放互斥對象
 g_clsMutex.Unlock();
 
return 0;
}

……
void CSample08View::OnMutexMfc() 
{
 
// 啟動線程
 AfxBeginThread(ThreadProc27, NULL);
 AfxBeginThread(ThreadProc28, NULL);
 
// 等待計(jì)算完畢
 Sleep(300);
 
// 報(bào)告計(jì)算結(jié)果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}


  小結(jié)

  線程的使用使程序處理更夠更加靈活,而這種靈活同樣也會帶來各種不確定性的可能。尤其是在多個(gè)線程對同一公共變量進(jìn)行訪問時(shí)。雖然未使用線程同步的程序代碼在邏輯上或許沒有什么問題,但為了確保程序的正確、可靠運(yùn)行,必須在適當(dāng)?shù)膱龊喜扇【€程同步措施。

posted on 2011-04-14 22:36 沛沛 閱讀(446) 評論(1)  編輯 收藏 引用 所屬分類: Windows

Feedback

# re: Visual C++線程同步技術(shù)剖析 2011-05-12 18:50 K.V
寫的很詳細(xì),也很清晰。  回復(fù)  更多評論
  

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲小说春色综合另类电影| 亚洲精品乱码久久久久久日本蜜臀| 一区二区久久久久| 欧美日韩综合视频| 亚洲欧美bt| 午夜精品一区二区三区在线播放| 国内一区二区在线视频观看| 你懂的网址国产 欧美| 免费在线欧美视频| 亚洲精选视频在线| 一本到12不卡视频在线dvd| 国产精品国产三级国产专播品爱网| 一区二区日本视频| 亚洲欧美日韩国产一区| 黑人巨大精品欧美一区二区小视频 | 国产视频精品免费播放| 蜜臀久久久99精品久久久久久| 欧美大片免费久久精品三p | 影音先锋在线一区| 亚洲精品美女在线观看播放| 国产美女一区二区| 欧美电影免费观看高清| 欧美日韩一区二区在线播放| 久久久久国产一区二区| 欧美日本一区二区三区| 久久精品72免费观看| 欧美精品系列| 久久综合九色| 国产精品揄拍500视频| 亚洲国产裸拍裸体视频在线观看乱了中文 | 国产性做久久久久久| 亚洲经典在线| 在线看国产日韩| 亚洲一区二区三区在线播放| 亚洲日本成人在线观看| 欧美一区二区三区在线| 亚洲资源av| 欧美激情精品久久久久久蜜臀 | 亚洲欧美日韩精品久久奇米色影视 | 欧美极品在线观看| 老司机午夜免费精品视频 | 欧美va天堂在线| 久久尤物视频| 国产精品一区视频网站| 亚洲精品永久免费| 亚洲毛片一区| 欧美成人一区二区三区在线观看 | 亚洲网站啪啪| 欧美精品激情在线观看| 欧美午夜国产| 亚洲性感激情| 亚洲精品国久久99热| 香蕉乱码成人久久天堂爱免费| 亚洲综合成人婷婷小说| 欧美日本在线一区| 91久久久亚洲精品| 亚洲国产精品一区制服丝袜| 久久精品国产亚洲高清剧情介绍| 久久9热精品视频| 国产精品亚洲欧美| 亚洲综合首页| 欧美综合激情网| 国产亚洲一区精品| 久久深夜福利免费观看| 老司机一区二区三区| 激情欧美丁香| 久久另类ts人妖一区二区| 久久久免费精品视频| 狠狠综合久久av一区二区小说| 久久福利视频导航| 欧美成人精品不卡视频在线观看| 在线日韩av永久免费观看| 久久嫩草精品久久久精品一| 欧美va亚洲va香蕉在线| 91久久综合亚洲鲁鲁五月天| 欧美高清视频一区二区| 亚洲精品少妇网址| 午夜一区二区三视频在线观看| 国产精品一卡| 久久久久久夜| 亚洲精品日产精品乱码不卡| 亚洲欧美日韩精品一区二区| 国产人成精品一区二区三| 欧美专区在线观看| 亚洲激情小视频| 午夜精品国产更新| 狠狠色综合色区| 欧美xxxx在线观看| 国产精品99久久99久久久二8| 欧美一区二区视频97| 精品动漫一区| 欧美日韩免费一区| 久久www免费人成看片高清| 欧美寡妇偷汉性猛交| 亚洲一区国产一区| 狠狠干成人综合网| 欧美日韩国产一级| 亚洲欧美日韩一区| 亚洲激情一区| 久久免费视频在线观看| 一区二区电影免费在线观看| 国产啪精品视频| 欧美激情四色 | 中日韩午夜理伦电影免费| 久久久久久久成人| 一区二区三区日韩欧美精品| 国产亚洲视频在线观看| 欧美精品日韩| 久久久久一区二区三区| 亚洲性感激情| 亚洲国产一二三| 久久精品免费看| 亚洲欧美一区二区原创| 最新中文字幕亚洲| 狠狠久久亚洲欧美| 国产精品一区二区在线观看不卡 | 国产精品久久国产精麻豆99网站| 欧美视频免费看| 99天天综合性| 久久天天躁夜夜躁狠狠躁2022| 亚洲国产欧美国产综合一区| 国产精品自拍三区| 欧美日韩中文字幕| 欧美成人免费全部| 久久精品国产99| 亚洲一区二区在线看| 亚洲精品一区二区在线观看| 欧美国产日韩精品| 欧美.com| 鲁大师成人一区二区三区| 久久九九99视频| 久久精品理论片| 欧美制服丝袜| 欧美影院一区| 久久精品国产一区二区三区| 欧美一级播放| 性做久久久久久| 欧美一区二区三区男人的天堂 | 亚洲麻豆国产自偷在线| 亚洲国产欧美在线人成| 尤物yw午夜国产精品视频明星| 国产一区欧美日韩| 国内成+人亚洲+欧美+综合在线| 国产日韩一区二区三区| 国产人久久人人人人爽| 国产婷婷色综合av蜜臀av| 国产日韩精品一区观看| 国产视频亚洲精品| 国外成人在线| 亚洲国产另类精品专区| 亚洲精品免费在线| 一区二区三区蜜桃网| 亚洲无毛电影| 欧美一区二区三区四区夜夜大片| 欧美伊人久久久久久午夜久久久久| 久久激情视频久久| 鲁大师影院一区二区三区| 欧美国产成人在线| 亚洲美洲欧洲综合国产一区| 一区二区三区不卡视频在线观看| 宅男噜噜噜66一区二区66| 亚洲小视频在线观看| 久久精品国产77777蜜臀| 美国成人直播| 欧美午夜大胆人体| 很黄很黄激情成人| 一本一本大道香蕉久在线精品| 亚洲欧美日本日韩| 免费在线观看一区二区| 亚洲经典自拍| 欧美影院成年免费版| 免费欧美日韩国产三级电影| 欧美亚洲第一页| 亚洲电影在线播放| 亚洲一区二区四区| 男人的天堂亚洲在线| 99re6热只有精品免费观看| 午夜一区二区三区不卡视频| 玖玖视频精品| 国产乱人伦精品一区二区| 亚洲国产91| 欧美一区二区三区免费在线看| 欧美激情亚洲精品| 午夜精品999| 欧美日韩国产不卡| 在线观看视频欧美| 性欧美1819性猛交| 亚洲黄网站在线观看| 久久国产精品一区二区三区四区 | 欧美日韩国产在线播放网站| 国产自产精品| 午夜欧美理论片| 欧美日韩福利在线观看| 欧美一区成人| 久久久久国产精品厨房| 亚洲欧洲三级| 久久久91精品| 国产亚洲精品aa午夜观看| 夜夜嗨av一区二区三区中文字幕| 久久久久久有精品国产|