天天看點

WinCE 系統中的同步機制

好文章,要好好學習。

本文轉自http://blog.csdn.net/thl789/archive/2006/01/17/582246.aspx ,轉載請注明出處

摘要 ... 1

目錄 ... 1

一、 WinCE程序 /線程模型概覽 ... 1

二、臨界區( Critical Section) ... 2

三、互斥體( Mutex) ... 3

四、信号量( Semaphore) ... 4

五、事件( Event) ... 5

六、消息隊列( MsgQueue P2P) ... 6

七、互鎖函數( Interlocked Function) ... 8

八、 Wait函數 ... 8

總結 ... 10

參考資料以及進一步閱讀 ... 10

關于作者 ... 10

一、 WinCE 程序 / 線程模型概覽

WinCE 作業系統實作了程序 / 線程兩級管理模型。連同核心程序和系統程序,以及應用程序一起, WinCE 共支援 32 個程序。程序還可以有自己的線程,程序預設有一個主線程,線程分為 256 個優先級别, WinCE 排程程式按照線程優先級高低來排程。程序是 WinCE 中最小資源配置設定的單元,線程是 WinCE 的最小排程單元。

本文講述的同步機制有些隻适用于線程間同步,有些既能用于線程間同步又能用于程序間同步,下面讨論到某一種機制的時候,再具體詳述其适用場景。

二、臨界區( Critical Section )

(本節内容适用于 WinCE 1.0 及以上版本 )

WinCE 實作了作業系統理論裡的臨界區管理。臨界區内含有對臨街資源的通路。通過對臨界區進行有效管理,使得某一時刻最多隻能有一個線程進入臨界區,實作對臨界資源的保護。

考慮下面用臨界區實作兩個線程對臨界資源互斥通路的情形。 The 1st Thread 和 The 2nd Thread 都要調用 Func_CriticalSection() 函數,而 Func_CriticalSection() 内部會對某一臨界資源進行操作,為了保護這一臨界資源,我們用一個 WinCE 的 CriticalSection 來實作。

圖一是該解決方案的一個場景。 The 1st Thread 和 The 2nd Thread 進入臨界區之前已經建立( new )并初始化( InitializeCriticalSection() )了一個臨界區。試圖進入該臨界區的線程首先必須獲得進入該臨界區(通過 EnterCriticalSection() / TryEnterCriticalSection() )的資格 ,如果臨界區内沒有線程,它就能進入,否則必須被挂起等待。進入臨界區的線程可以對臨界資源進行操作( OpOnSharedResources() )。操作完成之後退出臨界區( LeaveCriticalSection () ),以允許其它線程進入。圖一中第一個線程進入臨界區還未退出之前,第二個線程因執行 EnterCriticalSection() 而 一直在被挂起等待,第一個線程退出臨界區之後,第二個線程從等待中被喚醒,按照相應的排程機制重新競争獲得 CPU ,進而繼續執行,完成臨界區内的操作。

圖一、應用臨界區( CriticalSection )實作同步

利用臨界區可以實作對臨界資源的互斥操作, WinCE 的臨界區應用在同一程序内,亦即實作的是同一程序内的線程間同步,不能應用在程序之間。

三、互斥體( Mutex )

(本節内容适用于 WinCE 1.01 及以上版本 )

互斥體( Mutex )顧名思義就是實作對共享資源實作互斥通路的。 WinCE 中的互斥體的使用規則如下(按線程之間的同步為例):

◇   互斥體可以是匿名互斥體也可以是命名互斥體;

◇   線程建立互斥體的時候可以指定建立完畢它是否就立即擁有該互斥體;

◇   某一時刻最多隻有一個線程擁有給定的互斥體;

◇   擁有互斥體的線程可多次獲得該互斥體;

◇   線程可用 CreateMutex 或 wait函數 來獲得互斥體。

看下面應用互斥體的情景。 Thread1 建立并擁有了一個互斥體 g_hMutex[ 序列 1&2] 。互斥體 g_hMutex 是定義的全局量, thread2 可通路到, Thread2 用 WaitForSingleObject() 試圖獲得該互斥體,因為此時 g_hMutex 是被 Thread1 擁有的,是以 Thread2 被挂起 [ 序列 3] 。 Thread1 執行了一些操作之後,又用 wait 函數試圖再次獲得了該互斥體,因為此時 g_hMutex 的擁有者還是 Thread1 ,是以 Thread1 立即再次獲得了該互斥體 [ 序列 4-6] 。 Thread1 對互斥體 g_hMutex 保護的共享資源操作完畢,釋放該互斥體 [ 序列 7] ,但是因為 Thread1 兩次獲得了 g_hMutex ,是以 g_hMutex 的擁有權并沒有交出。等到 Thread1 再次釋放互斥體 g_hMutex[ 序列 8] 之後, Thread1 才失去了 g_hMutex 的擁有權, Thread2 可競争 g_hMutex 的擁有權,如能成功擁有,就可從等待狀态被喚醒,完成對共享資源的通路操作。

圖二、應用互斥體( Mutex )實作同步

不知道從上面的描述,讀者有又沒有看出互斥體與臨界區之間的差別。使用上,它們都實作的對共享資源的互斥通路,但是臨界區是多個線程對同一段程式的執行,這段程式會通路到臨界資源,是以它們是同一個程序内的多個線程;而互斥體的應用情景是線上程之間獨立執行,可以不是程式上的重疊,隻是一個線程執行到共享資源的時候,有可能别的線程也要通路該共享資源,是以要用互斥體來保護該共享資源。

由于互斥體上述的應用範圍,它不但能應用在同一程序内的線程之間,也能應用在程序之間。程序之間可以通過命名互斥體來實作。一個程序通過為 CreateMutex() 指定一個名字做參數來獲得已經存在的互斥體的句柄,處理過程如下面程式所示。

  HANDLE  hMutex;

  hMutex = CreateMutex (

                NULL,                       //

                FALSE,                      // Mutex object NOT initially owned

                TEXT("NameOfMutexObject")); // Muetx Name

  if (NULL == hMutex)

  {

    // Something wrong, deal with it here.

  }

  else

  {

    if ( ERROR_ALREADY_EXISTS == GetLastError () )

    {

      // CreateMutex() opened existing mutex."

      // ...

    }

    else

    {

      // CreateMutex() created new mutex."

      // ...

    }

  }

程序獲得已經存在的互斥體的句柄之後,就可以如線程之間同步規則那樣來實作程序之間的互斥體使用。

四、信号量( Semaphore )

(本節内容适用于 WinCE 3.0 及以上版本 )

信号量實體有一個數值訓示目前該信号量使用情況,目前值的大小處于零和最大值之間。用下列操作原語實作信号量的同步操作(用線程間同步來說明):

◇ P ( S, num ) :如果信号量目前值減去 num 大于零,執行該操作的線程獲得信号量,可繼續執行,同時信号量的目前值減小 num ;否則通路線程被挂起等待

◇ V ( S, num ) :信号量的目前值增加 num (增加之後仍不大于最大值),如果有等待該信号量的線程被挂起,喚醒等待線程并按照相應的排程機制參與排程。

信号量一般用來控制某類共享資源,最大值辨別該類資源的數目,執行 P 操作是申請一定數目這類資源, V 操作是釋放一定數目的這類資源。在 WinCE 的信号量實作中,并未實作 OpenSemaphore , P 操作是用 wait函數 來實作的,而 V 操作由 ReleaseSemaphore 來實作。

看下面用信号量來控制數量為 2 的某類共享資源的使用情景。

圖三、用信号量( Semaphore )實作同步

Thread1 建立一個控制 2 個共享資源的信号量 [ 序列 1&2] ,并且自己用 WaitForSingleObject() 來申請一個資源,因為目前可用的這類資源有 2 個,是以它就獲得了其中的一個 [ 序列 3&4] 。同樣地, Thread2 獲得了另外一個資源 [ 序列 5&6] 。但是當 Thread3 也申請這類資源的時候,因為此時已經沒有這類資源,信号量的值為零,它就被挂起 [ 序列 7] 。擁有這類資源的線程釋放掉一個資源 [ 序列 8&9] ,并且滿足能滿足 Thread3 申請資源數目的要求, Thread3 競争獲得了該資源 [ 序列 10] 。

信号量是實作同步的基本方法,在幾乎所有的多任務作業系統裡面都做了信号量的實作,其它一些同步機制其實可以通過信号量來實作。如果把信号量的最大值和初始值均設定為 1 ,那麼它就可實作互斥體 ,即保證對共享資源互斥通路的保護。如果把信号量的初始值設定為 0 ,等待别的線程 ReleaseSemaphore 來喚醒它,那麼它就可實作事件( Event ) 機制。

信号量機制可以用在同一程序内的線程之間同步,也可以用在程序之間的同步。程序間同步的實作方法如同互斥體的此類實作。

五、事件( Event )

(本節内容适用于 WinCE 1.0 及以上版本 )

WinCE 系統中廣泛用到事件( Event )機制來實作線程之間的協調工作,具體表現在:

◇ 通知一個線程什麼時候去執行它的特定的任務

◇ 辨別事件的發生

WinCE 中的線程操作原語有 CreateEvent() , SetEvent()/PulseEvent() , ResetEvent() 等。建立 Event 的時候在 CreateEvent() 的參數中指定 Event 的初始狀态(觸發的 / 未觸發的),還要指定事件是否手動複位(手動複位是隻有用 ResetEvent() 才能把事件狀态顯式地設定為未觸發的,自動複位是等待該事件的線程等待事件到來之後,系統自動把該事件的狀态複位為未觸發的)。線程等待事件仍然用 wait函數 。

下面是使用 Event 同步的簡單情況:

圖四、用事件( Event )實作同步

線程 Thread1 執行過程中,要等到某個條件滿足(事件觸發),是以它建立了一個事件 Event (參數設定為:手動複位,初始條件為未觸發的),用 WaitForSingleObject() 來等待這個事件。線程 Thread2 執行了一些操作之後,滿足了 Thread1 的條件,用 SetEvent 來觸發該事件。

       除了可以用 SetEvent() 來觸發事件之外,也可以用 PulseEvent() 來觸發,差別是 PulseEvent() 觸發該事件之後把它又複位。

       另外,也可以把命名事件用于程序之間的同步。實作方法同互斥體中的描述。

六、消息隊列( MsgQueue P2P )

(本節内容适用于 WinCE.net 4.0 及以上版本 )

消息隊列通信機制如同建立了一個管道,管道的雙方通過分别建立到管道的兩端,與管道的讀端口建立連接配接的程序可以從該端口讀取消息( Message ),與管道的寫端口建立連接配接的程序可以寫入消息( Message )到管道。管道内消息組成了一個 FIFO ( F irst I n F irst O ut )的隊列,從讀端口讀取消息是讀取隊列的頭,寫入消息到寫端口是在隊列尾部追加一個消息。

WinCE 中關于 MsgQueue 的操作函數主要有:

◇ CreateMsgQueue() 建立一個消息隊列。在該函數的參數中指定消息隊列的名字,消息隊列的最大數目,每個消息的最大長度,對該消息隊列可進行讀還是寫操作等。因為調用一次 CreateMsgQueue 函數,隻能指定讀或者寫這樣的二選一的消息隊列,是以一般需要用相同的消息隊列名字做參數兩次調用該函數,分别建立讀消息隊列和寫消息隊列,它們的傳回值分别被讀程序和寫程序用 OpenMsgQueue() 打開用于讀取消息和寫入消息。

◇ OpenMsgQueue() 打開消息隊列并建立與相應端口的連接配接。程序與讀端口建立連接配接之後,可用傳回的句柄從消息隊列中讀取消息;程序與寫端口建立連接配接之後,可用傳回的句柄寫入消息到消息隊列中。

◇ CloseMsgQueue() 斷開與消息隊列相應的端口之間的連接配接,并關閉由 CreateMsgQueue() 或 OpenMsgQueue() 建立或打開的消息隊列。

◇ ReadMsgQueue() 如同從普通檔案中讀取資料一樣,用于從消息隊列中讀取消息。可以指定讀取消息時,如果消息隊列為空,讀程序是被挂起還是直接傳回。

◇ WriteMsgQueue() 如同寫資料到普通檔案中一樣,用于寫消息到消息隊列中。可以指定寫入消息時,如果消息隊列已滿,寫程序是被挂起還是直接傳回。

下圖是 MsgQueue 應用的典型場景。

圖五、用消息隊列( MsgQueue )實作同步

這種場景下的執行過程為:

◇ 主程序 MainProcess 建立了名為“ Reader/Writer MsgQueue ”的讀和寫的消息隊列,并分别傳回 hMsgQ_r_m 和 hMsgQ_w_m[ 序列 1-4] 。

◇ 讀程序 ReaderProcess 以主程序的 ProcessId 和 hMsgQ_r_m 為參數,通過 OpenMsgQueue() 與 MainProcess 消息隊列的讀端口建立連接配接 [ 序列 5&6] 。

◇ ReaderProcess 與消息隊列建立連接配接之後,用 WaitForSingleOnject(hMsg_r) 看消息隊列中是否有消息,因為此時消息隊列為空,是以 ReaderProcess 被挂起 [ 序列 7] 。

◇ 寫程序 WriterProcess 以主程序的 ProcessId 和 hMsgQ_w_m 為參數,通過 OpenMsgQueue() 與 MainProcess 消息隊列的寫端口建立連接配接 [ 序列 8&9] 。

◇ WriterProcess 與消息隊列建立連接配接之後,用 WaitForSingleOnject(hMsg_w) 看消息隊列中消息是否滿,因為此時消息隊列為空,未滿,是以 WriterProcess 不會被挂起 [ 序列 10&11] 。

◇ WriterProcess 寫消息到消息隊列中 [ 序列 12&13] 。

◇ 因為消息隊列中已經有了消息, ReaderProcess 從挂起狀态被喚醒 [ 序列 14] 。

◇ ReaderProcess 繼續執行,從消息隊列中讀取 WriterProcess 剛才寫入的消息。

消息隊列除可用于同步之外,主要用于程序之間的資料傳遞,另外消息隊列也可以用于同一程序中的線程之間同步,但是既然線程之間能直接傳遞資料,又何必那麼麻煩呢。

七、互鎖函數( Interlocked Function )

(本節内容适用于 WinCE 1.0 及以上版本)

除了上面各節的同步方法之外, WinCE 還提供了一些用于原子操作的互鎖函數,這些函數在執行過程中,不會因為線程的排程引起的目前線程被搶占而打斷函數内的操作。

這些函數主要有:

InterlockedIncrement

InterlockedDecrement

InterlockedExchange

InterlockedTestExchange

InterlockedCompareExchange

InterlockedCompareExchangePointer

InterlockedExchangePointer

InterlockedExchangeAdd

八、 Wait 函數

(本節内容适用于 WinCE 1.0 及以上版本 )

Wait 函數不是特指的某一個函數,而是指 wait 的系列函數。 wait 函數并不是 WinCE 同步機制中的一種,但是 WinCE 的很多同步機制要用到 wait 函數,這些在前面講述各個同步方法的時候也已有論述。

一般地,執行 wait 函數時,如果等待的同步對象條件不滿足,那麼執行 wait 函數的程序 / 線程會被挂起,當然也可以給它們設定等待的逾時時間,超過給定時間,不管條件是否滿足,它們會自動從等待狀态蘇醒。等待既可以等待某一個條件,也可以等待多個條件中的一個, WinCE 不支援等待多個條件同時滿足,如果有這種需要,要自己實作。

Wait 函數原型如下:

DWORD WaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds );

DWORD WaitForMultipleObjects (

  DWORD nCount,              // No. of object handles in the array.

  CONST HANDLE* lpHandles,   // Pointer to an array of object handles.

  BOOL fWaitAll,             // MUST be FALSE in WinCE

  DWORD dwMilliseconds       // Timeout (0, mills, or INFINITE)

);

DWORD MsgWaitForMultipleObjects (

  DWORD nCount,             // No. of object handles in the array.

  LPHANDLE pHandles,        // Pointer to an array of object handles.

  BOOL fWaitAll,            // MUST be FALSE in WinCE

  DWORD dwMilliseconds,     // Timeout (0, mills, or INFINITE)

  DWORD dwWakeMask          // Input types for which an input event object handle

);

前面講述各種同步機制的時候都是以 WaitForSingleObject() 來說明的,這裡就不再贅述它了。

WaitForMultipleObjects() 和 MsgWaitForMultipleObjects() 可以用來等多個同步對象,它們之間的差別就是 MsgWaitForMultipleObjects() 還等待 dwWakeMask 參數中指定的輸入事件,即這些事件發生時,等待的程序 / 線程也能被喚醒。

用 WaitForMultipleObjects() 等待的多個同步對象的句柄放在參數 lpHandles 數組中,同步對象的句柄的數目放在參數 nCount 中。 dwMilliseconds 指定了等待的逾時參數:如果指定為 0 ,該函數等待每個同步對象之後,不管觸發與否都直接傳回;如果指定為 INFINITE ,該函數等待每個同步對象,直到有一個同步對象被觸發,否則執行該函數的運作實體将一直被挂起;如果指定為非 0 ,非 INFINITE 的一個數值,那麼不管 等待的同步對象是否被觸發,到了指定的時間,執行該函數而被挂起的運作實體也會被喚醒。因哪個同步對象被觸發而傳回還是因逾時而傳回,可以從傳回值中來判定,傳回值為 WAIT_TIMEOUT ,是因為逾時; 傳回值為 WAIT_OBJECT_0 到 WAIT_OBJECT_0 + nCount -1 之間的數時,可以按順序找到具體那個同步對象被觸發 。

下面是 WaitForMultipleObjects 的典型應用。

  HANDLE hSynchObjects[EVENT_COUNT];

  DWORD dwEvent;

  // ...

  dwEvent = WaitForMultipleObjects (

                      EVENT_COUNT,        // Number of objects in an array

                      hSynchObjects,      // Array of objects

                      FALSE,              // MUST be FALSE

                      500);               // timeout, 0.5s

  switch (dwEvent)

  {

    case WAIT_TIMEOUT:

      // Handle for timeout

      break;

    case WAIT_OBJECT_0 + 0:

      // Handle the 1st event

      break;

    case WAIT_OBJECT_0 + 1:

      // Handle the 2nd one

      break;

    ...

    case WAIT_OBJECT_0 + EVENT_COUNT -1:

      // Handle the final one

      break;

    default:

      // Error: Not an anticipant one, handle it.

      break;

  }

總結

本文探讨了 WinCE 中的各種同步機制的用法,并給出了它們的典型應用場景。關于它們進一步的進階話題,将在後續文章中探讨。

參考資料以及進一步閱讀

1) MSDN

2) UML Reference Manual, 2nd Edition

3 ) Abraham Silberschatz, Peter Baer Galvin, Greg Gagne. Operating System Concepts, 6th Edition. John Wiley & Sons, Inc/ 高等教育出版社影印 , 2002.5

4 ) David R. Butenhof/ 于磊,曾剛 . Programming with POSIX Threads. Addison Wesley/ 中國電力出版社 , 2003

繼續閱讀