Windows CE 進程、線程和記憶體管理(二)

來源:互聯網
上載者:User
二、同步

  在多數情況下,線程之間難免要相互連信、相互協調才能完成任務。比如,當有多個線程共同訪問同一個資源時,就必須保證一個線程正讀取這個資源資料的時候,其它線程不能夠修改它。這就需要線程之間相互連信,瞭解對方的行為。再有當一個線程要準備執行下一個任務之前,它必須等待另一個線程終止才能運行,這也需要彼此相互連信。實際開發過程中,線程間需要同步的情況非常多。Windows CE.NET給我們提供了很多的同步機制,熟練的掌握這些機制併合理運用會使線程之間的同步更合理、更高效。進程間的通訊機制在下一篇文章中講解。
  Windows CE.NET具有兩種運行模式:使用者模式和核心模式。並且允許一個運行於使用者模式的應用程式隨時切換為核心模式,或切換回來。線程同步的有些解決辦法運行在使用者模式,有些運行在核心模式。《Windows核心編程》上說從使用者模式切換到核心模式再切換回來至少要1000個CPU周期。我查看過CE下API函數SetKMode的源碼,這個函數用於在兩種模式間切換,改變模式只需修改一些標誌,至於需要多少個CPU周期很難確定。但至少可以肯定來回切換是需要一定時間的。所以在選擇同步機制上應該優先考慮運行在使用者模式的同步解決辦法。

1、互鎖函數
  互鎖函數運行在使用者模式。它能保證當一個線程訪問一個變數時,其它線程無法訪問此變數,以確保變數值的唯一性。這種訪問方式被稱為原子訪問。互鎖函數及其功能見如下列表:
 

函數 參數和功能
InterlockedIncrement 參數為PLONG類型。此函數使一個LONG變數增1
InterlockedDecrement 參數為PLONG類型。此函數使一個LONG變數減1
InterlockedExchangeAdd 參數1為PLONG類型,參數2為LONG類型。此函數將參數2賦給參數1指向的值
InterlockedExchange 參數1為PLONG類型,參數2為LONG類型。此函數將參數2的值賦給參數1指向的值
InterlockedExchangePointer 參數為PVOID* 類型,參數2為PVOID類型。此函數功能同上。具體參見協助
InterlockedCompareExchange 參數1為PLONG類型,參數2為LONG類型,參數3為LONG類型。此函數將參數1指向的值與參數3比較,相同則把參數2的值賦給參數1指向的值。不相同則不變
InterlockedCompareExchangePointer 參數1為PVOID* 類型,參數2為PVOID類型,參數3為PVOID。此函數功能同上。具體參見協助

2、臨界區
  臨界區對象運行在使用者模式。它能保證在臨界區內所有被訪問的資源不被其它線程訪問,直到當前線程執行完臨界區代碼。除了API外,MFC也對臨界區函數進行了封裝。臨界區相關函數:

void  InitializeCriticalSection ( LPCRITICAL_SECTION );void  EnterCriticalSection ( LPCRITICAL_SECTION );void  LeaveCriticalSection ( LPCRITICAL_SECTION );void  DeleteCriticalSection ( LPCRITICAL_SECTION );      

舉例如下:

void CriticalSectionExample (void){CRITICAL_SECTION  csMyCriticalSection;InitializeCriticalSection (&csMyCriticalSection);  ///初始化臨界區變數__try{EnterCriticalSection (&csMyCriticalSection);   ///開始保護機制///此處編寫代碼}__finally   ///異常處理,無論是否異常都執行此段代碼{LeaveCriticalSection (&csMyCriticalSection);  ///撤銷保護機制}}

MFC類使用更簡單:

CCriticalSection  cs;cs.Lock();///編寫代碼cs.Unlock();      

  使用臨界區要注意的是避免死結。當有兩個線程,每個線程都有臨界區,而且臨界區保護的資源有相同的時候,這時就要在編寫代碼時多加考慮。

3、事件對象
  事件對象運行在核心模式。與使用者模式不同,核心模式下線程利用等待函數來等待所需要的事件、訊號,這個等待過程由作業系統核心來完成,而線程處於睡眠狀態,當接收到訊號後,核心恢複線程的運行。核心模式的優點是線程在等待過程中並不浪費CPU時間,缺點是從使用者模式切換到核心模式需要一定的時間,而且還要切換回來。在講解事件對象前應該先談談等待函數。等待函數有四個。具體參數和功能見下表:

 

函數 參數和功能
WaitForSingleObject 參數1為HANDLE類型,參數2為DWORD類型。此函數等待參數1標識的事件,等待時間為參數2的值,單位ms。如果不逾時,當事件成為有訊號狀態時,線程喚醒繼續運行。
WaitForMultipleObjects 參數1為DWORD類型,參數2為HANDLE * 類型,參數3為BOOL類型,參數4為DWORD類型。此函數等待參數2指向的數組中包含的所有事件。如果不逾時,當參數3為FALSE時,只要有一個事件處於有訊號狀態,函數就返回這個事件的索引。參數3為TRUE時,等待所有事件都處於有訊號狀態時才返回。
MsgWaitForMultipleObjects 參數1為DWORD類型,參數2為LPHANDLE類型,參數3為BOOL類型,參數4為DWORD類型,參數5為DWORD類型。此函數功能上同WaitForMultipleObjects函數相似,只是多了一個喚醒掩碼。喚醒掩碼都是和訊息有關的。此函數不但能夠為事件等待,還能為特定的訊息等待。其實這個函數就是專為等待訊息而定義的。
MsgWaitForMultipleObjectsEx 參數1為DWORD類型,參數2為LPHANDLE類型,參數3為DWORD類型,參數4為DWORD類型,參數5為DWORD類型。此函數是MsgWaitForMultipleObjects函數的擴充。將原來函數的參數3除掉,添加參數5為標誌。標誌有兩個值:0或MWMO_INPUTAVAILABLE。

  如果一個線程既要執行大量任務同時又要響應使用者的按鍵訊息,這兩個專用於等待訊息的函數將非常有用。

和事件有關的函數有:

HANDLE  CreateEvent(LPSECURITY_ATTRIBUTES  lpEventAttributes,BOOL bManualReset,BOOL  bInitialState,LPTSTR  lpName);BOOL  SetEvent(HANDLE  hEvent );BOOL  PulseEvent(HANDLE  hEvent);BOOL  ResetEvent(HANDLE  hEvent);HANDLE  OpenEvent(DWORD  dwDesiredAccess,BOOL  bInheritHandle,LPCTSTR  lpName );

  事件對象是最常用的核心模式同步方法。它包含一個使用計數和兩個BOOL變數。其中一個BOOL變數指定這個事件對象是自動重設還是手工重設。另一個BOOL變數指定當前事件對象處於有訊號狀態還是無訊號狀態。
  函數CreateEvent建立一個事件對象,參數1必須為NULL,參數2指定是否手工重新設定事件對象的狀態。如果為FALSE,當等待函數接到訊號並返回後此事件對象被自動置為無訊號狀態。這時等待此事件對象的其它線程就不會被喚醒,因為事件對象已經被置為無訊號狀態。如果參數2設定為TRUE,當等待函數接到訊號並返回後事件對象不會被自動置於無訊號狀態,其它等待此事件對象的線程都能夠被喚醒。用ResetEvent函數可以手工將事件對象置為無訊號狀態。相反SetEvent函數將事件對象置為有訊號狀態。PulseEvent函數將事件對象置為有訊號狀態,然後立即置為無訊號狀態,在實際開發中這個函數很少使用。OpenEvent函數開啟已經建立的事件對象,一般用於不同進程內的線程同步。在調用CreateEvent建立一個事件對象時,傳遞一個名字給參數4,這樣在其它進程中的線程就可以調用OpenEvent函數並指定事件對象的名字,來訪問這個事件對象。

4、互斥對象
  互斥對象運行在核心模式。它的行為特性同臨界區非常相似,在一個線程訪問某個共用資源時,它能夠保證其它線程不能訪問這個資源。不同的是,互斥對象運行在核心模式,從時間上比臨界區要慢。由於核心對象具有全域性,不同的進程都能夠訪問,這樣利用互斥對象就可以讓不同的進程中的線程互斥訪問一個共用資源。而臨界區只能在一個進程內有效。

和互斥相關的函數有:

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES  lpMutexAttributes,BOOL  bInitialOwner,LPCTSTR  lpName);BOOL  ReleaseMutex(HANDLE  hMutex);      

  互斥對象包含一個引用計數,一個線程ID和一個遞迴計數。引用計數是所有核心對象都含有的。線程ID表示哪個線程正在使用互斥資源,當ID為0時,互斥對象發出訊號。遞迴計數用於一個線程多次等待同一個互斥對象。函數CreateMutex建立一個互斥對象,參數1必須設定為NULL,參數2如果設定為FALSE,表示當前線程並不佔有互斥資源,互斥對象的線程ID和遞迴計數都被設定為0,互斥對象處於有訊號狀態。如果設定為TRUE,表示當前線程將佔有互斥資源,互斥對象的線程ID被設定為當前線程ID,遞迴計數被設定為1,互斥對象處於無訊號狀態。當調用等待函數時,等待函數檢驗互斥對象的線程ID是否為0,如果為0,說明當前沒有線程訪問互斥資源,核心將線程喚醒,並且將互斥對象的遞迴計數加1。當一個線程被喚醒後,必須調用函數ReleaseMutex將互斥對象的遞迴計數減1。如果一個線程多次調用等待函數,就必須以同樣的次數調用ReleaseMutex函數。與其它Windows不同的是,和互斥相關的函數中沒有OpenMutex函數。要在不同進程中訪問同一互斥對象,調用CreateMutex函數,參數傳遞互斥對象的名稱,返回這個互斥對象的控制代碼。

5、信標對象
  信標對象,也叫號誌,用於限制資源訪問數量,他包含一個引用計數,一個當前可用資源數,一個最大可用資源數。如果當前可用資源數大於0,信標對象處於有訊號狀態。當可用資源數等於0,信標對象處於無訊號狀態。

和信標對象相關的函數:

HANDLE  CreateSemaphore(LPSECURITY_ATTRIBUTES  lpSemaphoreAttributes,LONG  lInitialCount,LONG  lMaximumCount,LPCTSTR  lpName);BOOL  ReleaseSemaphore(HANDLE  hSemaphore,LONG  lReleaseCount,LPLONG  lpPreviousCount);

  函數CreateSemaphore的參數1為NULL,參數2為當前可用資源初始值,參數3為最大可用資源數,參數4為名字。當參數2的值等於0時,信標對象處於無訊號狀態,這時核心將調用等待函數的線程置於睡眠狀態,如果參數2的值大於0,信標對象處於有訊號狀態,這時核心將調用等待函數的線程置於運行狀態,並將信標對象的當前可用資源數減1。函數ReleaseSemaphore的參數1為信標對象的控制代碼,參數2為要釋放的資源數,參數3返回原來可用資源數,調用此函數將當前可用資源數加上參數2的值。當一個線程訪問完可用資源後,應該調用ReleaseSemaphore函數使當前可用資源數遞增。要在不同進程中訪問同一信標對象,調用CreateSemaphore函數並傳遞信標對象的名稱,得到已經在其它進程建立的信標對象的控制代碼。CE下沒有OpenSemaphore函數。另外我還要說明一點,等待函數預設將信標對象的當前可用資源數減1,但線程可能一次使用多個資源,這就可能出現問題了。為避免問題出現,應該遵守一個線程只使用一個資源的原則。

6、訊息佇列
  Windows CE.NET允許一個應用程式或驅動程式建立自己的訊息佇列。訊息佇列既可以作為線上程之間傳遞資料的工具,也可以作為線程之間同步的工具。它的優點是需要很小的記憶體,一般只用於點到點的通訊。

和訊息佇列相關的函數:

HANDLE  WINAPI  CreateMsgQueue(LPCWSTR  lpszName,LPMSGQUEUEOPTIONS  lpOptions);BOOL  WINAPI  CloseMsgQueue(HANDLE  hMsgQ);BOOL  GetMsgQueueInfo(HANDLE  hMsgQ,LPMSGQUEUEINFO  lpInfo);HANDLE  WINAPI  OpenMsgQueue(HANDLE  hSrcProc,HANDLE  hMsgQ,LPMSGQUEUEOPTIONS  lpOptions);BOOL  ReadMsgQueue(HANDLE  hMsgQ,LPVOID  lpBuffer,DWORD  cbBufferSize,LPDWORD  lpNumberOfBytesRead,DWORD  dwTimeout,DWORD  *pdwFlags);BOOL  WINAPI  WriteMsgQueue(HANDLE  hMsgQ,LPVOID  lpBuffer,DWORD  cbDataSize,DWORD  dwTimeout,DWORD  dwFlags);      

  使用CreateMsgQueue函數建立一個訊息佇列,傳遞一個MSGQUEUEOPTIONS結構指標。在這個結構中設定標誌(允許隊列緩衝區動態改變大小,允許直接讀或者寫操作而不管之前是否有過寫操作或讀操作)、隊列允許的最大訊息數、隊列屬性(唯讀或者唯寫)。使用WriteMsgQueue函數把一個訊息寫入到訊息佇列中。傳遞一個訊息佇列的緩衝區、訊息資料的大小、寫入緩衝區的逾時值、標誌。使用ReadMsgQueue函數把一個訊息從訊息佇列中讀出。使用CloseMsgQueue函數關閉訊息佇列緩衝區。使用OpenMsgQueue函數能夠開啟其它進程中建立的訊息佇列。另外可以用等待函數等待訊息佇列的變化。當訊息佇列由沒有訊息到有訊息時,或由滿訊息到不滿訊息時喚醒調用等待函數的線程。關於訊息佇列我並沒有實驗過,MSDN上有幾個簡單的例子。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.