進程與線程雜談(二)

上次說了線程的作用,顯然,要使得線程之間能夠通訊才能利用好線程,否則,每個線程都各幹個的,向一盤散沙,程序就什麽也做不了兒了。現在,我們來看看線程間如何通訊。

線程間通訊的方法有很多,常用的有:變量、臨界段、Windows消息、事件。

首先來討論變量。既然線程都處于同一個進程內,它們的地址空間就是相同的,對于完全依賴地址空間的變量來說,當然可以被同在一個進程中的任意一個線程訪問,因而就可以用來通訊。

比如,有一個全局變量,兩個線程;我們希望第一個線程在工作,而第二個線程等待,當第一個線程檢測的某件事發生時,通知第二個線程,使第二個線程開始運行。可以將全局變量置爲0,然後讓第二個線程進入一個「死循環」,等待全局變量變爲1。而第一個線程執行他的任務,當事件發生時,第一個線程將全局變量賦值爲1,于是第二個線程便奇妙的結束了他的死循環,開始執行預定的工作。

由此可見,變量確實可以進行線程間的通訊;但不是使用上面的方法(該方法被稱爲「循環鎖」),因爲它太拙劣了,第二個線程在等待中要消耗大量的CPU時間,卻不作任何事。而且,它不能處理複雜的情況,例如:

還是上兩個線程,第一個線程分發任務,第二個線程執行任務;第一個線程每次給全局變量加上一個需執行的任務的數目,第二個線程每次從全局變量中減掉它完成的任務的數目。兩個運算都是「全局變量 op 任務數目 → 全局變量」,假設線程一先讀取全局變量,是n,然後加上新任務數目,結果是n+p;而就在同時,線程二也讀取了全局變量,由于線程一還沒來得及將結果寫回,線程二讀到的也是n,減去完成的任務數,結果是n-q;現在,兩個線程都要將結果寫回到全局變量,顯然這裏有問題,最終結果要麽是n+p、要麽是n-q,總之不是正確的n+p-q。

問題就出在那個「同時」上,如果讓線程二等線程一把結果寫回全局變量再讀取、減去、寫回,錯誤就不會發生。

Windows提供了一個專門針對變量通訊的同步方法——互鎖函數。這是一組形如Interlocked???()的函數。每個函數都可以對一個變量進行一種特定的操作,在操作進行中,能夠自動保持與其它線程同步——每個函數都執行一種「原子操作」——該操作不能與共用同一資源的其它操作同時進行。

例如,上面的例子,通過使用InterlockedExchangeAdd()來執行加減操作,便可以保證對全局變量的操作不會同時發生。

有時,線程間的通訊過程不像上述的那樣簡單、僅僅是做一次加減法,而是由許多步組成的。比如,線程一除了加上任務數外,還要填寫每個任務的具體參數,線程一進而希望在自己填寫完全部所需數據後,線程二再對它們修改,原因類似。這是可以使用臨界段。

CRITICAL_SECTION類型聲明一個臨界段結構變量,然後用InitializeCriticalSection()初始化它。EnterCriticalSection()和LeaveCriticalSection()兩個API分別指示進入或退出臨界段,參數是一個已經定義的臨界段。在臨界段內的一組操作都是原子的,它們不能與共用同一臨界段的另外一組操作同時進行。

與互鎖函數的不同是,互鎖函數僅涉及一個共享資源,執行一個操作,因而沒有該保護哪些資源、保護多長時間的問題;臨界段涉及多個資源,執行多個操作,因而需要一個變量來代表對一類資源和操作的保護。

Windows的消息機制就是用來通訊的,它本身就支持線程間通訊。消息一般是基于窗口的,而窗口是屬于創建它的線程的。兩個屬于不同線程的窗口可以通過Windows消息來通訊。例如上面線程二等待的情況就可以讓線程二調用GetMessage()等待消息,當線程一使用PostMessage()將消息發送給線程二時,GetMessage()將返回,線程二可以執行相應的任務。消息的兩個參數可以用來攜帶對任務的描述。即便一個線程沒有窗口,其它線程也可以使用PostThreadMessage()將消息直接發給該線程。使用Windows消息來通訊,是一對一的進行的,在某些場合,可能需要一對多、多對一或多對多的通訊,這時,可以借助事件(Event)來完成。CreateEvent()創建一個事件,事件有兩種狀態——已觸發和未觸發。SetEvent()用來觸發一個事件,ResetEvent()用來恢複事件到未觸發狀態。一組「等待函數」用來檢測事件的狀態,例如WaitForSingleObject()等待一個事件,直到事件的狀態爲已觸發時才返回。與上面Windows消息的例子類似,它也可以用于讓線程二等待,更方便的是,如果有另外的線程三做與線程二類似的工作,它可以等待同一個事件,線程一的一次SetEvent()將同時通知兩個線程執行任務;如果有線程零做與線程一類似的工作,它也可以觸發同一個事件,線程零或線程一任意一方觸發事件都將使兩個線程執行任務。

除了上述的幾種方法以外,旗語等方法也是用來進行線程間通訊的,這裏先不作介紹。

當設計線程間的通訊時,一定要時刻記得各個線程之間是異步運行的,必須要使用某種機制才能進行通訊,在實際中,情況可能會非常複雜,如果考慮不全面,結果將是難以預料的。

 
進程與線程雜談(一)
進程與線程雜談(一) --轉載我了解不多,只能說說80x86上的Windows環境,其他如Alpha,Mac或者Linux,我就一竅不通了,見笑見笑……  現代操作系統都是多任務的操作系統,在這裏要澄清一個概念,...查看完整版>>進程與線程雜談(一)
 
WinCE 進程、線程和內存管理(一)
WinCE 進程、線程和內存管理(一)
  進程、線程、內存治理是一個內核最基本的服務,也是一個內核最主要的組成部分。這幾方面的知識是一個軟件開發人員必須把握的基礎知識。雖然一個人不懂這些知識也能編寫簡單的程序,但這樣的程序只能算是皮毛。把...查看完整版>>WinCE 進程、線程和內存管理(一)
 
API之進程和線程函數
  CancelWaitableTimer 這個函數用于取消一個可以等待下去的計時器操作 CallNamedPipe 這個函數由一個希望通過管道通信的一個客戶進程調用 ConnectNamedPipe 指示一台服務器等待下去,直至客戶機同一個命名管道連接...查看完整版>>API之進程和線程函數
 
C#中四種進程或線程同步互斥的控制方法
此文章轉載自 http://bbs.caoyuan.net/viewtopic.php?p=28660很想整理一下自己對進程線程同步互斥的理解。正巧周六一個剛剛回到學校的同學請客吃飯。在吃飯的過程中,有兩個同學,爲了一個問題爭論的面紅耳赤。一個認...查看完整版>>C#中四種進程或線程同步互斥的控制方法
 
四種進程或線程同步互斥的控制方法
  很想整理一下自己對進程線程同步互斥的理解。正巧周六一個剛剛回到學校的同學請客吃飯。在吃飯的過程中,有兩個同學,爲了一個問題爭論的面紅耳赤。一個認爲.Net下的進程線程控制模型更加合理。一個認爲Java下的...查看完整版>>四種進程或線程同步互斥的控制方法