Windows CE 進(jìn)程、線(xiàn)程和內存管理(二)
在多數情況下,線(xiàn)程之間難免要相互通信、相互協(xié)調才能完成任務(wù)。比如,當有多個(gè)線(xiàn)程共同訪(fǎng)問(wèn)同一個(gè)資源時(shí),就必須保證一個(gè)線(xiàn)程正讀取這個(gè)資源數據的時(shí)候,其它線(xiàn)程不能夠修改它。這就需要線(xiàn)程之間相互通信,了解對方的行為。再有當一個(gè)線(xiàn)程要準備執行下一個(gè)任務(wù)之前,它必須等待另一個(gè)線(xiàn)程終止才能運行,這也需要彼此相互通信。實(shí)際開(kāi)發(fā)過(guò)程中,線(xiàn)程間需要同步的情況非常多。Windows CE.NET給我們提供了很多的同步機制,熟練的掌握這些機制并合理運用會(huì )使線(xiàn)程之間的同步更合理、更高效。進(jìn)程間的通信機制在下一篇文章中講解。
Windows CE.NET具有兩種運行模式:用戶(hù)模式和內核模式。并且允許一個(gè)運行于用戶(hù)模式的應用程序隨時(shí)切換為內核模式,或切換回來(lái)。線(xiàn)程同步的有些解決辦法運行在用戶(hù)模式,有些運行在內核模式?!禬indows核心編程》上說(shuō)從用戶(hù)模式切換到內核模式再切換回來(lái)至少要1000個(gè)CPU周期。我查看過(guò)CE下API函數SetKMode的源碼,這個(gè)函數用于在兩種模式間切換,改變模式只需修改一些標志,至于需要多少個(gè)CPU周期很難確定。但至少可以肯定來(lái)回切換是需要一定時(shí)間的。所以在選擇同步機制上應該優(yōu)先考慮運行在用戶(hù)模式的同步解決辦法。
1、互鎖函數
互鎖函數運行在用戶(hù)模式。它能保證當一個(gè)線(xiàn)程訪(fǎng)問(wèn)一個(gè)變量時(shí),其它線(xiàn)程無(wú)法訪(fǎng)問(wèn)此變量,以確保變量值的唯一性。這種訪(fǎng)問(wèn)方式被稱(chēng)為原子訪(fǎng)問(wèn)?;ユi函數及其功能見(jiàn)如下列表:
函數 | 參數和功能 |
InterlockedIncrement | 參數為PLONG類(lèi)型。此函數使一個(gè)LONG變量增1 |
InterlockedDecrement | 參數為PLONG類(lèi)型。此函數使一個(gè)LONG變量減1 |
InterlockedExchangeAdd | 參數1為PLONG類(lèi)型,參數2為L(cháng)ONG類(lèi)型。此函數將參數2賦給參數1指向的值 |
InterlockedExchange | 參數1為PLONG類(lèi)型,參數2為L(cháng)ONG類(lèi)型。此函數將參數2的值賦給參數1指向的值 |
InterlockedExchangePointer | 參數為PVOID* 類(lèi)型,參數2為PVOID類(lèi)型。此函數功能同上。具體參見(jiàn)幫助 |
InterlockedCompareExchange | 參數1為PLONG類(lèi)型,參數2為L(cháng)ONG類(lèi)型,參數3為L(cháng)ONG類(lèi)型。此函數將參數1指向的值與參數3比較,相同則把參數2的值賦給參數1指向的值。不相同則不變 |
InterlockedCompareExchangePointer | 參數1為PVOID* 類(lèi)型,參數2為PVOID類(lèi)型,參數3為PVOID。此函數功能同上。具體參見(jiàn)幫助 |
2、臨界區
臨界區對象運行在用戶(hù)模式。它能保證在臨界區內所有被訪(fǎng)問(wèn)的資源不被其它線(xiàn)程訪(fǎng)問(wèn),直到當前線(xiàn)程執行完臨界區代碼。除了API外,MFC也對臨界區函數進(jìn)行了封裝。臨界區相關(guān)函數:
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); ///開(kāi)始保護機制
///此處編寫(xiě)代碼
}
__finally ///異常處理,無(wú)論是否異常都執行此段代碼
{
LeaveCriticalSection (csMyCriticalSection); ///撤銷(xiāo)保護機制
}
}
MFC類(lèi)使用更簡(jiǎn)單:
CCriticalSection cs;
cs.Lock();
///編寫(xiě)代碼
cs.Unlock();
使用臨界區要注意的是避免死鎖。當有兩個(gè)線(xiàn)程,每個(gè)線(xiàn)程都有臨界區,而且臨界區保護的資源有相同的時(shí)候,這時(shí)就要在編寫(xiě)代碼時(shí)多加考慮。
3、事件對象
事件對象運行在內核模式。與用戶(hù)模式不同,內核模式下線(xiàn)程利用等待函數來(lái)等待所需要的事件、信號,這個(gè)等待過(guò)程由操作系統內核來(lái)完成,而線(xiàn)程處于睡眠狀態(tài),當接收到信號后,內核恢復線(xiàn)程的運行。內核模式的優(yōu)點(diǎn)是線(xiàn)程在等待過(guò)程中并不浪費CPU時(shí)間,缺點(diǎn)是從用戶(hù)模式切換到內核模式需要一定的時(shí)間,而且還要切換回來(lái)。在講解事件對象前應該先談?wù)劦却瘮?。等待函數有四個(gè)。具體參數和功能見(jiàn)下表:
函數 | 參數和功能 |
WaitForSingleObject | 參數1為HANDLE類(lèi)型,參數2為DWORD類(lèi)型。此函數等待參數1標識的事件,等待時(shí)間為參數2的值,單位ms。如果不超時(shí),當事件成為有信號狀態(tài)時(shí),線(xiàn)程喚醒繼續運行。 |
WaitForMultipleObjects | 參數1為DWORD類(lèi)型,參數2為HANDLE * 類(lèi)型,參數3為BOOL類(lèi)型,參數4為DWORD類(lèi)型。此函數等待參數2指向的數組中包含的所有事件。如果不超時(shí),當參數3為FALSE時(shí),只要有一個(gè)事件處于有信號狀態(tài),函數就返回這個(gè)事件的索引。參數3為T(mén)RUE時(shí),等待所有事件都處于有信號狀態(tài)時(shí)才返回。 |
MsgWaitForMultipleObjects | 參數1為DWORD類(lèi)型,參數2為L(cháng)PHANDLE類(lèi)型,參數3為BOOL類(lèi)型,參數4為DWORD類(lèi)型,參數5為DWORD類(lèi)型。此函數功能上同WaitForMultipleObjects函數相似,只是多了一個(gè)喚醒掩碼。喚醒掩碼都是和消息有關(guān)的。此函數不但能夠為事件等待,還能為特定的消息等待。其實(shí)這個(gè)函數就是專(zhuān)為等待消息而定義的。 |
MsgWaitForMultipleObjectsEx | 參數1為DWORD類(lèi)型,參數2為L(cháng)PHANDLE類(lèi)型,參數3為DWORD類(lèi)型,參數4為DWORD類(lèi)型,參數5為DWORD類(lèi)型。此函數是MsgWaitForMultipleObjects函數的擴展。將原來(lái)函數的參數3除掉,添加參數5為標志。標志有兩個(gè)值:0或MWMO_INPUTAVAILABLE。 |
如果一個(gè)線(xiàn)程既要執行大量任務(wù)同時(shí)又要響應用戶(hù)的按鍵消息,這兩個(gè)專(zhuān)用于等待消息的函數將非常有用。
和事件有關(guān)的函數有:
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 );
事件對象是最常用的內核模式同步方法。它包含一個(gè)使用計數和兩個(gè)BOOL變量。其中一個(gè)BOOL變量指定這個(gè)事件對象是自動(dòng)重置還是手工重置。另一個(gè)BOOL變量指定當前事件對象處于有信號狀態(tài)還是無(wú)信號狀態(tài)。
函數CreateEvent創(chuàng )建一個(gè)事件對象,參數1必須為NULL,參數2指定是否手工重新設置事件對象的狀態(tài)。如果為FALSE,當等待函數接到信號并返回后此事件對象被自動(dòng)置為無(wú)信號狀態(tài)。這時(shí)等待此事件對象的其它線(xiàn)程就不會(huì )被喚醒,因為事件對象已經(jīng)被置為無(wú)信號狀態(tài)。如果參數2設置為T(mén)RUE,當等待函數接到信號并返回后事件對象不會(huì )被自動(dòng)置于無(wú)信號狀態(tài),其它等待此事件對象的線(xiàn)程都能夠被喚醒。用ResetEvent函數可以手工將事件對象置為無(wú)信號狀態(tài)。相反SetEvent函數將事件對象置為有信號狀態(tài)。PulseEvent函數將事件對象置為有信號狀態(tài),然后立即置為無(wú)信號狀態(tài),在實(shí)際開(kāi)發(fā)中這個(gè)函數很少使用。OpenEvent函數打開(kāi)已經(jīng)創(chuàng )建的事件對象,一般用于不同進(jìn)程內的線(xiàn)程同步。在調用CreateEvent創(chuàng )建一個(gè)事件對象時(shí),傳遞一個(gè)名字給參數4,這樣在其它進(jìn)程中的線(xiàn)程就可以調用OpenEvent函數并指定事件對象的名字,來(lái)訪(fǎng)問(wèn)這個(gè)事件對象。
4、互斥對象
互斥對象運行在內核模式。它的行為特性同臨界區非常相似,在一個(gè)線(xiàn)程訪(fǎng)問(wèn)某個(gè)共享資源時(shí),它能夠保證其它線(xiàn)程不能訪(fǎng)問(wèn)這個(gè)資源。不同的是,互斥對象運行在內核模式,從時(shí)間上比臨界區要慢。由于內核對象具有全局性,不同的進(jìn)程都能夠訪(fǎng)問(wèn),這樣利用互斥對象就可以讓不同的進(jìn)程中的線(xiàn)程互斥訪(fǎng)問(wèn)一個(gè)共享資源。而臨界區只能在一個(gè)進(jìn)程內有效。
和互斥相關(guān)的函數有:
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName);
BOOL ReleaseMutex(HANDLE hMutex);
互斥對象包含一個(gè)引用計數,一個(gè)線(xiàn)程ID和一個(gè)遞歸計數。引用計數是所有內核對象都含有的。線(xiàn)程ID表示哪個(gè)線(xiàn)程正在使用互斥資源,當ID為0時(shí),互斥對象發(fā)出信號。遞歸計數用于一個(gè)線(xiàn)程多次等待同一個(gè)互斥對象。函數CreateMutex創(chuàng )建一個(gè)互斥對象,參數1必須設置為NULL,參數2如果設置為FALSE,表示當前線(xiàn)程并不占有互斥資源,互斥對象的線(xiàn)程ID和遞歸計數都被設置為0,互斥對象處于有信號狀態(tài)。如果設置為T(mén)RUE,表示當前線(xiàn)程將占有互斥資源,互斥對象的線(xiàn)程ID被設置為當前線(xiàn)程ID,遞歸計數被設置為1,互斥對象處于無(wú)信號狀態(tài)。當調用等待函數時(shí),等待函數檢驗互斥對象的線(xiàn)程ID是否為0,如果為0,說(shuō)明當前沒(méi)有線(xiàn)程訪(fǎng)問(wèn)互斥資源,內核將線(xiàn)程喚醒,并且將互斥對象的遞歸計數加1。當一個(gè)線(xiàn)程被喚醒后,必須調用函數ReleaseMutex將互斥對象的遞歸計數減1。如果一個(gè)線(xiàn)程多次調用等待函數,就必須以同樣的次數調用ReleaseMutex函數。與其它Windows不同的是,和互斥相關(guān)的函數中沒(méi)有OpenMutex函數。要在不同進(jìn)程中訪(fǎng)問(wèn)同一互斥對象,調用CreateMutex函數,參數傳遞互斥對象的名稱(chēng),返回這個(gè)互斥對象的句柄。
5、信標對象
信標對象,也叫信號燈,用于限制資源訪(fǎng)問(wèn)數量,他包含一個(gè)引用計數,一個(gè)當前可用資源數,一個(gè)最大可用資源數。如果當前可用資源數大于0,信標對象處于有信號狀態(tài)。當可用資源數等于0,信標對象處于無(wú)信號狀態(tài)。
和信標對象相關(guān)的函數:
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時(shí),信標對象處于無(wú)信號狀態(tài),這時(shí)內核將調用等待函數的線(xiàn)程置于睡眠狀態(tài),如果參數2的值大于0,信標對象處于有信號狀態(tài),這時(shí)內核將調用等待函數的線(xiàn)程置于運行狀態(tài),并將信標對象的當前可用資源數減1。函數ReleaseSemaphore的參數1為信標對象的句柄,參數2為要釋放的資源數,參數3返回原來(lái)可用資源數,調用此函數將當前可用資源數加上參數2的值。當一個(gè)線(xiàn)程訪(fǎng)問(wèn)完可用資源后,應該調用ReleaseSemaphore函數使當前可用資源數遞增。要在不同進(jìn)程中訪(fǎng)問(wèn)同一信標對象,調用CreateSemaphore函數并傳遞信標對象的名稱(chēng),得到已經(jīng)在其它進(jìn)程創(chuàng )建的信標對象的句柄。CE下沒(méi)有OpenSemaphore函數。另外我還要說(shuō)明一點(diǎn),等待函數默認將信標對象的當前可用資源數減1,但線(xiàn)程可能一次使用多個(gè)資源,這就可能出現問(wèn)題了。為避免問(wèn)題出現,應該遵守一個(gè)線(xiàn)程只使用一個(gè)資源的原則。
6、消息隊列
Windows CE.NET允許一個(gè)應用程序或驅動(dòng)程序創(chuàng )建自己的消息隊列。消息隊列既可以作為在線(xiàn)程之間傳遞數據的工具,也可以作為線(xiàn)程之間同步的工具。它的優(yōu)點(diǎn)是需要很小的內存,一般只用于點(diǎn)到點(diǎn)的通信。
和消息隊列相關(guān)的函數:
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函數創(chuàng )建一個(gè)消息隊列,傳遞一個(gè)MSGQUEUEOPTIONS結構指針。在這個(gè)結構中設置標志(允許隊列緩沖區動(dòng)態(tài)改變大小,允許直接讀或者寫(xiě)操作而不管之前是否有過(guò)寫(xiě)操作或讀操作)、隊列允許的最大消息數、隊列屬性(只讀或者只寫(xiě))。使用WriteMsgQueue函數把一個(gè)消息寫(xiě)入到消息隊列中。傳遞一個(gè)消息隊列的緩沖區、消息數據的大小、寫(xiě)入緩沖區的超時(shí)值、標志。使用ReadMsgQueue函數把一個(gè)消息從消息隊列中讀出。使用CloseMsgQueue函數關(guān)閉消息隊列緩沖區。使用OpenMsgQueue函數能夠打開(kāi)其它進(jìn)程中創(chuàng )建的消息隊列。另外可以用等待函數等待消息隊列的變化。當消息隊列由沒(méi)有消息到有消息時(shí),或由滿(mǎn)消息到不滿(mǎn)消息時(shí)喚醒調用等待函數的線(xiàn)程。關(guān)于消息隊列我并沒(méi)有實(shí)驗過(guò),MSDN上有幾個(gè)簡(jiǎn)單的例子。
評論