同步的意思是,保證一個程式在被不适宜的切換時,不會出現問題。
對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 (
<b> );</b>
HANDLE OpenEvent(
函數參數和傳回值解釋請參考MSDN,以下相同。
然後,該程式再調用WaitForSingleObject,標明事件句柄和等待逾時時間。那麼線程就會被挂起,一直到其他線程調用下面API函數,給出事件有關信号後才再次被激活。
BOOL SetEvent(
<b> );</b>
BOOL PulseEvent(
比如,當一個線程要使用另一個線程的排序結果時,你或許希望去使用一個事件。比較糟糕的方法是執行這個線程,并在結束時設定全局變量标志,另一個線程循環檢查這個标志是否已設定,這就浪費很多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(
<b> );</b>
如果Semaphores在其他程序中建立,可以用OpenSemaphore去擷取其句柄。
HANDLE OpenSemaphore(
);
接下來當然是利用等待函數來阻塞線程。如果這個Semaphore計數大于0,這等待功能隻是簡單處理Semaphores的使用數,線程繼續執行,換句話說,如果Semaphores使用數超出最大值,則等待線程被挂起。當然也可以利用ReleaseSemaphore來釋放資源。
BOOL ReleaseSemaphore(
<b> );</b>
也就是用信号量這個對象來管理某個資源的配置設定與回收。
3。互斥(Mutexes)
Mutex是“mutual exclusion”的縮寫。希望一次隻有一個線程去通路一個資源或一段代碼時可以使用互斥。使用方法與信号量類似。建立和釋放Mutex的函數原型如下:
HANDLE CreateMutex(
BOOL ReleaseMutex(
可以将使用方法封裝成類,如下:
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(
VOID EnterCriticalSection(
VOID LeaveCriticalSection(
5。WaitForSingleObject/WaitForMultipleObjects函數
其實,線程同步,除了上面四種方法外,還可以使用WaitForSingleObject/WaitForMultipleObjects函數。等待的HANDLE可以是線程的句柄,檔案的句柄等。
本文轉自 fanxiaojun 51CTO部落格,原文連結:http://blog.51cto.com/2343338/1060567,如需轉載請自行聯系原作者