同步的意思是,保證一個程式在被不適宜的切換時,不會出現問題。
對Window3.1來講,雖然有多任務,但是沒有同步基層。因為這些多任務的協作是通過調用API函數,比如(GetMessage和PeekMessage)來實現。如果一個程式調用了GetMessage或PeekMessage,則意思就是說,我現在處於可中斷狀態。
Win32程式沒有這樣的協作多任務,他們必須做好隨時被CPU切換掉的準備。一個真正的Win32程式不應該耗盡CPU時間去等待某些事情的發生。
Win32API有四個主要的同步對象:(1)Event 事件;(2)Semaphore 訊號量;(3)Mutexes 互斥;(4)Critical Section 臨界區。
除Critical Setion外,其餘是系統全域對象,並且與不同進程及相同進程中的線程一起工作,這樣同步機制也可以用於進程同步。
1。事件(Event)
這是同步對象的一種類型類型,正如其名字含義,在這個中心周圍是一些發生在另一個進程或線程中的特殊活動。當你希望線程暫時掛起時,不會消耗CPU的工作周期。事件類別似我們常用的訊息概念。如果我們剖析訊息的核心,肯定能發現,它就是用事件來實現的。這裡解釋一下事件與訊息的區別:
事件實際上就是訊息的到達,也就是說一個訊息經過一系列的過程到達了,它就會觸發一個事件處理器,事件處理器就會調用你寫的事件處理函數。而訊息就是發訊息給系統,系統會有訊息佇列。然後系統根據一定的調度會取到等待處理的訊息(當然有可能丟失)來調用訊息相應函數。雖然效果一樣,但是事件系統顯然跟安全,因為不會丟失訊息。
程式可用CreateEvent或OpenEvent對事件獲得一個控制代碼:
HANDLE CreateEvent (
LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
BOOL bManualReset, // reset type
BOOL bInitialState, // initial state
LPCTSTR lpName // object name
);
HANDLE OpenEvent(
DWORD dwDesiredAccess, // access
BOOL bInheritHandle, // inheritance option
LPCTSTR lpName // object name
);
函數參數和傳回值解釋請參考MSDN,以下相同。
然後,該程式再調用WaitForSingleObject,選定事件控制代碼和等待逾時時間。那麼線程就會被掛起,一直到其他線程調用下面API函數,給出事件有關訊號後才再次被啟用。
BOOL SetEvent(
HANDLE hEvent // handle to event
);
BOOL PulseEvent(
HANDLE hEvent // handle to event object
);
比如,當一個線程要使用另一個線程的排序結果時,你或許希望去使用一個事件。比較糟糕的方法是執行這個線程,並在結束時設定全域變數標誌,另一個線程迴圈檢查這個標誌是否已設定,這就浪費很多CPU時間。用事件作同樣的事情則很簡單,排序線程在結束時產生一個事件,其他線程調用WaitForSingleObject。這就使得線程被掛起。當排序線程完成時,調用SetEvent喚醒另一個線程繼續執行。
除了WaitForSingleObject外,還有WaitForMultipleObjects,允許一個線程被掛起,直到滿足多個Event條件。
舉例說明。
音視頻通訊過程中,我們用一個TCP Socket,m_hDataSock接收資料,在沒有資料到達時,接收線程會被掛起,直到有資料到達或者Socket逾時,來進行相應處理,樣本方法如下:
UINT RecvDataThread(LPVOID pPara)
{
WSAEVENT = WSACreateEvent();
WSAEventSelect(m_hDataSock,m_hEvent,FD_READ | FD_CLOSE);
while(!m_bThreadEnd)
{
DWORD dwWait = WSAWaitForMultipleEvents(1,&m_hEvent,FALSE,18000,FALSE);
if (WSA_WAIT_TIMEOUT == dwWait)
{
//逾時處理
break;
}
if(WAIT_OBJECT_0 == dwWait)
{
WSANETWORKEVENTS netEvents;
if(SOCKET_ERROR == WSAEnumNetworkEvents(m_hDataSock,m_hEvent,&netEvents))
{
continue;
}
if((netEvents.lNetworkEvents & FD_READ) && (0 == netEvents.iErrorCode[FD_READ_BIT]))
{
//接收資料
}
else if(netEvents.lNetworkEvents & FD_CLOSE)
{
//處理通道關閉
break;
}
}
}
WSACloseEvent(m_hEvent);
_endthreadex(0);
return 0;
}
2。訊號量
當需要限制訪問特殊資源或限制一段代碼到某些線程是,Semaphores非常有用。比如說,一樣資源有十個,當你需要用時,已經被其他十個人佔用了。這樣就必須等待,直到有人不用了,歸還了資源。
在Win32編程中獲得Semaphores就好像獲得該資源的一次控制。
為了利用Semaphores,一個線程調用CreateSemaphore去獲得一個HANDLE給Semaphores。也就是將Semaphores與資源綁定,並初始化該Semaphores,並返回該Semaphores的控制代碼。
函數原型如下:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD
LONG lInitialCount, // initial count
LONG lMaximumCount, // maximum count
LPCTSTR lpName // object name
);
如果Semaphores在其他進程中建立,可以用OpenSemaphore去擷取其控制代碼。
HANDLE OpenSemaphore(
DWORD dwDesiredAccess, // access
BOOL bInheritHandle, // inheritance option
LPCTSTR lpName // object name
);
接下來當然是利用等待函數來阻塞線程。如果這個Semaphore計數大於0,這等待功能只是簡單處理Semaphores的使用數,線程繼續執行,換句話說,如果Semaphores使用數超出最大值,則等待線程被掛起。當然也可以利用ReleaseSemaphore來釋放資源。
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // handle to semaphore
LONG lReleaseCount, // count increment amount
LPLONG lpPreviousCount // previous count
);
也就是用訊號量這個對象來管理某個資源的分配與回收。
3。互斥(Mutexes)
Mutex是“mutual exclusion”的縮寫。希望一次只有一個線程去訪問一個資源或一段代碼時可以使用互斥。使用方法與訊號量類似。建立和釋放Mutex的函數原型如下:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // SD
BOOL bInitialOwner, // initial owner
LPCTSTR lpName // object name
);
BOOL ReleaseMutex(
HANDLE hMutex // handle to mutex
);
可以將使用方法封裝成類,如下:
class CMutex
{
public:
CMutex(HANDLE hMutex){m_hMutex = hMutex; WaitForSingleObject(m_hMutex,INFINITE);}
virtual ~CMutex(){ReleaseMutex(m_hMutex);}
private:
HANDLE m_hMutex;
};
使用的時候首先聲明一個HANDLE m_hMutex;調用介面建立Mutex,m_hMutex = CreateMutex(NULL,FALSE,NULL);然後再任何需要互斥的代碼前構造這樣一個類就可以了。比如,CMutex mutex(m_hMutex);
4。臨界區(Critical Sections)
臨界段相當於一個微型的互斥,只能被同一個進程中的線程使用。臨界區是為了防止多線程同時執行一段代碼。相對其他同步機而言,臨界區相對簡單和易用。一般先聲明一個CRITICAL_SECTION類型的全域變數,然後調用下面三個函數來使用它。
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
VOID EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
VOID LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
5。WaitForSingleObject/WaitForMultipleObjects函數
其實,線程同步,除了上面四種方法外,還可以使用WaitForSingleObject/WaitForMultipleObjects函數。等待的HANDLE可以是線程的控制代碼,檔案的控制代碼等。