| 導購 | 订阅 | 在线投稿
分享
 
 
 

進程與線程雜談(二)

來源:互聯網  2004-11-11 09:45:53  評論

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

線程間通訊的方法有很多,常用的有:變量、臨界段、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()將同時通知兩個線程執行任務;如果有線程零做與線程一類似的工作,它也可以觸發同一個事件,線程零或線程一任意一方觸發事件都將使兩個線程執行任務。

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

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

上次說了線程的作用,顯然,要使得線程之間能夠通訊才能利用好線程,否則,每個線程都各幹個的,向一盤散沙,程序就什麽也做不了兒了。現在,我們來看看線程間如何通訊。   線程間通訊的方法有很多,常用的有:變量、臨界段、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()將同時通知兩個線程執行任務;如果有線程零做與線程一類似的工作,它也可以觸發同一個事件,線程零或線程一任意一方觸發事件都將使兩個線程執行任務。   除了上述的幾種方法以外,旗語等方法也是用來進行線程間通訊的,這裏先不作介紹。   當設計線程間的通訊時,一定要時刻記得各個線程之間是異步運行的,必須要使用某種機制才能進行通訊,在實際中,情況可能會非常複雜,如果考慮不全面,結果將是難以預料的。
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 熱帖排行
 
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有