第十二章志珍、并發(fā)編程

第十二章、并發(fā)編程

現(xiàn)代操作系統(tǒng)提供了三種基本的構(gòu)造并發(fā)程序的方法:

1垛叨、進(jìn)程: 每個(gè)邏輯控制流都是一個(gè)進(jìn)程伦糯,由內(nèi)核來調(diào)度和維護(hù)。因?yàn)檫M(jìn)程由獨(dú)立的虛擬地址空間嗽元,想要和其他流通信敛纲,控制流必須使用某種顯式的進(jìn)程間通信機(jī)制。

2剂癌、I/O多路復(fù)用:在這種形式的并發(fā)編程中淤翔,應(yīng)用程序在一個(gè)進(jìn)程的上下文中顯式的調(diào)度他們自己的邏輯流。邏輯流被模型化為狀態(tài)機(jī)珍手,數(shù)據(jù)到達(dá)文件描述符后办铡,主程序顯式的從一個(gè)狀態(tài)轉(zhuǎn)移到另一個(gè)狀態(tài)。因?yàn)槌绦蚴且粋€(gè)單獨(dú)的進(jìn)程琳要,所以所有的流都共享同一個(gè)地址空間。

3秤茅、線程:線程是運(yùn)行在一個(gè)單一進(jìn)程上下文中的邏輯流稚补,由內(nèi)核進(jìn)行調(diào)度。你可以把線程看成是其他兩種方式的混合體框喳,像進(jìn)程流一樣由內(nèi)核進(jìn)行調(diào)度,而像I/O多路復(fù)用流一樣共享同一個(gè)虛擬地址空間。

12.1 基于進(jìn)程的并發(fā)編程

例如度苔,一個(gè)構(gòu)造并發(fā)服務(wù)器的自然方法就是宣蠕,在父進(jìn)程中接受客戶端連接請求,然后創(chuàng)建一個(gè)新的子進(jìn)程為每個(gè)新客戶端提供服務(wù)埋泵。

假設(shè)我們有兩個(gè)客戶端和一個(gè)服務(wù)器:

1、服務(wù)器正在監(jiān)聽一個(gè)描述符(如socket 3,監(jiān)聽socket)上的連接請求∏说現(xiàn)在假設(shè)服務(wù)器接受了客戶端1的連接請求,并返回一個(gè)已連接描述符(如socket 4莉撇, 連接socket)呢蛤。

2、在接受連接請求之后棍郎,服務(wù)器派生一個(gè)子進(jìn)程其障,這個(gè)子進(jìn)程獲得服務(wù)器描述符的完整拷貝。子進(jìn)程關(guān)閉它的拷貝中的監(jiān)聽描述符3涂佃,而父進(jìn)程關(guān)閉它以連接描述符4的拷貝励翼,因?yàn)椴恍枰@些描述符了。 這之后辜荠,子進(jìn)程正忙于為客戶端提供服務(wù)抚笔。因?yàn)楦浮⒆舆M(jìn)程中的已連接描述符都指向同一個(gè)文件表表項(xiàng)侨拦,所以父進(jìn)程關(guān)閉它的已連接描述符的拷貝是至關(guān)重要的殊橙。否則,將永遠(yuǎn)不會釋放已連接描述符4的文件表?xiàng)l目狱从,而且由此引起的存儲器泄漏將最終消耗盡可用的存儲器膨蛮,使系統(tǒng)崩潰。

3季研、現(xiàn)在敞葛,假設(shè)父進(jìn)程為客戶端1創(chuàng)建了子進(jìn)程之后,它接受一個(gè)新的客戶端2的連接請求与涡,并返回一個(gè)新的已連接描述符(如socket 5)惹谐,然后父進(jìn)程又派生另一個(gè)子進(jìn)程,這個(gè)子進(jìn)程用已連接描述符5位客戶端提供服務(wù)驼卖,如2中處理氨肌,此時(shí),父進(jìn)程等待下一個(gè)連接請求酌畜,而兩個(gè)子進(jìn)程正在并發(fā)地為它們各自的客戶端提供服務(wù)怎囚。

總結(jié):

服務(wù)器端父進(jìn)程:

監(jiān)聽套接字監(jiān)聽->收到連接請求->返回連接套接字->派生子進(jìn)程->釋放連接套接字->繼續(xù)監(jiān)聽

子進(jìn)程: 得到父進(jìn)程完整拷貝->釋放監(jiān)聽套接字->提供服務(wù)

12.1.2 關(guān)于進(jìn)程的優(yōu)劣

1、父子進(jìn)程共享狀態(tài)信息桥胞,進(jìn)程有一個(gè)非常清晰的模型:共享文件表恳守,但是不共享用戶地址空間考婴。進(jìn)程有獨(dú)立的地址空間即是優(yōu)點(diǎn)也是缺點(diǎn)。這樣一來催烘,一個(gè)進(jìn)程不會覆蓋另一個(gè)進(jìn)程的虛擬存儲器沥阱,這就消除了許多令人迷惑的錯(cuò)誤(優(yōu)點(diǎn))。

2伊群、獨(dú)立的地址空間使得進(jìn)程共享狀態(tài)信息變得更加困難考杉。為了共享信息,它們必須使用顯式的IPC(進(jìn)程間通信)機(jī)制在岂。這樣往往比較慢奔则,因?yàn)檫M(jìn)程控制和IPC的開銷很高。

12.2 基于I/O多路復(fù)用的并發(fā)編程

假設(shè)要求你編寫一個(gè)echo服務(wù)器蔽午,它也能對用戶從標(biāo)準(zhǔn)輸入鍵入的交互命令做出響應(yīng)易茬。在這種情況下,服務(wù)器必須響應(yīng)兩個(gè)互相獨(dú)立的I/O時(shí)間:1)網(wǎng)絡(luò)客戶端發(fā)起連接請求及老,2)用戶在鍵盤上鍵入命令行抽莱。一個(gè)問題就是我們先等待哪個(gè)事件呢?沒有哪個(gè)選擇是理想的骄恶。如果在accept中等待一個(gè)連接請求食铐,我們就不能響應(yīng)輸入命令。類似的僧鲁,如果在read中等待輸入命令虐呻,我們就不能響應(yīng)任何連接請求。

針對這種困境的一個(gè)解決方法就是I/O多路復(fù)用技術(shù)寞秃≌宓穑基本的思路就是使用select函數(shù),要求內(nèi)核掛起進(jìn)程春寿,只有在一個(gè)或多個(gè)I/O事件發(fā)生后朗涩,才將控制返回給應(yīng)用程序。就像下面的示例:

當(dāng)集合{0绑改,4}中任意描述符準(zhǔn)備好讀時(shí)返回谢床。

當(dāng)集合{1,2厘线,7}中任意描述符準(zhǔn)備好寫時(shí)返回识腿。

如果等待一個(gè)I/O事件發(fā)生時(shí)過了152.13秒,就超時(shí)皆的。

select函數(shù)處理類型為fd_set的集合覆履,也叫作描述符集合。邏輯上费薄,我們將描述符集合看成是一個(gè)的大小為n的位向量:


每個(gè)為bk對應(yīng)于描述符k硝全。當(dāng)且僅當(dāng)bk=1,描述符k才表明是描述符集合的一個(gè)元素楞抡。只允許對描述符集合做三件事1)分配它們伟众,2)將一個(gè)此種類型的變量賦值給另一個(gè)變量,3)用FD_ZERO召廷、FD_SET凳厢、FD_CLR和FD_ISSET宏指令來修改和檢查它們。

針對我們的目的竞慢,select函數(shù)有兩個(gè)輸入:一個(gè)稱為讀集合的描述符集合(fdset)和該集合的基數(shù)(n)(實(shí)際上是任何描述符集合的最大基數(shù))先紫。select函數(shù)會一直阻塞,直到讀集合中至少有一個(gè)描述符準(zhǔn)備好可以讀筹煮。當(dāng)且僅當(dāng)一個(gè)從描述符讀取一個(gè)字節(jié)的請求不會阻塞時(shí)遮精,描述符k就表示準(zhǔn)備好可以讀了。作為一個(gè)副作用败潦,select修改了參數(shù)fdset指向的fd_set本冲,指明讀集合中一個(gè)稱為準(zhǔn)備好集合(ready set)的子集,這個(gè)集合是由讀集合中準(zhǔn)備好可以讀了的描述符組成的劫扒。函數(shù)返回的值指明了準(zhǔn)備好集合的基數(shù)檬洞。注意,由于這個(gè)副作用沟饥,我們必須在每次調(diào)用select時(shí)都更新讀集合添怔。

理解select的最好辦法就是研究一個(gè)具體例子。

1贤旷、一開始打開一個(gè)監(jiān)聽描述符广料,然后創(chuàng)建一個(gè)空的讀集合:


2、接下來遮晚,定義由描述符0(標(biāo)準(zhǔn)輸入)和描述符3(監(jiān)聽描述符)組成的讀集合:


3性昭、然后開始典型的服務(wù)器循環(huán)。但是我們不是調(diào)用accept函數(shù)來等待一個(gè)連接請求县遣,而是調(diào)用select函數(shù)糜颠,這個(gè)函數(shù)會一直阻塞,直到監(jiān)聽描述符或者標(biāo)準(zhǔn)輸入準(zhǔn)備好可以讀萧求。例如其兴,下面是當(dāng)用戶按下回車鍵,因此使得標(biāo)準(zhǔn)輸入描述符變?yōu)榭勺x時(shí)夸政,select會返回ready_set的值:

4元旬、一旦select返回,我們就用宏指令來判斷哪個(gè)描述符準(zhǔn)備好可以讀了。如果是標(biāo)準(zhǔn)輸入準(zhǔn)備好了匀归,就調(diào)用command函數(shù)坑资,如果是監(jiān)聽描述符準(zhǔn)備好了,就調(diào)用accept函數(shù)穆端。

12.2.2 I/O多路復(fù)用技術(shù)的優(yōu)劣

優(yōu)點(diǎn):

1袱贮、比基于進(jìn)程的設(shè)計(jì)給了程序員更多的對程序行為的控制。

2体啰、一個(gè)基于I/O多路復(fù)用的時(shí)間驅(qū)動服務(wù)器是在運(yùn)行在單一進(jìn)程上下文中的攒巍,因此每個(gè)邏輯流都能訪問該進(jìn)程的全部地址空間。這使得在流之間共享數(shù)據(jù)變得很容易荒勇。

3柒莉、時(shí)間驅(qū)動設(shè)計(jì)常常比基于進(jìn)程的設(shè)計(jì)要高效的多,因?yàn)樗鼈儾恍枰M(jìn)程上下文切換來調(diào)度新的流沽翔。

缺點(diǎn):

1兢孝、編碼復(fù)雜。我們的時(shí)間驅(qū)動的并發(fā)echo服務(wù)器需要代碼比基于進(jìn)程的服務(wù)器多三倍搀擂。

12.3 基于線程的并發(fā)編程

已經(jīng)了解了兩種創(chuàng)建并發(fā)邏輯流的方法西潘。在第一種方法中,我們?yōu)槊總€(gè)流使用了單獨(dú)的進(jìn)程哨颂。內(nèi)核會自動調(diào)度每個(gè)進(jìn)程喷市。每個(gè)進(jìn)程有它自己的私有地址空間,這使得流共享數(shù)據(jù)很困難威恼。在第二種方法中品姓,我們創(chuàng)建了自己的邏輯流,并利用I/O多路復(fù)用來顯式地調(diào)度流箫措。因?yàn)橹挥幸粋€(gè)進(jìn)程腹备,所有的流共享整個(gè)地址空間。而基于線程的并發(fā)編程斤蔓,則是以上兩種方式的混合植酥。

線程是運(yùn)行在進(jìn)程上下文的邏輯流(進(jìn)程是資源分配的單位,線程是調(diào)度和執(zhí)行的單位弦牡,進(jìn)程由操作系統(tǒng)看到友驮,線程由CPU看到)

每個(gè)線程都有它自己的線程上下文驾锰,包括一個(gè)唯一的整數(shù)線程ID(tid)卸留、棧、棧指針椭豫、程序計(jì)數(shù)器耻瑟、通用目的寄存器和條件碼旨指。所有的運(yùn)行在一個(gè)進(jìn)程里的線程共享該進(jìn)程的虛擬地址空間

兩種方式混合的理解:

1喳整、同進(jìn)程一樣谆构,線程由內(nèi)核自動調(diào)度,并且內(nèi)核通過一個(gè)整數(shù)ID來識別線程算柳。

2低淡、同基于I/O多路復(fù)用一樣姓言,多個(gè)線程運(yùn)行在單一進(jìn)程的上下文中瞬项,因此共享這個(gè)進(jìn)程的虛擬地址空間的整個(gè)內(nèi)容,包括它的代碼何荚、數(shù)據(jù)囱淋、堆、共享庫和打開的文件餐塘。(線程有自己的棧區(qū)妥衣,沒有自己的堆區(qū))。

12.3.1 線程執(zhí)行模型

每個(gè)進(jìn)程開始生命周期時(shí)都是單一線程戒傻,這個(gè)線程成為主線程税手。在某一時(shí)刻,主線程會創(chuàng)建一個(gè)對等線程需纳,從這個(gè)時(shí)間點(diǎn)開始芦倒,兩個(gè)線程就并發(fā)的運(yùn)行。

線程執(zhí)行是不同于進(jìn)程的:

1不翩、因?yàn)橐粋€(gè)線程的上下文要比一個(gè)進(jìn)程的上下文小得多兵扬,線程的上下文切換要比進(jìn)程的上下文切換快的多。

2口蝠、線程不像進(jìn)程那樣器钟,不是按照嚴(yán)格的父子層來組織的。和一個(gè)進(jìn)程相關(guān)的線程組成一個(gè)線程池妙蔗,獨(dú)立于其他進(jìn)程創(chuàng)建的線程傲霸。在一個(gè)線程池中的主線程和其他線程的區(qū)別僅僅在于主線程總是進(jìn)程中第一個(gè)運(yùn)行的線程。線程池概念的主要影響是眉反,一個(gè)線程可以殺死它的任何對等線程昙啄,或者等待它的任意對等線程終止。另外禁漓,每個(gè)對等線程都能讀寫相同的共享數(shù)據(jù)跟衅。

12.3.3 創(chuàng)建線程

線程通過調(diào)用pthread_create函數(shù)來創(chuàng)建其他線程。

12.3.4 終止線程

一個(gè)線程是以以下方式之一來終止的:

1播歼、當(dāng)頂層的線程例程返回時(shí)伶跷,線程會隱式的終止掰读。

2、通過調(diào)用pthread_exit函數(shù)叭莫,線程會顯式的終止蹈集。

3、某個(gè)對等線程調(diào)用Unix的exit函數(shù)雇初,該函數(shù)終止進(jìn)程以及所有與該進(jìn)程相關(guān)的線程拢肆。

4、另一個(gè)對等線程通過以當(dāng)前線程ID作為參數(shù)調(diào)用pthread_cancle函數(shù)來終止當(dāng)前線程靖诗。

12.3.5 回收已終止的線程的資源

線程通過調(diào)用pthread_join函數(shù)等待其他線程終止郭怪。

pthread_join函數(shù)會阻塞,直到線程tid終止刊橘,將線程例程返回的指針賦值為thread_return指向的位置鄙才,然后回收已終止線程占用的所有存儲器資源。

12.3.6 分離線程

在任意時(shí)間點(diǎn)上促绵,線程是可分離的或可結(jié)合的攒庵。

1、一個(gè)可結(jié)合的線程能夠被其他線程回收資源和殺死败晴。在被其他線程回收之前浓冒,它的存儲器資源(如棧)是沒有被釋放的。

2尖坤、一個(gè)分離的線程是不能被其他線程回收或殺死的稳懒。它的存儲器資源在它終止時(shí)由系統(tǒng)自動釋放。

默認(rèn)情況下糖驴,線程被創(chuàng)建為可結(jié)合的僚祷。但是為了避免存儲器泄漏,每個(gè)可結(jié)合線程都應(yīng)該要么被其他線程顯式的回收贮缕,要么調(diào)用pthread_detch函數(shù)被分離辙谜。這樣是為了確保釋放其存儲器資源。

12.4 多線程程序中的共享變量

12.4.1 線程存儲器模型

一組并發(fā)線程運(yùn)行在一個(gè)進(jìn)程的上下文中感昼。每個(gè)線程都有它自己獨(dú)立的線程上下文装哆,包括線程ID,棧定嗓,棧指針蜕琴,程序計(jì)數(shù)器,條件碼宵溅,和通用目的寄存器值凌简。每個(gè)線程和其他線程一起共享進(jìn)程上下文的剩余部分。這包括整個(gè)用戶虛擬地址空間恃逻,它是由制度文件(代碼)雏搂、讀/寫數(shù)據(jù)藕施、堆,以及所有的共享庫代碼和數(shù)據(jù)區(qū)域組成的凸郑。線程也共享同樣的打開文件的集合裳食。

在線程之間寄存器是從不共享的,而虛擬存儲器總是共享的芙沥。

雖然每個(gè)線程有自己的椈寤觯空間,但是不同線程棧是不對其他線程設(shè)防的而昨。所以救氯,如果一個(gè)線程以某種方式得到一個(gè)指向其他線程棧的指針,那么它就可以讀寫這個(gè)棧的任何部分配紫。

12.4.2 將變量映射到存儲器

線程化的程序中變量根據(jù)它們的存儲類型被映射到虛擬存儲器中:

1径密、全局變量:全局變量是定義在函數(shù)之外的變量。

在運(yùn)行時(shí)躺孝,虛擬存儲器的讀/寫區(qū)域只包含每個(gè)全局變量的一個(gè)實(shí)例,任何線程都可以引用底桂。

2植袍、本地自動變量:本地自動變量就是定義在函數(shù)內(nèi)部但沒有static屬性的變量。

在運(yùn)行時(shí)籽懦,每個(gè)線程的棧都包含它自己所有本地變量的實(shí)例于个。

3、本地靜態(tài)變量:本地靜態(tài)變量時(shí)定義在函數(shù)內(nèi)部并有static屬性的變量暮顺。

和全局變量一樣厅篓,虛擬存儲器的讀/寫區(qū)域只包含在程序中聲明的每個(gè)本地靜態(tài)變量的一個(gè)實(shí)例。在運(yùn)行時(shí)捶码,每個(gè)對等線程都讀和寫這個(gè)實(shí)例羽氮。

12.5 用信號量同步線程

共享變量是十分方便的,但是也引入了同步錯(cuò)誤的可能性惫恼。

對于下面一個(gè)同步不正確的計(jì)數(shù)器程序:


它創(chuàng)建了兩個(gè)線程档押,每個(gè)線程都對共享計(jì)數(shù)變量cnt加1.因?yàn)槊總€(gè)線程都對計(jì)數(shù)器增加了niters次,我們預(yù)計(jì)它的最終值為2×niters祈纯。但是當(dāng)運(yùn)行程序是令宿,每次得到的答案都是不同的。

為了理解這個(gè)問題腕窥,我們需要研究計(jì)數(shù)器循環(huán)(39~40行)的匯編代碼粒没,線程i的循環(huán)代碼可以分為五個(gè)部分:

Hi:在循環(huán)頭部的指令塊;

Li:加載共享變量cnt到寄存器%eax_i的指令簇爆,這里%eax_i為線程i中的寄存器的值癞松;

Ui:更新(增加)%eax_i的指令倾贰;

Si:將%eax_i的更新值返回到共享變量cnt的指令;

Ti:循環(huán)尾部指令塊拦惋。

注意頭和尾只操作本地棧變量匆浙,而Li,Ui和Si操作共享計(jì)數(shù)器變量的內(nèi)容厕妖。

當(dāng)程序的兩個(gè)對等線程在一個(gè)單處理器上并發(fā)運(yùn)行時(shí)首尼,機(jī)器指令以某種順序一個(gè)接一個(gè)完成。因此言秸,每個(gè)并發(fā)執(zhí)行定義了兩個(gè)線程中的指令的某種全序软能。不行的是:這些順序中的一些會產(chǎn)生正確結(jié)果,但是其他的不會举畸。而我們無法預(yù)測操作系統(tǒng)是否將線程選擇一個(gè)正確的順序查排。

下圖為兩種不同順序下得到的不同結(jié)果示意圖:


12.5.1 進(jìn)度圖

進(jìn)度圖將n個(gè)并發(fā)線程的執(zhí)行模型轉(zhuǎn)化為一個(gè)n為空間中的軌跡線。每條軸k對應(yīng)進(jìn)程k的進(jìn)度抄沮。每個(gè)點(diǎn)Ik代表線程k已經(jīng)完成了指令I(lǐng)k這一狀態(tài)跋核。


一個(gè)程序的執(zhí)行歷史被模型化為空間中的一條軌跡線。如下圖所示:

進(jìn)度圖中只允許向上或向右移動叛买。

對于線程i操作共享變量cnt內(nèi)容的指令為(Li砂代,Ui,Si)構(gòu)成了一個(gè)共享變量cnt的臨界區(qū)率挣,這個(gè)臨界區(qū)不應(yīng)該和其他線程的臨界區(qū)交替執(zhí)行刻伊。換句話說,我們想要確保每個(gè)線程在執(zhí)行它的臨界區(qū)中的指令時(shí)椒功,擁有對共享變量的互斥的訪問捶箱。

兩個(gè)臨界區(qū)的交集形成的狀態(tài)空間區(qū)域稱為不安全區(qū)。經(jīng)過不安全區(qū)的軌跡則為不安全軌跡線动漾。


clipboard.png

任何安全軌跡線都將正確的更新共享計(jì)數(shù)器丁屎。為了保證線程化程序示例的正確執(zhí)行,我們必須以某種方式同步線程谦炬,使它們總是有一條安全軌跡線悦屏。一個(gè)經(jīng)典的方法就是基于信號量的思想。

12.5.2 信號量

信號量s是具有非負(fù)整數(shù)值的全局變量键思,只能用兩種特殊的操作來處理础爬,這兩種操作稱為P和V:

P(s):如果s是非零的,則P將s減1吼鳞,并立即返回看蚜。如果s為0,就掛起這個(gè)線程(可以理解為加鎖)赔桌。

V(s):V操作將s加1供炎,如果任何線程阻塞在P操作等待s變?yōu)榉?渴逻,那么V操作會重啟這些線程中的一個(gè),然后改線程s減1音诫,完成它的P操作(可以理解為解鎖)惨奕。

P和V的定義確保了一個(gè)正在運(yùn)行的程序絕不可能進(jìn)入這樣一種狀態(tài),也就是一個(gè)正在初始化的信號量有一個(gè)負(fù)值竭钝。這個(gè)屬性成為信號量的不變性梨撞,為控制并發(fā)程序的軌跡提供了強(qiáng)有力的工具。

12.5.3 使用信號量來實(shí)現(xiàn)互斥

信號量提供了一種很方便的方法來確保對共享變量的互斥訪問香罐∥圆ǎ基本思想就是將每個(gè)共享變量與一個(gè)信號量s(初始化為1)聯(lián)系起來,然后通過P和V操作將相應(yīng)的臨界區(qū)包圍起來庇茫。

以這種方式來保護(hù)共享變量的信號量叫做二元信號量港粱,因?yàn)樗闹悼偸?或者1。以提供互斥為目的的二元信號量常常也成為互斥鎖旦签。在一個(gè)互斥鎖上執(zhí)行P操作稱為加鎖查坪。V操作稱為解鎖。

與二元信號量相對應(yīng)的還有計(jì)數(shù)信號量顷霹。


12.5.4 利用信號量來調(diào)度共享資源

兩個(gè)經(jīng)典而有用的例子是生產(chǎn)者-消費(fèi)者讀者-寫者問題咪惠。

1、生產(chǎn)者-消費(fèi)者問題

模型描述:生產(chǎn)者和消費(fèi)者線程共享一個(gè)有n個(gè)槽的有限緩沖區(qū)淋淀。生產(chǎn)者線程反復(fù)生成新的item,并把它們插入到緩沖區(qū)中覆醇。消費(fèi)者線程不斷地從緩沖區(qū)中取出這些item朵纷,然后消費(fèi)(使用)它們。


模型產(chǎn)生的問題:

1永脓、因?yàn)椴迦牒腿〕鰅tem都涉及更新共享變量袍辞,所以必須保證對緩沖區(qū)的訪問是互斥的。

2常摧、但是只保證互斥訪問時(shí)不夠的搅吁,還需要調(diào)度對緩沖區(qū)的訪問。如果緩沖區(qū)是滿的(沒有空的槽位)落午,那么生產(chǎn)者必須等待知道有一個(gè)槽位變?yōu)榭捎没雅场Ec之相似,如果緩沖區(qū)是空的(沒有可取用的item)溃斋,那么消費(fèi)者必須等待直到有一個(gè)item變?yōu)榭捎谩?/p>

因此生產(chǎn)者消費(fèi)者模型主要考慮兩個(gè)問題:一個(gè)是生產(chǎn)者消費(fèi)者互斥界拦,另一個(gè)是調(diào)度緩沖區(qū)的訪問。

模型實(shí)例:

1梗劫、圖形用戶接口設(shè)計(jì)享甸。生產(chǎn)者檢測到鼠標(biāo)和鍵盤事件截碴,并將它們插入到緩沖區(qū)。消費(fèi)者以某種基于優(yōu)先級的方式從緩沖區(qū)取出這些事件蛉威,并顯示在屏幕上日丹。

2、在一個(gè)多媒體系統(tǒng)中蚯嫌,生產(chǎn)者編碼視頻幀哲虾,消費(fèi)者解碼并在屏幕上呈現(xiàn)出來。緩沖區(qū)的目的是為了減少視頻流的抖動齐帚,而這種抖動是由各個(gè)幀的編碼和解碼時(shí)與數(shù)據(jù)相關(guān)的差異引起的妒牙。緩沖區(qū)為生產(chǎn)者提供了一個(gè)槽位池,而為消費(fèi)者提供了一個(gè)已編碼的幀值对妄。

實(shí)際操作:

需要三個(gè)信號量同步對緩沖區(qū)的訪問湘今。一個(gè)提供互斥的緩沖區(qū)訪問,另外兩個(gè)信號量分別記錄空槽位和可用項(xiàng)目的數(shù)量剪菱。

2摩瞎、讀者-寫者問題

問題描述:

一組并發(fā)的線程要訪問一個(gè)共享對象,例如一個(gè)主存中的數(shù)據(jù)結(jié)構(gòu)孝常,或者一個(gè)磁盤上的數(shù)據(jù)庫旗们。有些線程只讀對象,而其他的線程只修改對象构灸。修改對象的線程叫做寫者上渴。只讀對象的線程叫做讀者。

寫者必須擁有對對象的獨(dú)占的訪問喜颁,而讀者可以和無限多個(gè)其他讀者共享對象稠氮。

總結(jié)來說:寫者之間需要互斥,寫者與讀者也需要互斥半开,而讀者之間不需要互斥隔披。

模型實(shí)例:

1、一個(gè)在線航空預(yù)定系統(tǒng)中寂拆,允許有無限多個(gè)客戶同時(shí)查詢作為分配奢米,但是正在預(yù)定作為的客戶必須擁有對數(shù)據(jù)庫的獨(dú)占的訪問。

2纠永、在一個(gè)多線程緩存Web代理中鬓长,無限多個(gè)線程可以從共享頁面緩存中取出已有的頁面,但是任何向緩存中寫入一個(gè)新頁面的線程必須擁有獨(dú)占的訪問渺蒿。

讀者寫者問題有兩種形式:

1痢士、讀者優(yōu)先:不要讓讀者等待,除非已經(jīng)把使用對象的權(quán)限賦予了一個(gè)寫者。換句話說怠蹂,讀者不會因?yàn)橛幸粋€(gè)寫者在等待而等待善延。

2、寫者優(yōu)先:要求一旦一個(gè)寫者準(zhǔn)備好可以寫城侧,它就會盡可能快的完成它的寫操作易遣。同1問題不同,在一個(gè)寫者后到達(dá)的讀者必須等待嫌佑,即使這個(gè)寫者也是在等待豆茫。

解決方式:

通過信號量w控制對訪問對象的臨界區(qū)的訪問。信號量mutex保護(hù)對共享變量readcnt的訪問屋摇,readcnt統(tǒng)計(jì)當(dāng)前在臨界區(qū)中的讀者數(shù)量揩魂。每當(dāng)一個(gè)寫者進(jìn)入臨界區(qū)時(shí),w加鎖炮温,每當(dāng)它離開臨界區(qū)時(shí)火脉,w解鎖。這就保證了任意時(shí)刻臨界區(qū)中最多只有一個(gè)寫者柒啤。

另一方面倦挂,只有第一個(gè)進(jìn)入臨界區(qū)的讀者對w加鎖,而只有最后一個(gè)離開臨界區(qū)的讀者對w解鎖担巩。當(dāng)一個(gè)讀者進(jìn)入和離開臨界區(qū)時(shí)方援,如果還有其他讀者在臨界區(qū)中,那么讀者會忽略互斥鎖w涛癌。這就意味著主要還有一個(gè)讀者占用互斥鎖w犯戏,無限多數(shù)量的讀者就可以沒有障礙的進(jìn)入到臨界區(qū)中。

12.6 使用線程提高并行性

1拳话、多線程可以使用多核笛丙。下圖中給出了順序、并發(fā)和并行程序之間的集合關(guān)系假颇。所有程序的集合能夠劃分成不相交的順序程序集合和并發(fā)程序的結(jié)合。寫順序程序只要一條邏輯流骨稿。寫并發(fā)程序需要多條并發(fā)流笨鸡。并行程序是在一個(gè)運(yùn)行在多處理器上的并發(fā)程序。

2坦冠、單核上多線程運(yùn)行時(shí)間反而會長形耗,因?yàn)榫€程同步的開銷,而并行程序則可以解決這一問題辙浑。在一個(gè)四核處理器上的多線程運(yùn)行效率圖如下:

當(dāng)線程數(shù)量小于核心數(shù)時(shí)激涤,運(yùn)行效率提高,當(dāng)線程數(shù)量大于核心數(shù)時(shí)判呕,反而運(yùn)行效率降低就是因?yàn)榫€程之間上下文切換等同步問題造成的開銷倦踢。

由此理解單核下的多進(jìn)程或多線程編程是為了進(jìn)程或線程間的公平性問題送滞,而效率并不會提高。

12.7 其他并發(fā)問題

1辱挥、線程安全

2犁嗅、可重入性

3、在線程化的程序中使用已存在的庫函數(shù)

4晤碘、競爭

5褂微、死鎖:可以通過下圖來進(jìn)一步理解死鎖


【參考】
[1] 《深入理解計(jì)算機(jī)系統(tǒng)》

歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處 wenmingxing 你好呀《CSAPP》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末园爷,一起剝皮案震驚了整個(gè)濱河市宠蚂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌童社,老刑警劉巖求厕,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叠洗,居然都是意外死亡甘改,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門灭抑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來十艾,“玉大人,你說我怎么就攤上這事腾节⊥担” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵案腺,是天一觀的道長庆冕。 經(jīng)常有香客問我,道長劈榨,這世上最難降的妖魔是什么访递? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮同辣,結(jié)果婚禮上拷姿,老公的妹妹穿的比我還像新娘。我一直安慰自己旱函,他們只是感情好响巢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著棒妨,像睡著了一般踪古。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天伏穆,我揣著相機(jī)與錄音拘泞,去河邊找鬼。 笑死蜈出,一個(gè)胖子當(dāng)著我的面吹牛田弥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铡原,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼偷厦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了燕刻?” 一聲冷哼從身側(cè)響起只泼,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卵洗,沒想到半個(gè)月后请唱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡过蹂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年十绑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酷勺。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡本橙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脆诉,到底是詐尸還是另有隱情甚亭,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布击胜,位于F島的核電站亏狰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏偶摔。R本人自食惡果不足惜暇唾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辰斋。 院中可真熱鬧信不,春花似錦、人聲如沸亡呵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锰什。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汁胆,已是汗流浹背梭姓。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嫩码,地道東北人誉尖。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像铸题,于是被迫代替她去往敵國和親铡恕。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355