MFC多線程編程之四——線程的同步

8、線程的同步php

雖然多線程能給咱們帶來好處,可是也有很多問題須要解決。例如,對於像磁盤驅動器這樣獨佔性系統資源,因爲線程能夠執行進程的任何代碼段,且線程的運行是由系統調度自動完成的,具備必定的不肯定性,所以就有可能出現兩個線程同時對磁盤驅動器進行操做,從而出現操做錯誤;又例如,對於銀行系統的計算機來講,可能使用一個線程來更新其用戶數據庫,而用另一個線程來讀取數據庫以響應儲戶的須要,極有可能讀數據庫的線程讀取的是未徹底更新的數據庫,由於可能在讀的時候只有一部分數據被更新過。數據庫

使隸屬於同一進程的各線程協調一致地工做稱爲線程的同步。MFC提供了多種同步對象,下面咱們只介紹最經常使用的四種:編程

臨界區(CCriticalSection)數組

事件(CEvent)網絡

互斥量(CMutex)多線程

信號量(CSemaphore)函數

經過這些類,咱們能夠比較容易地作到線程同步。.net

A、使用 CCriticalSection 類線程

當多個線程訪問一個獨佔性共享資源時,可使用「臨界區」對象。任一時刻只有一個線程能夠擁有臨界區對象,擁有臨界區的線程能夠訪問被保護起來的資源或代碼段,其餘但願進入臨界區的線程將被掛起等待,直到擁有臨界區的線程放棄臨界區時爲止,這樣就保證了不會在同一時刻出現多個線程訪問共享資源。code

CCriticalSection類的用法很是簡單,步驟以下:

定義CCriticalSection類的一個全局對象(以使各個線程均能訪問),如CCriticalSection critical_section;

在訪問須要保護的資源或代碼以前,調用CCriticalSection類的成員Lock()得到臨界區對象:

1.critical_section.Lock();

在線程中調用該函數來使線程得到它所請求的臨界區。若是此時沒有其它線程佔有臨界區對象,則調用Lock()的線程得到臨界區;不然,線程將被掛起,並放入到一個系統隊列中等待,直到當前擁有臨界區的線程釋放了臨界區時爲止。

訪問臨界區完畢後,使用CCriticalSection的成員函數Unlock()來釋放臨界區:

1.critical_section.Unlock();

再通俗一點講,就是線程A執行到critical_section.Lock();語句時,若是其它線程(B)正在執行critical_section.Lock();語句後且critical_section. Unlock();語句前的語句時,線程A就會等待,直到線程B執行完critical_section. Unlock();語句,線程A纔會繼續執行。

下面再經過一個實例進行演示說明。

例程8 MultiThread8

創建一個基於對話框的工程MultiThread8,在對話框IDD_MULTITHREAD8_DIALOG中加入兩個按鈕和兩個編輯框控件,兩個按鈕的ID分別爲IDC_WRITEW和IDC_WRITED,標題分別爲「寫‘W’」和「寫‘D’」;兩個編輯框的ID分別爲IDC_W和IDC_D,屬性都選中Read-only;

在MultiThread8Dlg.h文件中聲明兩個線程函數:

1.UINT WriteW(LPVOID pParam);

2.UINT WriteD(LPVOID pParam);

使用ClassWizard分別給IDC_W和IDC_D添加CEdit類變量m_ctrlW和m_ctrlD;

在MultiThread8Dlg.cpp文件中添加以下內容:

爲了文件中可以正確使用同步類,在文件開頭添加:

1.#include "afxmt.h"

定義臨界區和一個字符數組,爲了可以在不一樣線程間使用,定義爲全局變量:

1.CCriticalSection critical_section;

2.char g_Array[10];

添加線程函數:

01.UINT WriteW(LPVOID pParam)

02.{

03.CEdit *pEdit=(CEdit*)pParam;

04.pEdit->SetWindowText("");

05.critical_section.Lock();

06.//鎖定臨界區,其它線程遇到critical_section.Lock();語句時要等待

07.//直至執行critical_section.Unlock();語句

08.for(int i=0;i<10;i++)

09.{

10.g_Array[i]=''W'';

11.pEdit->SetWindowText(g_Array);

12.Sleep(1000);

13.}

14.critical_section.Unlock();

15.return 0;

16. 

17.}

18. 

19.UINT WriteD(LPVOID pParam)

20.{

21.CEdit *pEdit=(CEdit*)pParam;

22.pEdit->SetWindowText("");

23.critical_section.Lock();

24.//鎖定臨界區,其它線程遇到critical_section.Lock();語句時要等待

25.//直至執行critical_section.Unlock();語句

26.for(int i=0;i<10;i++)

27.{

28.g_Array[i]=''D'';

29.pEdit->SetWindowText(g_Array);

30.Sleep(1000);

31.}

32.critical_section.Unlock();

33.return 0;

34. 

35.}

分別雙擊按鈕IDC_WRITEW和IDC_WRITED,添加其響應函數:

01.void CMultiThread8Dlg::OnWritew()

02.{

03.CWinThread *pWriteW=AfxBeginThread(WriteW,

04.&m_ctrlW,

05.THREAD_PRIORITY_NORMAL,

06.0,

07.CREATE_SUSPENDED);

08.pWriteW->ResumeThread();

09.}

10. 

11.void CMultiThread8Dlg::OnWrited()

12.{

13.CWinThread *pWriteD=AfxBeginThread(WriteD,

14.&m_ctrlD,

15.THREAD_PRIORITY_NORMAL,

16.0,

17.CREATE_SUSPENDED);

18.pWriteD->ResumeThread();

19. 

20.}

因爲代碼較簡單,再也不詳述。編譯、運行該例程,您能夠連續點擊兩個按鈕,觀察體會臨界類的做用。

B、使用 CEvent 類

CEvent 類提供了對事件的支持。事件是一個容許一個線程在某種狀況發生時,喚醒另一個線程的同步對象。例如在某些網絡應用程序中,一個線程(記爲A)負責監聽通信端口,另一個線程(記爲B)負責更新用戶數據。經過使用CEvent 類,線程A能夠通知線程B什麼時候更新用戶數據。每個CEvent 對象能夠有兩種狀態:有信號狀態和無信號狀態。線程監視位於其中的CEvent 類對象的狀態,並在相應的時候採起相應的操做。

在MFC中,CEvent 類對象有兩種類型:人工事件和自動事件。一個自動CEvent 對象在被至少一個線程釋放後會自動返回到無信號狀態;而人工事件對象得到信號後,釋放可利用線程,但直到調用成員函數ReSetEvent()纔將其設置爲無信號狀態。在建立CEvent 類的對象時,默認建立的是自動事件。 CEvent 類的各成員函數的原型和參數說明以下:

1.一、CEvent(BOOL bInitiallyOwn=FALSE,

2.BOOL bManualReset=FALSE,

3.LPCTSTR lpszName=NULL,

4.LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);

 

bInitiallyOwn:指定事件對象初始化狀態,TRUE爲有信號,FALSE爲無信號;

bManualReset:指定要建立的事件是屬於人工事件仍是自動事件。TRUE爲人工事件,FALSE爲自動事件;

後兩個參數通常設爲NULL,在此不做過多說明。

1.二、BOOL CEvent::SetEvent();

將 CEvent 類對象的狀態設置爲有信號狀態。若是事件是人工事件,則 CEvent 類對象保持爲有信號狀態,直到調用成員函數ResetEvent()將 其從新設爲無信號狀態時爲止。若是CEvent 類對象爲自動事件,則在SetEvent()將事件設置爲有信號狀態後,CEvent 類對象由系統自動重置爲無信號狀態。

若是該函數執行成功,則返回非零值,不然返回零。

1.三、BOOL CEvent::ResetEvent();

該函數將事件的狀態設置爲無信號狀態,並保持該狀態直至SetEvent()被調用時爲止。因爲自動事件是由系統自動重置,故自動事件不須要調用該函數。若是該函數執行成功,返回非零值,不然返回零。咱們通常經過調用WaitForSingleObject函數來監視事件狀態。前面咱們已經介紹了該函數。因爲語言描述的緣由,CEvent 類的理解確實有些難度,但您只要經過仔細玩味下面例程,多看幾遍就可理解。

例程9 MultiThread9

創建一個基於對話框的工程MultiThread9,在對話框IDD_MULTITHREAD9_DIALOG中加入一個按鈕和兩個編輯框控件,按鈕的ID爲IDC_WRITEW,標題爲「寫‘W’」;兩個編輯框的ID分別爲IDC_W和IDC_D,屬性都選中Read-only;

在MultiThread9Dlg.h文件中聲明兩個線程函數:

1.UINT WriteW(LPVOID pParam);

2.UINT WriteD(LPVOID pParam);

使用ClassWizard分別給IDC_W和IDC_D添加CEdit類變量m_ctrlW和m_ctrlD;

在MultiThread9Dlg.cpp文件中添加以下內容:

爲了文件中可以正確使用同步類,在文件開頭添加

1.#include "afxmt.h"

定義事件對象和一個字符數組,爲了可以在不一樣線程間使用,定義爲全局變量。

1.CEvent eventWriteD;

2.char g_Array[10];

添加線程函數:

01.UINT WriteW(LPVOID pParam)

02.{

03.CEdit *pEdit=(CEdit*)pParam;

04.pEdit->SetWindowText("");

05.for(int i=0;i<10;i++)

06.{

07.g_Array[i]=''W'';

08.pEdit->SetWindowText(g_Array);

09.Sleep(1000);

10.}

11.eventWriteD.SetEvent();

12.return 0;

13. 

14.}

15.UINT WriteD(LPVOID pParam)

16.{

17.CEdit *pEdit=(CEdit*)pParam;

18.pEdit->SetWindowText("");

19.WaitForSingleObject(eventWriteD.m_hObject,INFINITE);

20.for(int i=0;i<10;i++)

21.{

22.g_Array[i]=''D'';

23.pEdit->SetWindowText(g_Array);

24.Sleep(1000);

25.}

26.return 0;

27. 

28.}

仔細分析這兩個線程函數, 您就會正確理解CEvent 類。線程WriteD執行到 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);處等待,直到事件eventWriteD爲有信號該線程才往下執行,由於eventWriteD對象是自動事件,則當WaitForSingleObject()返回時,系統自動把eventWriteD對象重置爲無信號狀態。

雙擊按鈕IDC_WRITEW,添加其響應函數:

01.void CMultiThread9Dlg::OnWritew()

02.{

03.CWinThread *pWriteW=AfxBeginThread(WriteW,

04.&m_ctrlW,

05.THREAD_PRIORITY_NORMAL,

06.0,

07.CREATE_SUSPENDED);

08.pWriteW->ResumeThread();

09. 

10.CWinThread *pWriteD=AfxBeginThread(WriteD,

11.&m_ctrlD,

12.THREAD_PRIORITY_NORMAL,

13.0,

14.CREATE_SUSPENDED);

15.pWriteD->ResumeThread();

16. 

17.}

編譯並運行程序,單擊「寫‘W’」按鈕,體會事件對象的做用。

C、使用CMutex 類

互斥對象與臨界區對象很像.互斥對象與臨界區對象的不一樣在於:互斥對象能夠在進程間使用,而臨界區對象只能在同一進程的各線程間使用。固然,互斥對象也能夠用於同一進程的各個線程間,可是在這種狀況下,使用臨界區會更節省系統資源,更有效率。

D、使用CSemaphore 類

當須要一個計數器來限制可使用某個線程的數目時,可使用「信號量」對象。CSemaphore 類的對象保存了對當前訪問某一指定資源的線程的計數值,該計數值是當前還可使用該資源的線程的數目。若是這個計數達到了零,則全部對這個CSemaphore 類對象所控制的資源的訪問嘗試都被放入到一個隊列中等待,直到超時或計數值不爲零時爲止。一個線程被釋放已訪問了被保護的資源時,計數值減1;一個線程完成了對被控共享資源的訪問時,計數值增1。這個被CSemaphore 類對象所控制的資源能夠同時接受訪問的最大線程數在該對象的構建函數中指定。

CSemaphore 類的構造函數原型及參數說明以下:

1.CSemaphore (LONG lInitialCount=1,

2.LONG lMaxCount=1,

3.LPCTSTR pstrName=NULL,

4.LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);

lInitialCount:信號量對象的初始計數值,便可訪問線程數目的初始值;

lMaxCount:信號量對象計數值的最大值,該參數決定了同一時刻可訪問由信號量保護的資源的線程最大數目;

後兩個參數在同一進程中使用通常爲NULL,不做過多討論;

在用CSemaphore 類的構造函數建立信號量對象時要同時指出容許的最大資源計數和當前可用資源計數。通常是將當前可用資源計數設置爲最大資源計數,每增長一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就能夠發出信號量信號。可是當前可用計數減少到0時,則說明當前佔用資源的線程數已經達到了所容許的最大數目,不能再容許其它線程的進入,此時的信號量信號將沒法發出。線程在處理完共享資源後,應在離開的同時經過ReleaseSemaphore()函數將當前可用資源數加1。

下面給出一個簡單實例來講明 CSemaphore 類的用法。

例程10 MultiThread10

創建一個基於對話框的工程MultiThread10,在對話框IDD_MULTITHREAD10_DIALOG中加入一個按鈕和三個編輯框控件,按鈕的ID爲IDC_START,標題爲「同時寫‘A’、‘B’、‘C’」;三個編輯框的ID分別爲IDC_A、IDC_B和IDC_C,屬性都選中Read-only;

在MultiThread10Dlg.h文件中聲明兩個線程函數:

1.UINT WriteA(LPVOID pParam);

2.UINT WriteB(LPVOID pParam);

3.UINT WriteC(LPVOID pParam);

使用ClassWizard分別給IDC_A、IDC_B和IDC_C添加CEdit類變量m_ctrlA、m_ctrlB和m_ctrlC;

在MultiThread10Dlg.cpp文件中添加以下內容:

爲了文件中可以正確使用同步類,在文件開頭添加:

1.#include "afxmt.h"

定義信號量對象和一個字符數組,爲了可以在不一樣線程間使用,定義爲全局變量:

1.CSemaphore semaphoreWrite(2,2); //資源最多訪問線程2個,當前可訪問線程數2個

2.char g_Array[10];

添加三個線程函數:

01.UINT WriteA(LPVOID pParam)

02.{

03.CEdit *pEdit=(CEdit*)pParam;

04.pEdit->SetWindowText("");

05.WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);

06.CString str;

07.for(int i=0;i<10;i++)

08.{

09.pEdit->GetWindowText(str);

10.g_Array[i]=''A'';

11.str=str+g_Array[i];

12.pEdit->SetWindowText(str);

13.Sleep(1000);

14.}

15.ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);

16.return 0;

17. 

18.}

19.UINT WriteB(LPVOID pParam)

20.{

21.CEdit *pEdit=(CEdit*)pParam;

22.pEdit->SetWindowText("");

23.WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);

24.CString str;

25.for(int i=0;i<10;i++)

26.{

27. 

28.pEdit->GetWindowText(str);

29.g_Array[i]=''B'';

30.str=str+g_Array[i];

31.pEdit->SetWindowText(str);

32.Sleep(1000);

33.}

34.ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);

35.return 0;

36. 

37.}

38.UINT WriteC(LPVOID pParam)

39.{

40.CEdit *pEdit=(CEdit*)pParam;

41.pEdit->SetWindowText("");

42.WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);

43.for(int i=0;i<10;i++)

44.{

45.g_Array[i]=''C'';

46.pEdit->SetWindowText(g_Array);

47.Sleep(1000);

48.}

49.ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);

50.return 0;

51. 

52.}

這三個線程函數再也不多說。在信號量對象有信號的狀態下,線程執行到WaitForSingleObject語句處繼續執行,同時可用線程數減1;若線程執行到WaitForSingleObject語句時信號量對象無信號,線程就在這裏等待,直到信號量對象有信號線程才往下執行。

雙擊按鈕IDC_START,添加其響應函數:

01.void CMultiThread10Dlg::OnStart()

02.{

03.CWinThread *pWriteA=AfxBeginThread(WriteA,

04.&m_ctrlA,

05.THREAD_PRIORITY_NORMAL,

06.0,

07.CREATE_SUSPENDED);

08.pWriteA->ResumeThread();

09. 

10.CWinThread *pWriteB=AfxBeginThread(WriteB,

11.&m_ctrlB,

12.THREAD_PRIORITY_NORMAL,

13.0,

14.CREATE_SUSPENDED);

15.pWriteB->ResumeThread();

16. 

17.CWinThread *pWriteC=AfxBeginThread(WriteC,

18.&m_ctrlC,

19.THREAD_PRIORITY_NORMAL,

20.0,

21.CREATE_SUSPENDED);

22.pWriteC->ResumeThread();

23. 

24. 

25.}

好吧,多線程編程就介紹到這裏,但願本文能對您有所幫助。

 

 

 

原文地址:http://www.vckbase.com/index.php/wv/1416

本文示例源碼下載地址:http://download.csdn.net/detail/arcsinsin/6578409

本文關聯的其它文章

 

MFC多線程編程之一———問題提出及多線程編程簡介

MFC多線程編程之三——線程間通信

MFC多線程編程之四——線程的同步  

相關文章
相關標籤/搜索