關於線程和進程間的同步

來源:互聯網
上載者:User

 臨界區

臨界區是一種最簡單的同步對象,它只可以在同一進程內部使用。它的作用是保證只有一個線程可以申請到該對象
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 : %d/n",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("/nover/n");
}

 

互斥量

 

互斥量與臨界區的作用非常相似,但互斥量是可以命名的,也就是說它可以跨越進程使用。所以建立互斥量需要的資源更多,所以如果只為了在進程內部是用的話使用臨界區會帶來速度上的優勢並能夠減少資源佔用量。因為互斥量是跨進程的互斥量一旦被建立,就可以通過名字開啟它
建立互斥量:
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 object/n",iID);
  WaitForSingleObject(hCounterIn,INFINITE);
  int iCopy=iCounter;
  Sleep(100);
  iCounter=iCopy+1;
  printf("/t/tthread %d : %d/n",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 object/n",iID);
  WaitForSingleObject(hCounterIn,INFINITE);
  printf("/t/tthread %d : do database access call/n",iID);
  Sleep(100);
  printf("/t/tthread %d : do database access call end/n",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 類更為直觀和方便。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.