1. 依賴關(guān)系
當(dāng)線程或進(jìn)程相互間需要通信或合作來(lái)完成一個(gè)共同目標(biāo)時(shí),它們就具有依賴關(guān)系(dependency relationship)。
1.1 通信依賴性
當(dāng)線程A需要來(lái)自線程B的數(shù)據(jù)進(jìn)行操作時(shí),就發(fā)生第一種類型的依賴性,即通信依賴性(communication dependency)。線程B必須在線程A繼續(xù)執(zhí)行前與線程A所獲取的數(shù)據(jù)進(jìn)行通信。
1.2 合作依賴性
當(dāng)線程A需要線程B擁有的資源,而且在線程A可以使用這些資源前,線程B必須釋放它,此時(shí)就發(fā)生第二種依賴性,即合作依賴性。如果有兩個(gè)并發(fā)執(zhí)行的進(jìn)程,它們都試圖同時(shí)訪問(wèn)相同的資源,那么這些進(jìn)程在成功執(zhí)行前需要合作。
1.3 計(jì)數(shù)線程與進(jìn)程依賴性
不管一個(gè)進(jìn)程有多少線程,我們都可以通過(guò)比較進(jìn)程內(nèi)線程與其它每個(gè)線程的依賴性來(lái)理解整體線程關(guān)系。一旦理解了進(jìn)程內(nèi)所有線程對(duì)的關(guān)系,該進(jìn)程的整體線程結(jié)構(gòu)就知道了。
線程依賴性圖對(duì)于說(shuō)明一套線程或進(jìn)程間的依賴關(guān)系有用。依賴性圖可以用于與應(yīng)用程序中相依賴的線程通信,而不需要通過(guò)源代碼來(lái)搜索這種關(guān)系。這種信息可用于理解、保持以及測(cè)試多線程程序。
2. 進(jìn)程間和線程間通信
程序員必須讓擁有依賴關(guān)系的進(jìn)程集或線程集協(xié)調(diào),這樣才能達(dá)到進(jìn)程或線程的共同目標(biāo)。可以使用兩種技術(shù)來(lái)達(dá)到協(xié)調(diào)。第一種技術(shù)在具有通信依賴關(guān)系的兩個(gè)進(jìn)程間傳遞信息,這種技術(shù)稱做進(jìn)程間或線程間通信(interprocess or interthread communication)。第二種技術(shù)是同步,當(dāng)線程或進(jìn)程相互間具有合作依賴性時(shí)使用。
2.1 進(jìn)程間通信
某個(gè)進(jìn)程中的數(shù)據(jù)對(duì)于其它進(jìn)程來(lái)說(shuō)是受保護(hù)的。為了讓一個(gè)進(jìn)程訪問(wèn)另一個(gè)進(jìn)程的數(shù)據(jù),必須最終使用操作系統(tǒng)調(diào)用。與之類似,為了讓一個(gè)進(jìn)程知道另一個(gè)進(jìn)程中文本片斷中發(fā)生的事,必須在進(jìn)程間建立一種通信方式,這也需要來(lái)自己OS API的幫助。當(dāng)進(jìn)程將數(shù)據(jù)發(fā)送到另一個(gè)進(jìn)程時(shí),稱做IPC(進(jìn)程間通信)。
2.2 進(jìn)程間通信類型
當(dāng)一個(gè)進(jìn)程派生出其它進(jìn)程時(shí),我們說(shuō)派生的進(jìn)程是相關(guān)聯(lián)的。關(guān)聯(lián)進(jìn)程使用一套技術(shù)來(lái)執(zhí)行進(jìn)程間通信,而不相關(guān)聯(lián)的進(jìn)程通常使用另一套技術(shù)來(lái)通信。
2.2.1環(huán)境變量、文件描述符
當(dāng)創(chuàng)建一個(gè)子進(jìn)程時(shí),它接受了父進(jìn)程許多資源的拷貝像文本、堆棧以及數(shù)據(jù)片斷的拷貝。同時(shí)也接受了父進(jìn)程的環(huán)境數(shù)據(jù)以及所有文件描述符的拷貝。子進(jìn)程從父進(jìn)程繼承資源的過(guò)程中創(chuàng)造了進(jìn)程間通信的一個(gè)機(jī)會(huì)。父進(jìn)程可在它的數(shù)據(jù)片斷或環(huán)境中設(shè)置一定的變量,然后執(zhí)行fork()。了進(jìn)程接受這些值。同樣,父進(jìn)程也可打開(kāi)一個(gè)文件,推進(jìn)到文件內(nèi)的期望位置,然后執(zhí)行fork(),子進(jìn)程接著就可以在父進(jìn)程離開(kāi)讀/寫指針的準(zhǔn)確位置訪問(wèn)該文件。
這種通信的缺陷在于它是單向的、一次性的。除了文件描述符外,如果子進(jìn)程繼承了任何其他數(shù)據(jù),也僅僅是父進(jìn)程數(shù)據(jù)拷貝的所有數(shù)據(jù)。一旦創(chuàng)建了子進(jìn)程,由子進(jìn)程對(duì)這些變量的任何改變都不會(huì)反映到父進(jìn)程的數(shù)據(jù)中同樣,父進(jìn)程對(duì)它數(shù)據(jù)的任何改變也不會(huì)反映到子進(jìn)程中。
2.2.2 命令行參數(shù)
命令行參數(shù)在調(diào)用一個(gè)exec或派生調(diào)用操作系統(tǒng)時(shí)傳遞給子進(jìn)程。命令行參數(shù)通常在其中一個(gè)參數(shù)中作為NULL終止字符串傳遞給exec或派生函數(shù)調(diào)用。
2.2.3 管道
管道可用于在關(guān)聯(lián)進(jìn)程間以及無(wú)關(guān)聯(lián)進(jìn)程間進(jìn)行通信。
管道是一種數(shù)據(jù)結(jié)構(gòu),像一個(gè)序列化文件一樣訪問(wèn),它形成了兩個(gè)進(jìn)程間的一種通信渠道。管道結(jié)構(gòu)通過(guò)使用文件讀/寫方式來(lái)進(jìn)行訪問(wèn)。它有兩種基本類型:匿名管道(anonymous pipe)與命名管道(named pipe)。只有關(guān)聯(lián)進(jìn)程可以使用匿名管道來(lái)通信,無(wú)關(guān)聯(lián)進(jìn)程必須使用命名管道。
通過(guò)文件描述符或文件句柄提供對(duì)匿名管道的訪問(wèn)。對(duì)系統(tǒng)API的調(diào)用創(chuàng)建一個(gè)管道,并返回一個(gè)文件描述符。這個(gè)文件描述符用作read()或write函數(shù)的一個(gè)參數(shù)。當(dāng)通過(guò)文件描述符調(diào)用read()或writer()函數(shù)時(shí),數(shù)據(jù)的源和目標(biāo)就是管道。
將管道用作兩個(gè)無(wú)關(guān)聯(lián)進(jìn)程間的通信渠道,程序員必須使用命名管道,它可以看作一種具有某名字的特殊類型文件。進(jìn)程可以根據(jù)它的名字訪問(wèn)這個(gè)管道。因?yàn)闊o(wú)關(guān)進(jìn)程不能訪問(wèn)彼此的文件描述符,所以不能使用匿名管道。命名管道可以持久,創(chuàng)建他的程序退出后,它們?nèi)匀豢梢源嬖?,還可在網(wǎng)絡(luò)或分布環(huán)境中使用,可用于多對(duì)一關(guān)系中。
服務(wù)器程序負(fù)責(zé)建立客戶與服務(wù)器進(jìn)程間管道通信的協(xié)議。服務(wù)器程序必須指定訪問(wèn)模式、阻塞模式、管道類型、讀取模式、緩沖器大小以及管道使用的即時(shí)計(jì)數(shù)。
命名管道不僅可用于無(wú)關(guān)聯(lián)進(jìn)程間、位于不同機(jī)器上的兩進(jìn)程間的通信,而且可用于多對(duì)一通信??梢越⒎?wù)器進(jìn)程,允許同時(shí)通過(guò)多個(gè)客戶訪問(wèn)命名管道。命名管道常常用于多線程服務(wù)器。
2.2.4 共享內(nèi)存
共享內(nèi)存被映射到使用它的每個(gè)進(jìn)程的地址空間,所以看起來(lái)像另一個(gè)在進(jìn)程內(nèi)聲明的變量。當(dāng)一個(gè)進(jìn)程寫共享內(nèi)存,所有的進(jìn)程都立即知道寫入的內(nèi)容,而且可以訪問(wèn)。進(jìn)程間共享內(nèi)存的關(guān)系與函數(shù)間全局變量的關(guān)系相似,它可以被正在執(zhí)行的所有進(jìn)程訪問(wèn)。
OS/2環(huán)境中,共享內(nèi)存可以是匿名的,也可以是有名的。如果共享內(nèi)存為匿名的,那么必須使用某些形式的進(jìn)程間通信來(lái)給希望應(yīng)用共享內(nèi)存的其它進(jìn)程傳遞共享內(nèi)存地址。如果內(nèi)存是有名的,則沒(méi)有必要在進(jìn)程間形成通信渠道,其它進(jìn)程可通過(guò)名字訪問(wèn)共享內(nèi)存。通常使用共享內(nèi)存比使用管道或隊(duì)列更簡(jiǎn)單,也更有效,它可以用于保存大數(shù)據(jù)結(jié)構(gòu),然后進(jìn)而可被從任何數(shù)量的進(jìn)程中有效訪問(wèn)。這種內(nèi)存可以充當(dāng)面向?qū)ο髷?shù)據(jù)庫(kù)、集合對(duì)象或容器對(duì)象的一種永久存儲(chǔ)空間。
2.2.5 動(dòng)態(tài)數(shù)據(jù)交換(dynamic data exchange)
這是當(dāng)今可用的進(jìn)程間通信最強(qiáng)大和完善的形式之一。它使用消息傳遞、共享內(nèi)存、事務(wù)協(xié)議、客戶/服務(wù)器范例、同步規(guī)則以及會(huì)話協(xié)議來(lái)讓數(shù)據(jù)和控制信息在進(jìn)程間流動(dòng)。動(dòng)態(tài)數(shù)據(jù)交換對(duì)話(dynamic data exchange session, DDE)的基本模型是客戶/服務(wù)器。服務(wù)器對(duì)來(lái)自客戶的數(shù)據(jù)或動(dòng)作做出反應(yīng)。
一個(gè)服務(wù)器可以與任意數(shù)量的客戶通信。一個(gè)客戶也可以與任意數(shù)量的服務(wù)器通信。進(jìn)程間通信的DDE形式使用消息系統(tǒng)來(lái)完成,它是由OS提供的。
所有DDE交互中主要有4種組件。它們的結(jié)合提供了進(jìn)程間通信的能力。其中客戶和服務(wù)器均代表系統(tǒng)內(nèi)的進(jìn)程??蛻糸g的會(huì)話組成了通信的實(shí)質(zhì)以及進(jìn)程間傳遞的內(nèi)容。進(jìn)程間的共享內(nèi)存用于保存任何種類的數(shù)據(jù),從簡(jiǎn)單數(shù)據(jù)類型到復(fù)雜、面向?qū)ο髷?shù)據(jù)庫(kù)。
3. 線程間通信
進(jìn)程和線程間的巨大的區(qū)別就是單個(gè)進(jìn)程有自己的地址空間,而線程沒(méi)有。當(dāng)兩個(gè)進(jìn)程需要通信時(shí),一般使用某種兩進(jìn)程外部的、兩進(jìn)程都能訪問(wèn)的數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)。使用這種數(shù)據(jù)結(jié)構(gòu)傳遞數(shù)據(jù)或命令,兩個(gè)進(jìn)程可以通信。當(dāng)兩個(gè)線程通信時(shí),它們一般使用屬于同一進(jìn)程的部分?jǐn)?shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)。
線程相對(duì)于進(jìn)程的一個(gè)重要優(yōu)點(diǎn)是,線程可以共享全局變量(global variable)。
完成線程間通信的基本工具可包括:全局?jǐn)?shù)據(jù)、全局變量、全局?jǐn)?shù)據(jù)結(jié)構(gòu)、參數(shù)和文件句柄(線程間共享文件的文件句柄。如果一個(gè)線程推進(jìn)讀/寫指針,其它訪問(wèn)該文件的所有線程都獲得相同的偏移)。
線程的參數(shù)是某些數(shù)據(jù)位置的地址。對(duì)線程中數(shù)據(jù)的任何修改都將反映在創(chuàng)建該數(shù)據(jù)的進(jìn)程中。
與將全局變量或全局?jǐn)?shù)據(jù)結(jié)構(gòu)用作線程間通信機(jī)制相比,將參數(shù)用作線程間通信的一種形式可能更為理想,因?yàn)檫M(jìn)程中的所有線程都可以訪問(wèn)全局?jǐn)?shù)據(jù),控制哪個(gè)線程什么時(shí)候做什么是困難的。通過(guò)限制只能訪問(wèn)那些可以訪問(wèn)全局?jǐn)?shù)據(jù),控制哪個(gè)線程什么時(shí)候做什么是困難的。而通過(guò)限制只能訪問(wèn)那些作為參數(shù)接受數(shù)據(jù)的線程,程序員可以控制對(duì)數(shù)據(jù)值和數(shù)據(jù)結(jié)構(gòu)的訪問(wèn),更好地控制同步。當(dāng)在多線程環(huán)境中聲明數(shù)據(jù)為全局時(shí),程序的增強(qiáng),維護(hù)和調(diào)試都變得更為困難。
當(dāng)多線程間的共享文件作為一種線程間的通信形式時(shí),同樣要小心全局變量。如果threadA移動(dòng)了文件指針,threadB也會(huì)受到影響。如果文件被threadB關(guān)閉,而threadA試圖寫入此文件,則顯然會(huì)有沖突。在多線程環(huán)境中,序列化或同步化文件訪問(wèn)時(shí)也要小心,因?yàn)榫€程可以共享實(shí)際的地址、文件讀指針、文件寫指針、全局變量以及數(shù)據(jù)結(jié)構(gòu),所以必須使用同步和合作技術(shù)。