1. 程式人生 > >C++中四種執行緒同步的方法

C++中四種執行緒同步的方法

現在流行的程序執行緒同步互斥的控制機制,其實是由最原始最基本的4種方法實現的。由這4種方法組合優化就有了.Net和Java下靈活多變的,程式設計簡便的執行緒程序控制手段。  
  這4種方法具體定義如下 在《作業系統教程》ISBN 7-5053-6193-7 一書中能夠找到更加周詳的解釋  
  
  1臨界區:通過對多執行緒的序列化來訪問公共資源或一段程式碼,速度快,適合控制資料訪問。  
  
  2互斥量:為協調一起對一個共享資源的單獨訪問而設計的。  
  
  3訊號量:為控制一個具備有限數量使用者資源而設計。  
  
  4事 件:用來通知執行緒有一些事件已發生,從而啟動後繼任務的開始。  
  

  臨界區(Critical Section)  
  
  確保在某一時刻只有一個執行緒能訪問資料的簡便辦法。在任意時刻只允許一個執行緒對共享資源進行訪問。假如有多個執行緒試圖同時訪問臨界區,那麼在有一個執行緒進入後其他任何試圖訪問此臨界區的執行緒將被掛起,並一直持續到進入臨界區的執行緒離開。臨界區在被釋放後,其他執行緒能夠繼續搶佔,並以此達到用原子方式操作共享資源的目的。  
  
  臨界區包含兩個操作原語: EnterCriticalSection() 進入臨界區 LeaveCriticalSection() 離開臨界區  
  
  EnterCriticalSection()語句執行後代碼將進入臨界區以後無論發生什麼,必須確保和之匹配的LeaveCriticalSection()都能夠被執行到。否則臨界區保護的共享資源將永遠不會被釋放。雖然臨界區同步速度很快,但卻只能用來同步本程序內的執行緒,而不可用來同步多個程序中的執行緒。  
  
  MFC提供了很多功能完備的類,我用MFC實現了臨界區。MFC為臨界區提供有一個CCriticalSection類,使用該類進行執行緒同步處理是很簡單的。只需線上程函式中用CCriticalSection類成員函式Lock()和UnLock()標定出被保護程式碼片段即可。Lock()後代碼用到的資源自動被視為臨界區內的資源被保護。UnLock後別的執行緒才能訪問這些資源。  
  
程式碼:   
//CriticalSection   
CCriticalSection global_CriticalSection;   
  
// 共享資源   
char global_Array[256];   
  
//初始化共享資源   
void InitializeArray()   
{   
for(int i = 0;i<256;i )   
{   
global_Array[i]=I;   
}   
}   
  
//寫執行緒   
UINT Global_ThreadWrite(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;   
ptr->SetWindowText("");   
//進入臨界區   
global_CriticalSection.Lock();   
for(int i = 0;i<256;i )   
{   
global_Array[i]=W;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}  
  
 //離開臨界區   
global_CriticalSection.Unlock();   
return 0;   
}   
  
//刪除執行緒   
UINT Global_ThreadDelete(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;   
ptr->SetWindowText("");   
//進入臨界區   
global_CriticalSection.Lock();   
for(int i = 0;i<256;i )   
{   
global_Array[i]=D;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}   
  
 //離開臨界區   
global_CriticalSection.Unlock();   
return 0;   
}   
  
//建立執行緒並啟動執行緒   
void CCriticalSectionsDlg::OnBnClickedButtonLock()   
{   
//Start the first Thread   
CWinThread *ptrWrite = AfxBeginThread(Global_ThreadWrite,   
&m_Write,   
THREAD_PRIORITY_NORMAL,   
0,   
CREATE_SUSPENDED);   
ptrWrite->ResumeThread();   
    
//Start the second Thread   
CWinThread *ptrDelete = AfxBeginThread(Global_ThreadDelete,   
&m_Delete,   
THREAD_PRIORITY_NORMAL,   
0,   
CREATE_SUSPENDED);   
ptrDelete->ResumeThread();   
}  


  
  
  在測試程式中,Lock UnLock兩個按鈕分別實現,在有臨界區保護共享資源的執行狀態,和沒有臨界區保護共享資源的執行狀態。  
  
互斥量(Mutex)  
  
  互斥量跟臨界區很相似,只有擁有互斥物件的執行緒才具備訪問資源的許可權,由於互斥物件只有一個,因此就決定了任何情況下此共享資源都不會同時被多個執行緒所訪問。當前佔據資源的執行緒在任務處理完後應將擁有的互斥物件交出,以便其他執行緒在獲得後得以訪問資源。互斥量比臨界區複雜。因為使用互斥不但僅能夠在同一應用程式不同執行緒中實現資源的安全共享,而且能夠在不同應用程式的執行緒之間實現對資源的安全共享。  
  
  互斥量包含的幾個操作原語:   
  CreateMutex() 建立一個互斥量   
  OpenMutex() 開啟一個互斥量   
  ReleaseMutex() 釋放互斥量   
  WaitForMultipleObjects() 等待互斥量物件  
  
  同樣MFC為互斥量提供有一個CMutex類。使用CMutex類實現互斥量操作很簡單,但是要特別注意對CMutex的建構函式的呼叫   
     
  CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)  
  
  不用的引數不能亂填,亂填會出現一些意想不到的執行結果。  
  
程式碼:   
//建立互斥量   
CMutex global_Mutex(0,0,0);   
  
// 共享資源   
char global_Array[256];   
  
void InitializeArray()   
{   
for(int i = 0;i<256;i )   
{   
global_Array[i]=I;   
}   
}   
UINT Global_ThreadWrite(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;  
  
ptr->SetWindowText("");   
global_Mutex.Lock();   
for(int i = 0;i<256;i )   
{   
global_Array[i]=W;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}   
global_Mutex.Unlock();   
return 0;   
}   
  
UINT Global_ThreadDelete(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;   
ptr->SetWindowText("");   
global_Mutex.Lock();   
for(int i = 0;i<256;i )   
{   
global_Array[i]=D;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}   
global_Mutex.Unlock();   
return 0;   
}  


  
  
  同樣在測試程式中,Lock UnLock兩個按鈕分別實現,在有互斥量保護共享資源的執行狀態,和沒有互斥量保護共享資源的執行狀態。  
  
   
 訊號量(Semaphores)  
  
  訊號量物件對執行緒的同步方式和前面幾種方法不同,訊號允許多個執行緒同時使用共享資源,這和作業系統中的PV操作相同。他指出了同時訪問共享資源的執行緒最大數目。他允許多個執行緒在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大執行緒數目。在用CreateSemaphore()建立訊號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數配置為最大資源計數,每增加一個執行緒對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就能夠發出訊號量訊號。但是當前可用計數減小到0時則說明當前佔用資源的執行緒數已達到了所允許的最大數目,不能在允許其他執行緒的進入,此時的訊號量訊號將無法發出。執行緒在處理完共享資源後,應在離開的同時通過ReleaseSemaphore()函式將當前可用資源計數加1。在任何時候當前可用資源計數決不可能大於最大資源計數。  
  
  PV操作及訊號量的概念都是由荷蘭科學家E.W.Dijkstra提出的。訊號量S是個整數,S大於等於零時代表可供併發程序使用的資源實體數,但S小於零時則表示正在等待使用共享資源的程序數。  
  
   P操作申請資源:   
  (1)S減1;   
  (2)若S減1後仍大於等於零,則程序繼續執行;   
  (3)若S減1後小於零,則該程序被阻塞後進入和該訊號相對應的佇列中,然後轉入程序排程。   
     
  V操作 釋放資源:   
  (1)S加1;   
  (2)若相加結果大於零,則程序繼續執行;   
  (3)若相加結果小於等於零,則從該訊號的等待佇列中喚醒一個等待程序,然後再返回原程序繼續執行或轉入程序排程。  
  
  訊號量包含的幾個操作原語:   
  CreateSemaphore() 建立一個訊號量   
  OpenSemaphore() 開啟一個訊號量   
  ReleaseSemaphore() 釋放訊號量   
  WaitForSingleObject() 等待訊號量  
  
程式碼:   
//訊號量控制代碼   
HANDLE global_Semephore;   
  
// 共享資源   
char global_Array[256];   
void InitializeArray()   
{   
for(int i = 0;i<256;i )   
{   
global_Array[i]=I;   
}   
}   
  
 //執行緒1   
UINT Global_ThreadOne(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;   
ptr->SetWindowText("");   
//等待對共享資源請求被通過 等於 P操作   
WaitForSingleObject(global_Semephore, INFINITE);   
for(int i = 0;i<256;i )   
{   
global_Array[i]=O;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}   
  
//釋放共享資源 等於 V操作   
ReleaseSemaphore(global_Semephore, 1, NULL);   
return 0;   
}   
  
UINT Global_ThreadTwo(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;   
ptr->SetWindowText("");   
WaitForSingleObject(global_Semephore, INFINITE);   
for(int i = 0;i<256;i )   
{   
global_Array[i]=T;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}   
ReleaseSemaphore(global_Semephore, 1, NULL);   
return 0;   
}   
  
UINT Global_ThreadThree(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;   
ptr->SetWindowText("");   
WaitForSingleObject(global_Semephore, INFINITE);   
for(int i = 0;i<256;i )   
{   
global_Array[i]=H;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}   
ReleaseSemaphore(global_Semephore, 1, NULL);   
return 0;   
}   
  
void CSemaphoreDlg::OnBnClickedButtonOne()   
{  
  
//配置訊號量 1 個資源 1同時只能夠有一個執行緒訪問   
global_Semephore= CreateSemaphore(NULL, 1, 1, NULL);   
this->StartThread();   
  
// TODO: Add your control notification handler code here   
}   
  
void CSemaphoreDlg::OnBnClickedButtonTwo()   
{  
  
//配置訊號量 2 個資源 2 同時只能夠有兩個執行緒訪問   
global_Semephore= CreateSemaphore(NULL, 2, 2, NULL);   
this->StartThread();  
  
// TODO: Add your control notification handler code here   
}   
  
void CSemaphoreDlg::OnBnClickedButtonThree()   
{  
  
//配置訊號量 3 個資源 3 同時只能夠有三個執行緒訪問   
global_Semephore= CreateSemaphore(NULL, 3, 3, NULL);   
this->StartThread();   
  
// TODO: Add your control notification handler code here   
}  


  
  
  訊號量的使用特點使其更適用於對Socket(套接字)程式中執行緒的同步。例如,網路上的HTTP伺服器要對同一時間內訪問同一頁面的使用者數加以限制,這時能夠為每一個使用者對伺服器的頁面請求配置一個執行緒,而頁面則是待保護的共享資源,通過使用訊號量對執行緒的同步作用能夠確保在任一時刻無論有多少使用者對某一頁面進行訪問,只有不大於設定的最大使用者數目的執行緒能夠進行訪問,而其他的訪問企圖則被掛起,只有在有使用者退出對此頁面的訪問後才有可能進入。  
  
事件(Event)  
  
  事件物件也能夠通過通知操作的方式來保持執行緒的同步。並且能夠實現不同程序中的執行緒同步操作。  
  
  事件包含的幾個操作原語:   
  CreateEvent() 建立一個事件   
  OpenEvent() 開啟一個事件   
  SetEvent() 回置事件   
  WaitForSingleObject() 等待一個事件   
  WaitForMultipleObjects() 等待多個事件  
  
  WaitForMultipleObjects 函式原型:   
  WaitForMultipleObjects(   
  IN DWORD nCount, // 等待控制代碼數   
  IN CONST HANDLE *lpHandles, //指向控制代碼陣列   
  IN BOOL bWaitAll, //是否完全等待標誌   
  IN DWORD dwMilliseconds //等待時間   
  )  
  
  引數nCount指定了要等待的核心物件的數目,存放這些核心物件的陣列由lpHandles來指向。fWaitAll對指定的這nCount個核心物件的兩種等待方式進行了指定,為TRUE時當任何物件都被通知時函式才會返回,為FALSE則只要其中任何一個得到通知就能夠返回。dwMilliseconds在這裡的作用和在WaitForSingleObject()中的作用是完全一致的。假如等待超時,函式將返回WAIT_TIMEOUT。  
  
程式碼:   
//事件陣列   
HANDLE global_Events[2];   
  
// 共享資源   
char global_Array[256];   
  
void InitializeArray()   
{   
for(int i = 0;i<256;i )   
{   
global_Array[i]=I;   
}   
}   
  
UINT Global_ThreadOne(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;   
ptr->SetWindowText("");   
for(int i = 0;i<256;i )   
{   
global_Array[i]=O;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}   
  
//回置事件   
SetEvent(global_Events[0]);   
return 0;   
}   
  
UINT Global_ThreadTwo(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;   
ptr->SetWindowText("");   
for(int i = 0;i<256;i )   
{   
global_Array[i]=T;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}   
  
//回置事件   
SetEvent(global_Events[1]);   
return 0;   
}   
  
UINT Global_ThreadThree(LPVOID pParam)   
{   
CEdit *ptr=(CEdit *)pParam;   
ptr->SetWindowText("");   
  
//等待兩個事件都被回置   
WaitForMultipleObjects(2, global_Events, true, INFINITE);   
for(int i = 0;i<256;i )   
{   
global_Array[i]=H;   
ptr->SetWindowText(global_Array);   
Sleep(10);   
}   
return 0;   
}   
void CEventDlg::OnBnClickedButtonStart()   
{   
for (int i = 0; i < 2; i )   
{   
  
//例項化事件   
global_Events[i]=CreateEvent(NULL,false,false,NULL);   
}   
CWinThread *ptrOne = AfxBeginThread(Global_ThreadOne,   
&m_One,   
THREAD_PRIORITY_NORMAL,   
0,   
CREATE_SUSPENDED);   
ptrOne->ResumeThread();   
  
//Start the second Thread   
CWinThread *ptrTwo = AfxBeginThread(Global_ThreadTwo,   
&m_Two,   
THREAD_PRIORITY_NORMAL,   
0,   
CREATE_SUSPENDED);   
ptrTwo->ResumeThread();   
  
//Start the Third Thread   
CWinThread *ptrThree = AfxBeginThread(Global_ThreadThree,   
&m_Three,   
THREAD_PRIORITY_NORMAL,   
0,   
CREATE_SUSPENDED);   
ptrThree->ResumeThread();  
  
// TODO: Add your control notification handler code here   
}  


  
  
  事件能夠實現不同程序中的執行緒同步操作,並且能夠方便的實現多個執行緒的優先比較等待操作,例如寫多個WaitForSingleObject來代替WaitForMultipleObjects從而使程式設計更加靈活。   
總結:  
  
  1. 互斥量和臨界區的作用很相似,但互斥量是能夠命名的,也就是說他能夠跨越程序使用。所以建立互斥量需要的資源更多,所以假如只為了在程序內部是用的話使用臨界區會帶來速度上的優勢並能夠減少資源佔用量。因為互斥量是跨程序的互斥量一旦被建立,就能夠通過名字開啟他。  
  
  2. 互斥量(Mutex),訊號燈(Semaphore),事件(Event)都能夠被跨越程序使用來進行同步資料操作,而其他的物件和資料同步操作無關,但對於程序和執行緒來講,假如程序和執行緒在執行狀態則為無訊號狀態,在退出後為有訊號狀態。所以能夠使用WaitForSingleObject來等待程序和執行緒退出。  
  
  3. 通過互斥量能夠指定資源被獨佔的方式使用,但假如有下面一種情況通過互斥量就無法處理,比如現在一位使用者購買了一份三個併發訪問許可的資料庫系統,能夠根據使用者購買的訪問許可數量來決定有多少個執行緒/程序能同時進行資料庫操作,這時候假如利用互斥量就沒有辦法完成這個需要,訊號燈物件能夠說是一種資源計數器。 

相關推薦

C++執行同步方法

現在流行的程序執行緒同步互斥的控制機制,其實是由最原始最基本的4種方法實現的。由這4種方法組合優化就有了.Net和Java下靈活多變的,程式設計簡便的執行緒程序控制手段。     這4種方法具體定義如下 在《作業系統教程》ISBN 7-5053-6193-7 一書中能夠找到

執行同步(或互斥)方式小結

一,什麼是執行緒同步和互斥 同步就是協同步調,按預定的先後次序進行執行。如:你說完,我再說。這裡的同步千萬不要理解成那個同時進行,應是指協同、協助、互相配合。執行緒同步是指多執行緒通過特定的設定(如互

Java併發程式設計執行池及自定義執行使用教程

引言 通過前面的文章,我們學習了Executor框架中的核心類ThreadPoolExecutor ,對於執行緒池的核心排程機制有了一定的瞭解,並且成功使用ThreadPoolExecutor 建立了執行緒池。 而在Java中,除了ThreadPoolExecutor ,Executor框

java執行池的區別

本文按: 一. 執行緒池的使用 二. 幾種執行緒池的區別 三. 如何合理配置執行緒池 一.執行緒池的使用 在Java中,通常使用Executors 獲取執行緒池。常用的執行緒池有以下幾種: (1)CachedThreadPool (2)FixedThreadPo

C# 關閉當前執行方式

從查MSDN和從網上查資料可以知道,Dispose()方法,雖然能釋放當前窗體的資源,卻不能強制結束迴圈, 要想強制突出當前程式要用:System.Environment.Exit(int exitcode)方法;該方法:終止當前程序併為基礎作業系統提供指定的退出程式碼。 如下則問題解決: private v

C++進程或線程同步互斥的控制方法

實現 適合 begin 執行 als 零基礎 time 並發 多少 分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!http://www.captainbed.net 現在流行的進程線程同步互斥的控制機

C++11併發學習之執行同步(續)

有時候,在第一個執行緒完成前,可能需要等待另一個執行緒執行完成。C++標準庫提供了一些工具可用於這種同步操作,形式上表現為條件變數(condition variable)和期望(future)。 一.條件變數(condition variable) C++標準庫對條件變數有兩套實現:std::c

淺析Java執行

1.使用執行緒池的好處    2.JUC中幾種常用的執行緒池 java.util.concurrent包下的Executors工廠類,提供了一系列的執行緒池的建立方法,其構造方法如下: public ThreadPoolExecutor(int corePoolSize,   

java常見的執行池的區別

在使用執行緒時,可以這樣建立一個執行緒池: ExecutorService executorService= Executors.newCachedThreadPool(); executorService.execute(()-> System.out.println("run ..

【收藏】C#的多執行——執行同步基礎

第二部分:執行緒同步基礎 同步要領 下面的表格列展了.NET對協調或同步執行緒動作的可用的工具: 簡易阻止方法 構成 目的 Sleep 阻止給定的時間週期 Join 等待另一個執行緒完成 鎖系統 構成 目的 跨程序?

Java執行池使用方法

1.new Thread的弊端執行一個非同步任務你還只是如下new Thread嗎?1234567new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stub

Java常用的執行

在Java中使用執行緒池,可以用ThreadPoolExecutor的建構函式直接創建出執行緒池例項,如何使用參見之前的文章Java執行緒池構造引數詳解。不過,在Executors類中,為我們提供了常用執行緒池的建立方法。接下來我們就來了解常用的四種: newFixedThreadPool 首先,看一下這種執

詳細分析 Java 實現多執行方法有幾?(從本質上出發)

[TOC] # 詳細分析 Java 中實現多執行緒的方法有幾種?(從本質上出發) ## 正確的說法(從本質上出發) - **實現多執行緒的官方正確方法: 2 種。** - Oracle 官網的文件說明 ![在這裡插入圖片描述](https://img-blog.csdnimg

android的執行

四種執行緒池內部構造都是來自同一個方法:  public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,

Java執行池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingle

轉自:https://www.cnblogs.com/baizhanshi/p/5469948.html 1、new Thread的弊端 執行一個非同步任務你還只是如下new Thread嗎? Java   1 2 3 4 5 6 7

執行(十執行同步技術Semaphore

理解: Semaphore通常用於限制可以訪問某些資源(物理或邏輯的)的執行緒數目,我們可以自己設定最大訪問量。它有兩個很常用的方法是acquire()和release(),分別是獲得許可和釋放許可。  借用武哥的理解: Semaphore相當於一個廁所,我在造的時候可以想

Java 執行池 - 使用

轉:https://www.cnblogs.com/zhujiabin/p/5404771.html 介紹new Thread的弊端及Java四種執行緒池的使用,對Android同樣適用。本文是基礎篇,後面會分享下執行緒池一些高階功能。 1、new Thread的弊端 執行一個非同步任務你還

Executors提供的執行

Java 5+中的Executor介面定義一個執行執行緒的工具。它的子型別即執行緒池介面是ExecutorService。要配置一個執行緒池是比較複雜的,尤其是對於執行緒池的原理不是很清楚的情況下,因此在工具類Executors面提供了一些靜態工廠方法,生成一些常用的執行緒池,如下所示:  -&n

2.3執行連線池的配置和使用(和自定義執行池)

四種執行緒連線池的配置和使用 最終呼叫類和方法 {引數有 核心執行緒數目,最大執行緒數目,存活時間(當前執行緒執行完這個任務之後,等待下一個任務到來的最長等待時間。如果在這個時間內沒有新的任務來到,那當前執行緒就會退出),時間單位,等待佇列(用於存放待執行的任務)} public

2.2執行連線池的作用

四種執行緒連線池的作用及其使用 執行緒池的作用: 執行緒池作用就是限制系統中執行執行緒的數量。 根據系統的環境情況,可以自動或手動設定執行緒數量,達到執行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用執行緒池控制執行緒數量,其他執行緒排 隊等候。一個任務執行完畢,再