<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 134,comments - 22,trackbacks - 0
    簡介

      線程之間通信的兩個(gè)基本問題是互斥和同步。

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

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

      線程互斥是一種特殊的線程同步。

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

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

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

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

      (1)事件(Event);

      (2)信號量(semaphore);

      (3)互斥量(mutex);

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

      全局變量

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

    int var; //全局變量
    UINT ThreadFunction(LPVOIDpParam)
    {
     var = 0;
     while (var < MaxValue)
     {
      //線程處理
      ::InterlockedIncrement(long*) &var);
     }
     return 0;
    }
    請看下列程序:
    int globalFlag = false;
    DWORD WINAPI ThreadFunc(LPVOID n)
    {
     Sleep(2000);
     globalFlag = true;

     return 0;
    }

    int main()
    {
     HANDLE hThrd;
     DWORD threadId;

     hThrd = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &threadId);
     if (hThrd)
     {
      printf("Thread launched\n");
      CloseHandle(hThrd);
     }

     while (!globalFlag)
     ;
     printf("exit\n");
    }

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

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

      (2)當(dāng)主線程的優(yōu)先級高于執(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)變遷方式的不同,事件可分為兩類:

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

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

      創(chuàng)建事件的函數(shù)原型為:

    HANDLE CreateEvent(
     LPSECURITY_ATTRIBUTES lpEventAttributes,
     // SECURITY_ATTRIBUTES結(jié)構(gòu)指針,可為NULL
     BOOL bManualReset,
     // 手動(dòng)/自動(dòng)
     // TRUE:在WaitForSingleObject后必須手動(dòng)調(diào)用ResetEvent清除信號
     // FALSE:在WaitForSingleObject后,系統(tǒng)自動(dòng)清除事件信號
     BOOL bInitialState, //初始狀態(tài)
     LPCTSTR lpName //事件的名稱
    );

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

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

      (2)事件是否要自動(dòng)恢復(fù);

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

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

    HANDLE hEvent=OpenEvent(EVENT_ALL_ACCESS,true,"MyEvent");
    ResetEvent(hEvent);

    臨界區(qū)

      定義臨界區(qū)變量

    CRITICAL_SECTION gCriticalSection;

      通常情況下,CRITICAL_SECTION結(jié)構(gòu)體應(yīng)該被定義為全局變量,以便于進(jìn)程中的所有線程方便地按照變量名來引用該結(jié)構(gòu)體。

      初始化臨界區(qū)

    VOID WINAPI InitializeCriticalSection(
     LPCRITICAL_SECTION lpCriticalSection
     //指向程序員定義的CRITICAL_SECTION變量
    );

      該函數(shù)用于對pcs所指的CRITICAL_SECTION結(jié)構(gòu)體進(jìn)行初始化。該函數(shù)只是設(shè)置了一些成員變量,它的運(yùn)行一般不會(huì)失敗,因此它采用了VOID類型的返回值。該函數(shù)必須在任何線程調(diào)用EnterCriticalSection函數(shù)之前被調(diào)用,如果一個(gè)線程試圖進(jìn)入一個(gè)未初始化的CRTICAL_SECTION,那么結(jié)果將是很難預(yù)計(jì)的。

      刪除臨界區(qū)

    VOID WINAPI DeleteCriticalSection(
     LPCRITICAL_SECTION lpCriticalSection
     //指向一個(gè)不再需要的CRITICAL_SECTION變量
    );

      進(jìn)入臨界區(qū)

    VOID WINAPI EnterCriticalSection(
     LPCRITICAL_SECTION lpCriticalSection
     //指向一個(gè)你即將鎖定的CRITICAL_SECTION變量
    );

      離開臨界區(qū)

    VOID WINAPI LeaveCriticalSection(
     LPCRITICAL_SECTION lpCriticalSection
     //指向一個(gè)你即將離開的CRITICAL_SECTION變量
    );

      使用臨界區(qū)編程的一般方法是:

    void UpdateData()
    {
     EnterCriticalSection(&gCriticalSection);
     ...//do something
     LeaveCriticalSection(&gCriticalSection);
    }

      關(guān)于臨界區(qū)的使用,有下列注意點(diǎn):

      (1)每個(gè)共享資源使用一個(gè)CRITICAL_SECTION變量;

      (2)不要長時(shí)間運(yùn)行關(guān)鍵代碼段,當(dāng)一個(gè)關(guān)鍵代碼段長時(shí)間運(yùn)行時(shí),其他線程就會(huì)進(jìn)入等待狀態(tài),這會(huì)降低應(yīng)用程序的運(yùn)行性能;

      (3)如果需要同時(shí)訪問多個(gè)資源,則可能連續(xù)調(diào)用EnterCriticalSection;

      (4)Critical Section不是OS核心對象,如果進(jìn)入臨界區(qū)的線程"掛"了,將無法釋放臨界資源。這個(gè)缺點(diǎn)在Mutex中得到了彌補(bǔ)。

      互斥

      互斥量的作用是保證每次只能有一個(gè)線程獲得互斥量而得以繼續(xù)執(zhí)行,使用CreateMutex函數(shù)創(chuàng)建:

    HANDLE CreateMutex(
     LPSECURITY_ATTRIBUTES lpMutexAttributes,
     // 安全屬性結(jié)構(gòu)指針,可為NULL
     BOOL bInitialOwner,
     //是否占有該互斥量,TRUE:占有,F(xiàn)ALSE:不占有
     LPCTSTR lpName
     //信號量的名稱
    );

      Mutex是核心對象,可以跨進(jìn)程訪問,下面的代碼給出了從另一進(jìn)程訪問命名Mutex的例子:

    HANDLE hMutex;
    hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, L"mutexName");
    if (hMutex){
     …

    else{
     …
    }

      相關(guān)API:

    BOOL WINAPI ReleaseMutex(
     HANDLE hMutex
    );

      使用互斥編程的一般方法是:

    void UpdateResource()
    {
     WaitForSingleObject(hMutex,…);
     ...//do something
     ReleaseMutex(hMutex);
    }

      互斥(mutex)內(nèi)核對象能夠確保線程擁有對單個(gè)資源的互斥訪問權(quán)。互斥對象的行為特性與臨界區(qū)相同,但是互斥對象屬于內(nèi)核對象,而臨界區(qū)則屬于用戶方式對象,因此這導(dǎo)致mutex與Critical Section的如下不同:

      (1) 互斥對象的運(yùn)行速度比關(guān)鍵代碼段要慢;

      (2) 不同進(jìn)程中的多個(gè)線程能夠訪問單個(gè)互斥對象;

      (3) 線程在等待訪問資源時(shí)可以設(shè)定一個(gè)超時(shí)值。

      下圖更詳細(xì)地列出了互斥與臨界區(qū)的不同:


    信號量

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

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

      (1)如果當(dāng)前資源的數(shù)量大于0,則信號量有效;

      (2)如果當(dāng)前資源數(shù)量是0,則信號量無效;

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

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

      創(chuàng)建信號量

    HANDLE CreateSemaphore (
     PSECURITY_ATTRIBUTE psa,
     LONG lInitialCount, //開始時(shí)可供使用的資源數(shù)
     LONG lMaximumCount, //最大資源數(shù)
    PCTSTR pszName);

      釋放信號量

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

    BOOL WINAPI ReleaseSemaphore(
     HANDLE hSemaphore,
     LONG lReleaseCount, //信號量的當(dāng)前資源數(shù)增加lReleaseCount
     LPLONG lpPreviousCount
    );

      打開信號量

      和其他核心對象一樣,信號量也可以通過名字跨進(jìn)程訪問,打開信號量的API為:

    HANDLE OpenSemaphore (
     DWORD fdwAccess,
     BOOL bInherithandle,
     PCTSTR pszName
    );

      互鎖訪問

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

      請看下列代碼:

    int globalVar = 0;

    DWORD WINAPI ThreadFunc1(LPVOID n)
    {
     globalVar++;
     return 0;
    }
    DWORD WINAPI ThreadFunc2(LPVOID n)
    {
     globalVar++;
     return 0;
    }

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

    00401038 mov eax,[globalVar (0042d3f0)]
    0040103D add eax,1
    00401040 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è)問題:

    int globalVar = 0;

    DWORD WINAPI ThreadFunc1(LPVOID n)
    {
     InterlockedExchangeAdd(&globalVar,1);
     return 0;
    }
    DWORD WINAPI ThreadFunc2(LPVOID n)
    {
     InterlockedExchangeAdd(&globalVar,1);
     return 0;
    }

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

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

      可等待定時(shí)器

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

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

    HANDLE CreateWaitableTimer(
     PSECURITY_ATTRISUTES psa,
     BOOL fManualReset,//人工重置或自動(dòng)重置定時(shí)器
    PCTSTR pszName);

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

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

    BOOL SetWaitableTimer(
     HANDLE hTimer, //要設(shè)置的定時(shí)器
     const LARGE_INTEGER *pDueTime, //指明定時(shí)器第一次激活的時(shí)間
     LONG lPeriod, //指明此后定時(shí)器應(yīng)該間隔多長時(shí)間激活一次
     PTIMERAPCROUTINE pfnCompletionRoutine,
     PVOID PvArgToCompletionRoutine,
    BOOL fResume);

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

    BOOl Cancel WaitableTimer(
     HANDLE hTimer //要取消的定時(shí)器
    );

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

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

    HANDLE OpenWaitableTimer (
     DWORD fdwAccess,
     BOOL bInherithandle,
     PCTSTR pszName
    );

      實(shí)例

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

    #include <windows.h>
    #include <stdio.h>
    CRITICAL_SECTION cs1, cs2;
    long WINAPI ThreadFn(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 WINAPI ThreadFn(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ū)1

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

      或

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

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

      或

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

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

      或

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

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

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

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

    long WINAPI ThreadFn(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ā)生。
    posted on 2008-11-30 13:25 何克勤 閱讀(422) 評論(0)  編輯  收藏 所屬分類: Win32API 編程

    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 在线观看免费大黄网站| ASS亚洲熟妇毛茸茸PICS| 成全影视免费观看大全二| 国产精品免费在线播放| 亚洲日韩国产欧美一区二区三区 | 在线永久看片免费的视频| 美女羞羞视频免费网站| 亚洲在成人网在线看| 国产aⅴ无码专区亚洲av麻豆 | 亚洲福利在线视频| 亚洲国产成人久久综合一区77 | 亚洲日韩精品一区二区三区无码| 成年午夜视频免费观看视频| 中文字幕乱码一区二区免费| 美女羞羞喷液视频免费| 亚洲砖码砖专无区2023| 久久久久亚洲av无码专区喷水| 伊伊人成亚洲综合人网7777| 免费人成在线观看视频播放| 成人免费看片又大又黄| 久久精品国产免费观看| 日本高清不卡aⅴ免费网站| 无码人妻一区二区三区免费视频| 日韩亚洲不卡在线视频中文字幕在线观看| 久久精品亚洲综合专区| 国产综合精品久久亚洲| 亚洲国产综合久久天堂| 午夜国产羞羞视频免费网站| 免费人成在线视频| 国产成人免费高清激情明星| 88av免费观看| 98精品全国免费观看视频| 可以免费观看的国产视频| 久久国产乱子伦精品免费午夜| 国产一区二区三区亚洲综合| 亚洲色成人网站WWW永久四虎 | 亚洲综合精品第一页| 国产成人精品日本亚洲专一区| 亚洲精品国产免费| 亚洲毛片基地日韩毛片基地| 少妇中文字幕乱码亚洲影视|