99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋(píng)果6袋
6
麥子學(xué)院

C++ 線程同步有哪4種方式?

發(fā)布時(shí)間:2016-12-24 12:25  回復(fù):0  查看:2186   最后回復(fù):2016-12-24 12:25  

C++程序開(kāi)發(fā)中,線程之間通信的兩個(gè)基本問(wèn)題是互斥和同步

  (1)線程同步是指線程之間所具有的一種制約關(guān)系,一個(gè)線程的執(zhí)行依賴(lài)另一個(gè)線程的消息,當(dāng)它沒(méi)有得到另一個(gè)線程的消息時(shí)應(yīng)等待,直到消息到達(dá)時(shí)才被喚醒。

  (2)線程互斥是指對(duì)于共享的操作系統(tǒng)資源(指的是廣義的資源,而不是Windows.res文件,譬如全局變量就是一種共享資源),在各線程訪問(wèn)時(shí)的排它性。當(dāng)有若干個(gè)線程都要使用某一共享資源時(shí),任何時(shí)刻最多只允許一個(gè)線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。

  線程互斥是一種特殊的線程同步。實(shí)際上,互斥和同步對(duì)應(yīng)著線程間通信發(fā)生的兩種情況:

  (1)當(dāng)有多個(gè)線程訪問(wèn)共享資源而不使資源被破壞時(shí);

  (2)當(dāng)一個(gè)線程需要將某個(gè)任務(wù)已經(jīng)完成的情況通知另外一個(gè)或多個(gè)線程時(shí)。

  從大的方面講,線程的同步可分用戶(hù)模式的線程同步和內(nèi)核對(duì)象的線程同步兩大類(lèi)。用戶(hù)模式中線程的同步方法主要有原子訪問(wèn)和臨界區(qū)等方法。其特點(diǎn)是同步速度特別快,適合于對(duì)線程運(yùn)行速度有嚴(yán)格要求的場(chǎng)合。

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

  在WIN32中,同步機(jī)制主要有以下幾種:

 ?。?/span>1)事件(Event);

  2)信號(hào)量(semaphore);

  3)互斥量(mutex);

  4)臨界區(qū)(Critical section)。

  臨界區(qū)

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

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

  全局變量

  因?yàn)檫M(jìn)程中的所有線程均可以訪問(wèn)所有的全局變量,因而全局變量成為Win32多線程通信的最簡(jiǎn)單方式。例如:

  int var; //全局變量

  UINTThreadFunction(LPVOIDpParam)

  {

  var = 0;

  while (var < MaxValue)

  {

  //線程處理

  ::InterlockedIncrement(long*) &var);

  }

  return 0;

  }

  請(qǐng)看下列程序:

  int globalFlag = false;

  DWORDWINAPIThreadFunc(LPVOID n)

  {

  Sleep(2000);

  globalFlag = true;

  return 0;

  }

  int main(){

  HANDLEhThrd;

  DWORDthreadId;

  hThrd = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &threadId);

  if (hThrd)

  {

  printf("Thread launched\n");

  CloseHandle(hThrd);

  }

  while (!globalFlag)

  ;

  printf("exit\n");

  }

  上述程序中使用全局變量和while循環(huán)查詢(xún)進(jìn)行線程間同步,實(shí)際上,這是一種應(yīng)該避免的方法,因?yàn)椋?/span>

 ?。?/span>1)當(dāng)主線程必須使自己與ThreadFunc函數(shù)的完成運(yùn)行實(shí)現(xiàn)同步時(shí),它并沒(méi)有使自己進(jìn)入睡眠狀態(tài)。由于主線程沒(méi)有進(jìn)入睡眠狀態(tài),因此操作系統(tǒng)繼續(xù)為它調(diào)度C P U時(shí)間,這就要占用其他線程的寶貴時(shí)間周期;

  (2)當(dāng)主線程的優(yōu)先級(jí)高于執(zhí)行ThreadFunc函數(shù)的線程時(shí),就會(huì)發(fā)生globalFlag永遠(yuǎn)不能被賦值為true的情況。因?yàn)樵谶@種情況下,系統(tǒng)決不會(huì)將任何時(shí)間片分配給ThreadFunc線程。

  事件

  事件(Event)WIN32提供的最靈活的線程間同步方式,事件可以處于激發(fā)狀態(tài)(signaled or true)或未激發(fā)狀態(tài)(unsignal or false)。根據(jù)狀態(tài)變遷方式的不同,事件可分為兩類(lèi):

  (1)手動(dòng)設(shè)置:這種對(duì)象只可能用程序手動(dòng)設(shè)置,在需要該事件或者事件發(fā)生時(shí),采用SetEventResetEvent來(lái)進(jìn)行設(shè)置。

  (2)自動(dòng)恢復(fù):一旦事件發(fā)生并被處理后,自動(dòng)恢復(fù)到?jīng)]有事件狀態(tài),不需要再次設(shè)置。

  使用事件機(jī)制應(yīng)注意以下事項(xiàng):

 ?。?/span>1)如果跨進(jìn)程訪問(wèn)事件,必須對(duì)事件命名,在對(duì)事件命名的時(shí)候,要注意不要與系統(tǒng)命名空間中的其它全局命名對(duì)象沖突;

 ?。?/span>2)事件是否要自動(dòng)恢復(fù);

 ?。?/span>3)事件的初始狀態(tài)設(shè)置。

  由于event對(duì)象屬于內(nèi)核對(duì)象,故進(jìn)程B可以調(diào)用OpenEvent函數(shù)通過(guò)對(duì)象的名字獲得進(jìn)程Aevent對(duì)象的句柄,然后將這個(gè)句柄用于ResetEventSetEventWaitForMultipleObjects等函數(shù)中。此法可以實(shí)現(xiàn)一個(gè)進(jìn)程的線程控制另一進(jìn)程中線程的運(yùn)行,例如:

  HANDLEhEvent=OpenEvent(EVENT_ALL_ACCESS,true,"MyEvent");

  ResetEvent(hEvent);

  信號(hào)量

  信號(hào)量是維護(hù)0到指定最大值之間的同步對(duì)象。信號(hào)量狀態(tài)在其計(jì)數(shù)大于0時(shí)是有信號(hào)的,而其計(jì)數(shù)是0時(shí)是無(wú)信號(hào)的。信號(hào)量對(duì)象在控制上可以支持有限數(shù)量共享資源的訪問(wèn)。

  信號(hào)量的特點(diǎn)和用途可用下列幾句話定義:

 ?。?/span>1)如果當(dāng)前資源的數(shù)量大于0,則信號(hào)量有效;

 ?。?/span>2)如果當(dāng)前資源數(shù)量是0,則信號(hào)量無(wú)效;

 ?。?/span>3)系統(tǒng)決不允許當(dāng)前資源的數(shù)量為負(fù)值;

 ?。?/span>4)當(dāng)前資源數(shù)量決不能大于最大資源數(shù)量。

  創(chuàng)建信號(hào)量

  HANDLECreateSemaphore (

  PSECURITY_ATTRIBUTEpsa,

  LONG lInitialCount, //開(kāi)始時(shí)可供使用的資源數(shù)

  LONG lMaximumCount, //最大資源數(shù)

  PCTSTRpszName);

  釋放信號(hào)量

  通過(guò)調(diào)用ReleaseSemaphore函數(shù),線程就能夠?qū)π艠?biāo)的當(dāng)前資源數(shù)量進(jìn)行遞增,該函數(shù)原型為:

  BOOL WINAPIReleaseSemaphore(

  HANDLEhSemaphore,

  LONG lReleaseCount, //信號(hào)量的當(dāng)前資源數(shù)增加lReleaseCount

  LPLONGlpPreviousCount

  );

  打開(kāi)信號(hào)量

  和其他核心對(duì)象一樣,信號(hào)量也可以通過(guò)名字跨進(jìn)程訪問(wèn),打開(kāi)信號(hào)量的API為:

  HANDLEOpenSemaphore (

  DWORDfdwAccess,

  BOOL bInherithandle,

  PCTSTRpszName

  );

  互鎖訪問(wèn)

  當(dāng)必須以原子操作方式來(lái)修改單個(gè)值時(shí),互鎖訪問(wèn)函數(shù)是相當(dāng)有用的。所謂原子訪問(wèn),是指線程在訪問(wèn)資源時(shí)能夠確保所有其他線程都不在同一時(shí)間內(nèi)訪問(wèn)相同的資源。

  請(qǐng)看下列代碼:

  int globalVar = 0;

  DWORDWINAPIThreadFunc1(LPVOID n)

  {

  globalVar++;

  return 0;

  }

  DWORDWINAPIThreadFunc2(LPVOID n)

  {

  globalVar++;

  return 0;

  }

  運(yùn)行ThreadFunc1ThreadFunc2線程,結(jié)果是不可預(yù)料的,因?yàn)?/span>globalVar++并不對(duì)應(yīng)著一條機(jī)器指令,我們看看globalVar++的反匯編代碼:

  00401038 moveax,[globalVar (0042d3f0)]0040103D addeax,100401040 mov [globalVar (0042d3f0)],eax

  在”mov eax,[globalVar (0042d3f0)]” 指令與”add eax,1″ 指令以及”add eax,1″ 指令與”mov [globalVar (0042d3f0)],eax”指令之間都可能發(fā)生線程切換,使得程序的執(zhí)行后globalVar的結(jié)果不能確定。我們可以使用InterlockedExchangeAdd函數(shù)解決這個(gè)問(wèn)題:

  int globalVar = 0;

  DWORDWINAPIThreadFunc1(LPVOID n)

  {

  InterlockedExchangeAdd(&globalVar,1);

  return 0;

  }

  DWORDWINAPIThreadFunc2(LPVOID n)

  {

  InterlockedExchangeAdd(&globalVar,1);

  return 0;

  }

  InterlockedExchangeAdd保證對(duì)變量globalVar的訪問(wèn)具有原子性?;ユi訪問(wèn)的控制速度非???,調(diào)用一個(gè)互鎖函數(shù)的CPU周期通常小于50,不需要進(jìn)行用戶(hù)方式與內(nèi)核方式的切換(該切換通常需要運(yùn)行1000個(gè)CPU周期)。

  互鎖訪問(wèn)函數(shù)的缺點(diǎn)在于其只能對(duì)單一變量進(jìn)行原子訪問(wèn),如果要訪問(wèn)的資源比較復(fù)雜,仍要使用臨界區(qū)或互斥。

  可等待定時(shí)器

  可等待定時(shí)器是在某個(gè)時(shí)間或按規(guī)定的間隔時(shí)間發(fā)出自己的信號(hào)通知的內(nèi)核對(duì)象。它們通常用來(lái)在某個(gè)時(shí)間執(zhí)行某個(gè)操作。

  創(chuàng)建可等待定時(shí)器

  HANDLECreateWaitableTimer(

  PSECURITY_ATTRISUTESpsa,

  BOOL fManualReset,//人工重置或自動(dòng)重置定時(shí)器

  PCTSTRpszName);

  設(shè)置可等待定時(shí)器

  可等待定時(shí)器對(duì)象在非激活狀態(tài)下被創(chuàng)建,程序員應(yīng)調(diào)用 SetWaitableTimer函數(shù)來(lái)界定定時(shí)器在何時(shí)被激活:

  BOOL SetWaitableTimer(

  HANDLEhTimer, //要設(shè)置的定時(shí)器

  const LARGE_INTEGER *pDueTime, //指明定時(shí)器第一次激活的時(shí)間

  LONG lPeriod, //指明此后定時(shí)器應(yīng)該間隔多長(zhǎng)時(shí)間激活一次

  PTIMERAPCROUTINEpfnCompletionRoutine,

  PVOIDPvArgToCompletionRoutine,

  BOOL fResume);

  取消可等待定時(shí)器

  BOOL CancelWaitableTimer(

  HANDLEhTimer //要取消的定時(shí)器

  );

  打開(kāi)可等待定時(shí)器

  作為一種內(nèi)核對(duì)象,WaitableTimer也可以被其他進(jìn)程以名字打開(kāi):

  HANDLEOpenWaitableTimer (

  DWORDfdwAccess,

  BOOL bInherithandle,

  PCTSTRpszName

  );

  實(shí)例

  下面給出的一個(gè)程序可能發(fā)生死鎖現(xiàn)象:

  #include #include 

  CRITICAL_SECTIONcs1, cs2;long WINAPIThreadFn(long);

  main()

  {

  long iThreadID;

  InitializeCriticalSection(&cs1);

  InitializeCriticalSection(&cs2);

  CloseHandle(CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFn, NULL, 0,&iThreadID));

  while (TRUE)

  {

  EnterCriticalSection(&cs1);

  printf("\n線程1占用臨界區(qū)1");

  EnterCriticalSection(&cs2);

  printf("\n線程1占用臨界區(qū)2");

  printf("\n線程1占用兩個(gè)臨界區(qū)");

  LeaveCriticalSection(&cs2);

  LeaveCriticalSection(&cs1);

  printf("\n線程1釋放兩個(gè)臨界區(qū)");

  Sleep(20);

  };

  return (0);

  }

  long WINAPIThreadFn(long lParam){

  while (TRUE)

  {

  EnterCriticalSection(&cs2);

  printf("\n線程2占用臨界區(qū)2");

  EnterCriticalSection(&cs1);

  printf("\n線程2占用臨界區(qū)1");

  printf("\n線程2占用兩個(gè)臨界區(qū)");

  LeaveCriticalSection(&cs1);

  LeaveCriticalSection(&cs2);

  printf("\n線程2釋放兩個(gè)臨界區(qū)");

  Sleep(20);

  };

  }

  運(yùn)行這個(gè)程序,在中途一旦發(fā)生這樣的輸出:

  線程1占用臨界區(qū)線程2占用臨界區(qū)2

  或

  線程2占用臨界區(qū)線程1占用臨界區(qū)1

  或

  線程1占用臨界區(qū)線程2占用臨界區(qū)1

  或

  線程2占用臨界區(qū)線程1占用臨界區(qū)2

  程序就掉了,再也運(yùn)行不下去。因?yàn)檫@樣的輸出,意味著兩個(gè)線程相互等待對(duì)方釋放臨界區(qū),也即出現(xiàn)了死鎖。

  如果我們將線程2的控制函數(shù)改為:

  long WINAPIThreadFn(long lParam)

  {

  while (TRUE)

  {

  EnterCriticalSection(&cs1);

  printf("\n線程2占用臨界區(qū)1");

  EnterCriticalSection(&cs2);

  printf("\n線程2占用臨界區(qū)2");

  printf("\n線程2占用兩個(gè)臨界區(qū)");

  LeaveCriticalSection(&cs1);

  LeaveCriticalSection(&cs2);

  printf("\n線程2釋放兩個(gè)臨界區(qū)");

  Sleep(20);

  };

  }

  再次運(yùn)行程序,死鎖被消除,程序不再擋掉。這是因?yàn)槲覀兏淖兞司€程2中獲得臨界區(qū)1、2的順序,消除了線程1、2相互等待資源的可能性。

  由此我們得出結(jié)論,在使用線程間的同步機(jī)制時(shí),要特別留心死鎖的發(fā)生。

 

來(lái)源:伯樂(lè)在線

您還未登錄,請(qǐng)先登錄

熱門(mén)帖子

最新帖子

?