1、線程概述
WinCE是有優先級的多任務操作系統,它允許重功能、進程在相同時間的系統中運行,WinCE支持最大的32位同步進程。一個進程包括一個或多個線程,每個線程代表進程的一個獨立部分,而一個線程被指定為進程的基本線程。
WinCE以搶先方式來調度線程。線程以“時間片”為單位來運行,WinCE的“時間片”通常為25毫秒。過來那個時間后,如果線程沒有放棄它的時間片,并且線程并不緊急,系統就會掛起線程并調度另一個線程來運行。WinCE將根據優先級方法來決定要運行的線程,高優先級的線程將在低優先級的線程前面調度。
2、線程API函數
2.1 創建線程
WinCE提供了CreateThread函數來創建線程,其聲明如下: HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, //線程安全指針,不支持 DWORD dwStackSize, //為自己所使用堆棧分配的地址空間大小,不支持 LPTHREAD_START_ROUTINE lpStartAddress, //線程函數地址 LPVOID lpParameter, //傳入線程函數的參數 DWORD dwCreationFlags, //控制線程創建的附加標志 LPDWORD lpThreadId//新線程的ID值 );
WinCE不支持lpThreadAttributes和dwStackSize參數,將它們設置成NULL和0即可。lpStartAddress指向線程函數的地址;lpParameter被傳遞到線程中的參數;dwCreationFlags線程創建參數,可以設置成0或CREATE_SUSPENDED,如果為0,表示線程立即執行,如果參數為CREATE_SUSPENDED,則被創建的線程將處于掛起狀態,而且必須要調用ResumeThread函數將其喚醒。
2.2掛起和恢復線程
正在運行的線程可以被掛起、暫停執行。同他使用SuspendThead函數即可實現以上功能,該函數的聲明如下:
DWORD SuspendThread( HANDLE hThread );
參數hThead代表要掛起線程的句柄。由于SuspendThread函數的調用將增加掛起計數,因此在實際調度線程運行之前,對SuspendThread函數的多次調用必須與對ResumeThread函數的多次調用相匹配。ResumeThread函數的定義
DWORD ResumeThread( HANDLE hThread );
參數hThead同樣代表要恢復線程的句柄。
3、線程同步
在使用線程時,會經常遇到兩個概念,即線程沖突和線程死鎖。
線程沖突:如果線程A讀寫數據G,線程B也正在讀取數據G,那么很顯然,該操作將導致數據沖突,引起數據混亂。這里需要使用同步技術,以保證線程A和線程B依次讀寫數據G,避免數據沖突。
線程死鎖:例如A工人為加工III零件在等待B提供的I零件,而B正好在等待應由A加工提供的II零件來裝配I零件。由于他們之間再沒有其他的任何人幫助通信或其他通信手段。所以他們一直在等對方的零件而進入死鎖狀態。死鎖屬于邏輯錯誤,無法通過線程同步來解決。
WinCE實現線程同步的常用方法:事件(Event)、互斥(Mutex)、信號量(Semaphore)、臨界區(CriticalSection)。
3.1 利用事件同步
“事件對象”是實現線程同步最基本的方法之一,一個事件對象可以處于“已標示”和“未標示”兩種狀態,如果將事件對象設置為“已標示”狀態,表示可以執行同步操作,事件對象處于“未標示”狀態,則表示需要等待事件對象變為“已標示”狀態才可以進行同步操作。下面介紹利用事件同步所需要的API函數。
(1)CreateEvent函數。創建事件對象函數CreateEvent,其聲明如下:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,//CE不支持,設為NULL
BOOL bManualReset, //設置是否手動設置事件對象狀態
BOOL bInitialState, //事件對象初始狀態
LPTSTR lpName //事件對象名稱
);
參數bManualReset表示是否手動設置事件對象狀態,當其值為TRUE時,在調用完等待函數(WaitForSingObject,WaitForMutipleObject)后,則必須調用ResetEvent函數,以設置事件對象沒有被標示,當其值為FALSE時,系統調用完等待函數,會自動將事件對象設置為未標示狀態。
參數bInitialState表示事件對象初始狀態,當其值為TRUE是,事件對象初始化狀態為已標示,當其值為FALSE時,事件對象初始狀態為未標示。
如果創建事件函數對象CreateEvent執行成功,將返回事件對象句柄。若失敗,則返回0,在不用事件句柄時,需要使用CloseHandle()將其關閉,以釋放資源。
(2)SetEvent函數和ResetEvent函數。函數SetEvent()的功能是將事件對象設置為已標示狀態。該函數的聲明如下:
BOOL SetEvent(HANDLE hEvent);
參數hEvent表示事件對象句柄。
函數ResetEvent函數功能將事件對象設置成未標示狀態,該函數的聲明如下:
BOOL ResetEvent(HANDLE hEvent);
(3) 使用事件同步的一般使用流程
通常情況,在主線程中,用戶利用CreateEvent函數創建一個事件對象,并且將參數bManualReset設為FALSE,參數bInitialState也設為FALSE,此時事件對象狀態未標示。然后在線程里通過WaitForSingleObject函數來等待事件被標示。此時,只要在主線程中調用SetEvent函數,將事件對象設置成已標示。那么線程里的WaitForSingleObject函數便會返回,繼續執行,同時將事件對象狀態設置成未標示。
3.2 利用互斥同步
互斥同步類似于事件對象同步。互斥同步也將創建一個互斥對象,該互斥對象也有“被線程擁有”和“不被線程擁有”兩種狀態;當互斥對象處于“不被線程擁有”狀態,表示可以執行相關操作;當互斥對象處于“被線程擁有”狀態,表示此時不可以執行相關操作。通過等待函數請求互斥對象實現同步。
(1)CreateMutex函數。通過CreateMutex函數創建互斥對象,該函數定義如下:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //CE不支持
BOOL bInitialOwner, //初始化擁有狀態,TRUE表示擁有,FALSE表示未被擁有
LPCTSTR lpName //互斥名稱
);
如果創建互斥函數對象CreateMutex執行成功,將返回互斥對象句柄。若失敗,則返回ERROR_INVALID_HANDLE,在不用互斥句柄時,需要使用CloseHandle()將其關閉,以釋放資源。
(2)ReleaseMutex函數。在使用等待函數請求互斥對象時,如果請求到互斥對象的擁有權,則等待函數將自動設置互斥對象狀態為“未被擁有”。ReleaseMutex函數負責釋放某個線程對象互斥對象的擁有權,也就是將互斥對象設置為“未被線程擁有”狀態。ReleaseMutex函數定義如下:
BOOL ReleaseMutex( HANDLE hHandle);hHandle表示互斥對象句柄;
(3)利用互斥同步的一般使用流程
利用互斥同步的一般使用流程是:首先利用CreateMutex函數創建互斥對象,并將CreateMutex中的參數bInitialOwer設置為FALSE,使互斥對象處于“未被線程擁有”狀態。然后利用WaitForObject等待互斥對象,執行相關操作。處理完成后,利用ReleaseMutex函數釋放線程對互斥對象的擁有權。當所有線程執行完畢后,需要使用CloseHandle()將其關閉。
3.3 利用臨界區同步
“臨界區”是進行線程同步的另一種方法,它能夠阻止兩個或多個不同的線程在同一時間內訪問同一個代碼區域。它通過調用 EnterCriticalSection函數來指出已經進入代碼的臨界區,如果另一線程也調用了EnterCritialSection函數,并且參數指向同一臨界區對象,那么另一線程將阻塞,直到第一個線程調用了LeaveCriticalSection函數離開臨界區為止。
臨界區同步所需要的API函數:
(1) InitializeCriticalSection函數。如果要使用臨界區,首先要使用InitializeCriticalSection函數創建臨界區,該函數定義如下:
void InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
(2) DeleteCriticalSection 函數,當結束使用臨界區對象時,必須調用DeleteCriticalSection 函數釋放臨界區對象所占有的資源。該函數定義如下:
void DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
(3)EnterCriticalSection函數,在創建了臨界區對象后,需要調用EnterCriticalSection函數進入臨界區,以保護代碼,該函數定義如下:
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
如果臨界區對象已經屬于另一個線程,那么此函數將阻塞直到另一線程離開臨界區才返回。
(4)LeaveCriticalSection函數。如果要離開臨界區,只需要調用LeaveCriticalSection函數即可。該函數定義如下:
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
3.4 利用信號量同步
信號量是建立在互斥基礎上,并增加了資源計數的功能。它允許預定數目的線程同時進入要同步的代碼。通過設置信號量計數為1,只允許一個線程同時訪問同步代碼,而實現線程同步。信號量同步所需要的API函數:
(1) CreateSemaphore函數。在使用信號量實現同步時,需要調用CreateSemaphore函數創建信號量對象。該函數定義如下:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //CE不支持
LONG lInitialCount, //信號量初始化計數值
LONG lMaximumCount, //信號量計數最大值
LPCTSTR lpName //信號量對象名稱
);
如創建信號量成功,函數返回信號量對象句柄,否則返回NULL值。
(2)ReleaseSemaphore函數。在使用等待函數請求信號量時,等待函數自動給信號量計數減1,那么當計數減到0時,信號量對象將不能被請求。ReleaseSemaphore函數負責給信號量計數加值,使信號量可以被請求。此函數定義如下:
BOOL ReleaseSemaphore(
HANDLE hSemaphore, //信號量句柄
LONG lReleaseCount, //信號量計數增加的值
LPLONG lpPreviousCount //輸出量,表示上一次信號量計數
);
3.5 利用互鎖函數可對變量和指針進行原子的讀/寫操作。因為它們不需要額外的同步對象,所以有時這些互鎖函數特別有用。Windows ce提供的互鎖函數有:
InterlockedIncrement //把一個變量的值加1 InterlockedDecrement InterlockedExchange //交換兩個變量的值 InterlockedTestExchange //根據條件交換變量的值 InterlockedCompareExchange //根據比較原子交換 InterlockedCompareExchangePointer //根據比較原子交換指針 InterlockedExchangePointer //交換兩個指針的值 InterlockedExchangeAdd //給某個變量嗇某個特定值