[轉載]WaitForSingleObject(轉)

臨界區是一種最簡單的同步對象,它只可以在同一進程內部使用。它的作用是保證只有一個線程可以申請到該對象
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); 產生臨界區
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); 刪除臨界區
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );  進入臨界區,相當於申請加鎖,如果該臨界區正被其他線程使用則該函數會等待到其他線程釋放
bool TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection ); 進入臨界區,相當於申請加鎖,和EnterCriticalSection不同如果該臨界區正被其他線程使用則該函數會立即返回FALSE,而不會等待
VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection );  退出臨界區,相當於申請解鎖

下面的示範代碼演示瞭如何使用臨界區來進行數據同步處理:

//全局變量
int iCounter=0;
CRITICAL_SECTION criCounter;

DWORD threadA(void* pD)
{
 int iID=(int)pD;
 for(int i=0;i<8;i++)
 {
  EnterCriticalSection(&criCounter);
  int iCopy=iCounter;
  Sleep(100);
  iCounter=iCopy+1;
  printf("thread %d : %dn",iID,iCounter);
  LeaveCriticalSection(&criCounter);
 }
 return 0;
}
//in main function
{
  //創建臨界區
  InitializeCriticalSection(&criCounter);
  //創建線程
  HANDLE hThread[3];
  CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
  CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
  CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
  hThread[0]=pT1->m_hThread;
  hThread[1]=pT2->m_hThread;
  hThread[2]=pT3->m_hThread;
  //等待線程結束
  //至於 WaitForMultipleObjects 的用法後面會講到。
  WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
  //刪除臨界區
  DeleteCriticalSection(&criCounter);
  printf("novern");
}

互斥量

互斥量與臨界區的作用非常相似,但互斥量是可以命名的,也就是說它可以跨越進程使用。所以創建互斥量需要的資源更多,所以如果只爲了在進程內部是用的話使用臨界區會帶來速度上的優勢並能夠減少資源佔用量。因爲互斥量是跨進程的互斥量一旦被創建,就可以通過名字打開它
創建互斥量:
HANDLE CreateMutex(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,  // 安全信息
  BOOL bInitialOwner,                 // 最初狀態,
  //如果設置爲真,則表示創建它的線程直接擁有了該互斥量,而不需要再申請
  LPCTSTR lpName                    // 名字,可以爲NULL,但這樣一來就不能被其他線程/進程打開
);
打開一個存在的互斥量:
HANDLE OpenMutex(
  DWORD dwDesiredAccess,    // 存取方式
  BOOL bInheritHandle,            // 是否可以被繼承
  LPCTSTR lpName                  // 名字
);
釋放互斥量的使用權,但要求調用該函數的線程擁有該互斥量的使用權:
BOOL ReleaseMutex(              //作用如同LeaveCriticalSection
  HANDLE hMutex                    // 句柄
);
關閉互斥量:
BOOL CloseHandle(
  HANDLE hObject                  // 句柄
);
對於互斥量來講如果正在被使用則爲無信號狀態,被釋放後變爲有信號狀態。當等待成功後 WaitForSingleObject 函數會將互斥量置爲無信號狀態,這樣其他的線程就不能獲得使用權而需要繼續等待。WaitForSingleObject 函數還進行排隊功能,保證先提出等待請求的線程先獲得對象的使用權

int iCounter=0;

DWORD threadA(void* pD)
{
 int iID=(int)pD;
 //在內部重新打開
 HANDLE hCounterIn=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"sam sp 44");

 for(int i=0;i<8;i++)
 {
  printf("%d wait for objectn",iID);
  WaitForSingleObject(hCounterIn,INFINITE);
  int iCopy=iCounter;
  Sleep(100);
  iCounter=iCopy+1;
  printf("ttthread %d : %dn",iID,iCounter);
  ReleaseMutex(hCounterIn);
 }
 CloseHandle(hCounterIn);
 return 0;
}

//in main function
{
  //創建互斥量
  HANDLE hCounter=NULL;
  if( (hCounter=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
  {
   //如果沒有其他進程創建這個互斥量,則重新創建
   hCounter = CreateMutex(NULL,FALSE,"sam sp 44");
  }

  //創建線程
  HANDLE hThread[3];
  CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
  CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
  CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
  hThread[0]=pT1->m_hThread;
  hThread[1]=pT2->m_hThread;
  hThread[2]=pT3->m_hThread;

  //等待線程結束
  WaitForMultipleObjects(3,hThread,TRUE,INFINITE);

  //關閉句柄
  CloseHandle(hCounter);
 }
}

WaitForSingleObject 這個函數可以作用於:
Mutex
Event
Semaphore
Job
Process
Thread
Waitable timer
Console input
互斥量(Mutex),信號燈(Semaphore),事件(Event)都可以被跨越進程使用來進行同步數據操作,而其他的對象與數據同步操作無關,但對於進程和線程來講,如果進程和線程在運行狀態則爲無信號狀態,在退出後爲有信號狀態。所以我們可以使用 WaitForSingleObject 來等待進程和線程退出。(至於信號燈,事件的用法我們接下來會講)我們在前面的例子中使用了 WaitForMultipleObjects 函數,這個函數的作用與 WaitForSingleObject 類似但從名字上我們可以看出,WaitForMultipleObjects 將用於等待多個對象變爲有信號狀態,函數原型如下:
DWORD WaitForMultipleObjects(
  DWORD nCount,                     // 等待的對象數量
  CONST HANDLE *lpHandles,  // 對象句柄數組指針
  BOOL fWaitAll,                        // 等待方式,
  //爲TRUE表示等待全部對象都變爲有信號狀態才返回,爲FALSE表示任何一個對象變爲有信號狀態則返回
  DWORD dwMilliseconds         // 超時設置,以ms爲單位,如果爲INFINITE表示無限期的等待
);

dwMilliseconds 的範圍從 0 - 0x7fffffff 或者 INFINITE - 0xffffffff

返回值意義:
WAIT_OBJECT_0 到 (WAIT_OBJECT_0 + nCount – 1):當 fWaitAll 爲 TRUE 時表示所有對象變爲有信號狀態,當 fWaitAll 爲 FALSE 時使用返回值減去 WAIT_OBJECT_0 得到變爲有信號狀態的對象在數組中的下標。
WAIT_ABANDONED_0 到 (WAIT_ABANDONED_0 + nCount – 1):當 fWaitAll 爲 TRUE 時表示所有對象變爲有信號狀態,當 fWaitAll 爲 FALSE 時表示對象中有一個對象爲互斥量,該互斥量因爲被關閉而成爲有信號狀態,使用返回值減去 WAIT_OBJECT_0 得到變爲有信號狀態的對象在數組中的下標。
WAIT_TIMEOUT:表示超過規定時間。

信號燈

信號燈有一個初始值,表示有多少進程/線程可以進入,當信號燈的值大於 0 時爲有信號狀態,小於等於 0 時爲無信號狀態,所以可以利用 WaitForSingleObject 進行等待,當 WaitForSingleObject 等待成功後信號燈的值會被減少 1,直到釋放時信號燈會被增加 1。用於信號燈操作的 API 函數有下面這些:

創建信號燈:
HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,   // 安全屬性,NULL表示使用默認的安全描述
  LONG lInitialCount,              // 初始值
  LONG lMaximumCount,       // 最大值
  LPCTSTR lpName               // 名字
);
打開信號燈:
HANDLE OpenSemaphore(
  DWORD dwDesiredAccess,    // 存取方式
  BOOL bInheritHandle,             // 是否能被繼承
  LPCTSTR lpName                  // 名字
);
釋放信號燈:
BOOL ReleaseSemaphore(
  HANDLE hSemaphore,          // 句柄
  LONG lReleaseCount,           // 釋放數,讓信號燈值增加數
  LPLONG lpPreviousCount     // 用來得到釋放前信號燈的值,可以爲NULL
);
關閉信號燈:
BOOL CloseHandle(
  HANDLE hObject   // 句柄
);
DWORD threadA(void* pD)
{
 int iID=(int)pD;
 //在內部重新打開
 HANDLE hCounterIn=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44");

 for(int i=0;i<3;i++)
 {
  printf("%d wait for objectn",iID);
  WaitForSingleObject(hCounterIn,INFINITE);
  printf("ttthread %d : do database access calln",iID);
  Sleep(100);
  printf("ttthread %d : do database access call endn",iID);
  ReleaseSemaphore(hCounterIn,1,NULL);
 }
 CloseHandle(hCounterIn);
 return 0;
}
//in main function
{
  //創建信號燈
  HANDLE hCounter=NULL;
  if( (hCounter=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
  {
   //如果沒有其他進程創建這個信號燈,則重新創建
   hCounter = CreateSemaphore(NULL,2,2,"sam sp 44");
  }

  //創建線程
  HANDLE hThread[3];
  CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
  CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
  CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
  hThread[0]=pT1->m_hThread;
  hThread[1]=pT2->m_hThread;
  hThread[2]=pT3->m_hThread;
  //等待線程結束
  WaitForMultipleObjects(3,hThread,TRUE,INFINITE);

  //關閉句柄
  CloseHandle(hCounter); 
}

信號燈有時用來作爲計數器使用,一般來講將其初始值設置爲 0,先調用 ReleaseSemaphore 來增加其計數,然後使用 WaitForSingleObject 來減小其計數,遺憾的是通常我們都不能得到信號燈的當前值,但是可以通過設置 WaitForSingleObject 的等待時間爲 0 來檢查信號燈當前是否爲 0。

事件

事件對象用於通知其他進程/線程某件操作已經完成方面的作用是很大的,而且如果有的任務要在進程尖進行協調採用等待其他進程中線程結束的方式是不可能實現的
事件對象可以一兩種方式創建,一種爲自動重置,在其他線程使用 WaitForSingleObject 等待到事件對象變爲有信號後該事件對象自動又變爲無信號狀態,一種爲人工重置在其他線程使用 WaitForSingleObject 等待到事件對象變爲有信號後該事件對象狀態不變。例如有多個線程都在等待一個線程運行結束,我們就可以使用人工重置事件,在被等待的線程結束時設置該事件爲有信號狀態,這樣其他的多個線程對該事件的等待都會成功(因爲該事件的狀態不會被自動重置)。事件相關的 API 如下:

創建事件對象:
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,    // 安全屬性,NULL表示使用默認的安全描述
  BOOL bManualReset,               // 是否爲人工重置
  BOOL bInitialState,                   // 初始狀態是否爲有信號狀態
  LPCTSTR lpName                    // 名字
);
打開事件對象:
HANDLE OpenEvent(
  DWORD dwDesiredAccess,     // 存取方式
  BOOL bInheritHandle,             // 是否能夠被繼承
  LPCTSTR lpName                   // 名字
);
設置事件爲無信號狀態:
BOOL ResetEvent(
  HANDLE hEvent          // 句柄
);
設置事件有無信號狀態:
BOOL SetEvent(
  HANDLE hEvent          // 句柄
);
關閉事件對象:
BOOL CloseHandle(
  HANDLE hObject         // 句柄
);

在MFC中對於各種同步對象都提供了相對應的類

在這些類中封裝了上面介紹的對象創建,打開,控制,刪除功能。但是如果要使用等待功能則需要使用另外兩個類:CSingleLock 和 CMultiLock。這兩個類中封裝了 WaitForSingleObject 和 WaitForMultipleObjects 函數。如果大家覺的需要可以看看這些類的定義,我想通過上面的介紹可以很容易理解,但是在對象同步問題上我覺得使用 API 函數比使用 MFC 類更爲直觀和方便。

發佈了41 篇原創文章 · 獲贊 5 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章