天天看點

線程同步機制的差別與比較及程序通信方法

http://hi.baidu.com/wobash/blog/item/4c1de9464899c40f6a63e500.html

線程同步機制的差別與比較及程序通信方法

2008-08-29 14:07

有關多線程的一些技術問題:

1、   何時使用多線程?

2、   線程如何同步?

3、   線程之間如何通訊?

4、   程序之間如何通訊?

先來回答第一個問題,線程實際主要應用于四個主要領域,當然各個領域之間不是絕對孤立的,他們有可能是重疊的,但是每個程式應該都可以歸于某個領域:

1、   offloading time-consuming task。由輔助線程來執行耗時計算,而使GUI有更好的反應。我想這應該是我們考慮使用線程最多的一種情況吧。

2、   Scalability。伺服器軟體最常考慮的問題,在程式中産生多個線程,每個線程做一份小的工作,使每個CPU都忙碌,使CPU(一般是多個)有最佳的使用率,達到負載的均衡,這比較複雜,我想以後再讨論這個問題。

3、   Fair-share resource allocation。當你向一個負荷沉重的伺服器送出請求,多少時間才能獲得服務。一個伺服器不能同時為太多的請求服務,必須有一個請求的最大個數,而且有時候對某些請求要優先處理,這是線程優先級幹的活了。

4、   Simulations。線程用于仿真測試。

我把主要的目光放在第一個領域,因為它正是我想要的。第二和第三個領域比較有意思,但是目前不在我的研究時間表中。

線程的同步機制:

1、   Event

用事件(Event)來同步線程是最具彈性的了。一個事件有兩種狀态:激發狀态和未激發狀态。也稱有信号狀态和無信号狀态。事件又分兩種類型:手動重置事件和自動重置事件。手動重置事件被設定為激發狀态後,會喚醒所有等待的線程,而且一直保持為激發狀态,直到程式重新把它設定為未激發狀态。自動重置事件被設定為激發狀态後,會喚醒“一個”等待中的線程,然後自動恢複為未激發狀态。是以用自動重置事件來同步兩個線程比較理想。MFC中對應的類為CEvent.。CEvent的構造函數預設建立一個自動重置的事件,而且處于未激發狀态。共有三個函數來改變事件的狀态:SetEvent,ResetEvent和PulseEvent。用事件來同步線程是一種比較理想的做法,但在實際的使用過程中要注意的是,對自動重置事件調用SetEvent和PulseEvent有可能會引起死鎖,必須小心。

2、   Critical Section

使用臨界區域的第一個忠告就是不要長時間鎖住一份資源。這裡的長時間是相對的,視不同程式而定。對一些控制軟體來說,可能是數毫秒,但是對另外一些程式來說,可以長達數分鐘。但進入臨界區後必須盡快地離開,釋放資源。如果不釋放的話,會如何?答案是不會怎樣。如果是主線程(GUI線程)要進入一個沒有被釋放的臨界區,呵呵,程式就會挂了!臨界區域的一個缺點就是:Critical Section不是一個核心對象,無法獲知進入臨界區的線程是生是死,如果進入臨界區的線程挂了,沒有釋放臨界資源,系統無法獲知,而且沒有辦法釋放該臨界資源。這個缺點在互斥器(Mutex)中得到了彌補。Critical Section在MFC中的相應實作類是CcriticalSection。CcriticalSection::Lock()進入臨界區,CcriticalSection::UnLock()離開臨界區。

3、   Mutex

互斥器的功能和臨界區域很相似。差別是:Mutex所花費的時間比Critical Section多的多,但是Mutex是核心對象(Event、Semaphore也是),可以跨程序使用,而且等待一個被鎖住的Mutex可以設定TIMEOUT,不會像Critical Section那樣無法得知臨界區域的情況,而一直死等。MFC中的對應類為CMutex。Win32函數有:建立互斥體CreateMutex() ,打開互斥體OpenMutex(),釋放互斥體ReleaseMutex()。Mutex的擁有權并非屬于那個産生它的線程,而是最後那個對此Mutex進行等待操作(WaitForSingleObject等等)并且尚未進行ReleaseMutex()操作的線程。線程擁有Mutex就好像進入Critical Section一樣,一次隻能有一個線程擁有該Mutex。如果一個擁有Mutex的線程在傳回之前沒有調用ReleaseMutex(),那麼這個Mutex就被舍棄了,但是當其他線程等待(WaitForSingleObject等)這個Mutex時,仍能傳回,并得到一個WAIT_ABANDONED_0傳回值。能夠知道一個Mutex被舍棄是Mutex特有的。

4、   Semaphore

信号量是最具曆史的同步機制。信号量是解決producer/consumer問題的關鍵要素。對應的MFC類是Csemaphore。Win32函數CreateSemaphore()用來産生信号量。ReleaseSemaphore()用來解除鎖定。Semaphore的現值代表的意義是目前可用的資源數,如果Semaphore的現值為1,表示還有一個鎖定動作可以成功。如果現值為5,就表示還有五個鎖定動作可以成功。當調用Wait…等函數要求鎖定,如果Semaphore現值不為0,Wait…馬上傳回,資源數減1。當調用ReleaseSemaphore()資源數加1,當時不會超過初始設定的資源總數。

線程之間的通訊:

線程常常要将資料傳遞給另外一個線程。Worker線程可能需要告訴别人說它的工作完成了,GUI線程則可能需要交給Worker線程一件新的工作。

通過PostThreadMessage(),可以将消息傳遞給目标線程,當然目标線程必須有消息隊列。以消息當作通訊方式,比起标準技術如使用全局變量等,有很大的好處。如果對象是同一程序中的線程,可以發送自定義消息,傳遞資料給目标線程,如果是線程在不同的程序中,就涉及程序之間的通訊了。下面将會講到。

程序之間的通訊:

當線程分屬于不同程序,也就是分駐在不同的位址空間時,它們之間的通訊需要跨越位址空間的邊界,便得采取一些與同一程序中不同線程間通訊不同的方法。

1、   Windows專門定義了一個消息:WM_COPYDATA,用來線上程之間搬移資料,――不管兩個線程是否同屬于一個程序。同時接受這個消息的線程必須有一個視窗,即必須是UI線程。WM_COPYDATA必須由SendMessage()來發送,不能由PostMessage()等來發送,這是由待發送資料緩沖區的生命期決定的,出于安全的需要。

2、   WM_COPYDATA效率上面不是太高,如果要求高效率,可以考慮使用共享記憶體(Shared Memory)。使用共享記憶體要做的是:設定一塊記憶體共享區域;使用共享記憶體;同步處理共享記憶體。

第一步:設定一塊記憶體共享區域。首先,CreateFileMapping()産生一個file-mapping核心對象,并指定共享區域的大小。MapViewOfFile()獲得一個指針指向可用的記憶體。如果是C/S模式,由Server端來産生file-mapping,那麼Client端使用OpenFileMapping(),然後調用MapViewOfFile()。

第二步:使用共享記憶體。共享記憶體指針的使用是一件比較麻煩的事,我們需要借助_based屬性,允許指針被定義為從某一點開始起算的32位偏移值。

第三步:清理。UnmapViewOfFile()交出由MapViewOfFile()獲得的指針,CloseHandle()交出file-mapping核心對象的handle。

第四步:同步處理。可以借助Mutex來進行同步處理。

3、   IPC

1)Anonymous Pipes。Anonymous Pipes隻被使用于點對點通訊。當一個程序産生另一個程序時,這是最有用的一種通訊方式。

2)Named Pipes。Named Pipes可以是單向,也可以是雙向,并且可以跨越網絡,步局限于單機。

3)Mailslots。Mailslots為廣播式通訊。Server程序可以産生Mailslots,任何Client程序可以寫資料進去,但是隻有Server程序可以取資料。

4)OLE Automation。OLE Automation和UDP都是更高階的機制,允許通訊發生于不同程序間,甚至不同機器間。

5)DDE。DDE動态資料交換,使用于16位Windows,目前這一方式應盡量避免使用。

繼續閱讀