概述
任何單個應用程序都不能完全使該處理器達到滿負荷。當一個線程遇到較長等待時間事件時,同步多線程還允許另一線程中的指令使用所有執行單元。例如,當一個線程發生高速緩存不命中,另一個線程可以繼續執行。同步多線程是 POWER5™ 和 POWER6™ 處理器的功能,可與共享處理器配合使用。
SMT 對於商業事務處理負載的性能優化可達30%。在更加注重系統的整體吞吐量而非單獨線程的吞吐量時,SMT 是一個很好地選擇。
但是並非所有的應用都能通過SMT 取得性能優化。那些性能受到執行單元限制的應用,或者那些耗盡所有處理器的內存帶寬的應用,其性能都不會通過在同一個處理器上執行兩個線程而得到提高。
盡管SMT 可以使系統識別到雙倍於物理CPU數量的邏輯CPU(lcpu),但是這並不意味着系統擁有了兩倍的CPU能力。
SMT技術允許內核在同一時間運行兩個不同的進程,以此來壓縮多任務處理時所需要的總時間。這么做有兩個好處,其一是提高處理器的計算性能,減少用戶得到結果所需的時間;其二就是更好的能效表現,利用更短的時間來完成任務,這就意味着在剩下的時間里節約更多的電能消耗。當然這么做有一個總前提——保證SMT不會重復HT所犯的錯誤,而提供這個擔保的則是在酷睿微架構中表現非常出色的分支預測設計。[1]
編輯本段同步多線程的同步機制
1、 Event
用事件(Event)來同步線程是最具彈性的了。一個事件有兩種狀態:激發狀態和未激發狀態。也稱有信號狀態和無信號狀態。事件又分兩種類型:手動重置事件和自動重置事件。手動重置事件被設置為激發狀態后,會喚醒所有等待的線程,而且一直保持為激發狀態,直到程序重新把它設置為未激發狀態。自動重置事件被設置為激發狀態后,會喚醒“一個”等待中的線程,然后自動恢復為未激發狀態。所以用自動重置事件來同步兩個線程比較理想。MFC中對應的類為CEvent.。CEvent的構造函數默認創建一個自動重置的事件,而且處於未激發狀態。共有三個函數來改變事件的狀態:SetEvent,ResetEvent和PulseEvent。用事件來同步線程是一種比較理想的做法,但在實際的使用過程中要注意的是,對自動重置事件調用SetEvent和PulseEvent有可能會引起死鎖,必須小心。
多線程同步-event
在所有的內核對象中,事件內核對象是個最基本的。它包含一個使用計數(與所有內核對象一樣),一個BOOL值(用於指明該事件是個自動重置的事件還是一個人工重置的事件),還有一個BOOL值(用於指明該事件處於已通知狀態還是未通知狀態)。事件能夠通知一個線程的操作已經完成。有兩種類型的事件對象。一種是人工重置事件,另一種是自動重置事件。他們不同的地方在於:當人工重置的事件得到通知時,等待該事件的所有線程均變為可調度線程。當一個自動重置的事件得到通知時,等待該事件的線程中只有一個線程變為可調度線程。
當一個線程執行初始化操作,然后通知另一個線程執行剩余的操作時,事件使用得最頻繁。在這種情況下,事件初始化為未通知狀態,然后,當該線程完成它的初始化操作后,它就將事件設置為已通知狀態,而一直在等待該事件的另一個線程在事件已經被通知后,就變成可調度線程。
當這個進程啟動時,它創建一個人工重置的未通知狀態的事件,並且將句柄保存在一個全局變量中。這使得該進程中的其他線程能夠非常容易地訪問同一個事件對象。程序一開始創建了三個線程,這些線程在初始化后就被掛起,等待事件。這些線程要等待文件的內容讀入內存,然后每個線程都會訪問這段文件內容。一個線程進行單詞計數,另一個線程運行拼寫檢查,第三個線程運行語法檢查。這3個線程函數的代碼的開始部分都相同,每個函數都調用WaitForSingleObject.,這將使線程暫停運行,直到文件的內容由主線程讀入內存為止。一旦主線程將數據准備好,它就調用SetEvent,給事件發出通知信號。這時,系統就使所有這3個輔助線程進入可調度狀態,它們都獲得了C P U時間,並且可以訪問內存塊。這3個線程都必須以只讀方式訪問內存,否則會出現內存錯誤。這就是所有3個線程能夠同時運行的唯一原因。如果計算機上配有三個以上CPU,理論上這個3個線程能夠真正地同時運行,從而可以在很短的時間內完成大量的操作
如果你使用自動重置的事件而不是人工重置的事件,那么應用程序的行為特性就有很大的差別。當主線程調用S e t E v e n t之后,系統只允許一個輔助線程變成可調度狀態。同樣,也無法保證系統將使哪個線程變為可調度狀態。其余兩個輔助線程將繼續等待。已經變為可調度狀態的線程擁有對內存塊的獨占訪問權。
讓我們重新編寫線程的函數,使得每個函數在返回前調用S e t E v e n t函數(就像Wi n M a i n函數所做的那樣)。
當主線程將文件內容讀入內存后,它就調用SetEvent函數,這樣操作系統就會使這三個在等待的線程中的一個成為可調度線程。我們不知道系統將首先選擇哪個線程作為可調度線程。當該線程完成操作時,它也將調用S e t E v e n t函數,使下一個被調度。這樣,三個線程會以先后順序執行,至於什么順序,那是操作系統決定的。所以,就算每個輔助線程均以讀/寫方式訪問內存塊,也不會產生任何問題,這些線程將不再被要求將數據視為只讀數據。
這個例子清楚地展示出使用人工重置事件與自動重置事件之間的差別。
P u l s e E v e n t函數使得事件變為已通知狀態,然后立即又變為未通知狀態,這就像在調用S e t E v e n t后又立即調用R e s e t E v e n t函數一樣。如果在人工重置的事件上調用P u l s e E v e n t函數,那么在發出該事件時,等待該事件的任何一個線程或所有線程將變為可調度線程。如果在自動重置事件上調用P u l s e E v e n t函數,那么只有一個等待該事件的線程變為可調度線程。如果在發出事件時沒有任何線程在等待該事件,那么將不起任何作用[2]。
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,當然不會超過初始設定的資源總數。
編輯本段有關多線程的一些技術問題
1、 何時使用多線程?
2、 線程如何同步?
3、 線程之間如何通訊?
4、 進程之間如何通訊?
先來回答第一個問題,線程實際主要應用於四個主要領域,當然各個領域之間不是絕對孤立的,他們有可能是重疊的,但是每個程序應該都可以歸於某個領域:
1、 offloading time-consuming task。由輔助線程來執行耗時計算,而使GUI有更好的反應。我想這應該是我們考慮使用線程最多的一種情況吧。
2、 Scalability。服務器軟件最常考慮的問題,在程序中產生多個線程,每個線程做一份小的工作,使每個CPU都忙碌,使CPU(一般是多個)有最佳的使用率,達到負載的均衡,這比較復雜,我想以后再討論這個問題。
3、 Fair-share resource allocation。當你向一個負荷沉重的服務器發出請求,多少時間才能獲得服務。一個服務器不能同時為太多的請求服務,必須有一個請求的最大個數,而且有時候對某些請求要優先處理,這是線程優先級干的活了。
4、 Simulations。線程用於仿真測試。
編輯本段線程之間的通訊
線程常常要將數據傳遞給另外一個線程。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,目前這一方式應盡量避免使用。[3]
內容來自網絡資源:http://baike.baidu.com/view/2808915.htm