最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Linux學(xué)習(xí) 并發(fā)與同步

2021-12-09 17:58 作者:后端攻城獅哇  | 我要投稿

典型的 UNIX 系統(tǒng)都支持一個進(jìn)程創(chuàng)建多個線程 (thread)。在Linux進(jìn)程基礎(chǔ)中提到,Linux 以進(jìn)程為單位組織操作,Linux 中的線程也都基于進(jìn)程。盡管實現(xiàn)方式有異于其它的 UNIX 系統(tǒng),但 Linux 的多線程在邏輯和使用上與真正的多線程并沒有差別。

多線程

我們先來看一下什么是多線程。在 Linux 從程序到進(jìn)程中,我們看到了一個程序在內(nèi)存中的表示。這個程序的整個運(yùn)行過程中,只有一個控制權(quán)的存在。當(dāng)函數(shù)被調(diào)用的時候,該函數(shù)獲得控制權(quán),成為激活(active)函數(shù),然后運(yùn)行該函數(shù)中的指令。與此同時,其它的函數(shù)處于離場狀態(tài),并不運(yùn)行。如下圖所示:

Linux從程序到進(jìn)程

我們看到,各個方塊之間由箭頭連接。各個函數(shù)就像是連在一根線上一樣,計算機(jī)像一條流水線一樣執(zhí)行各個函數(shù)中定義的操作。這樣的一個程序叫做?單線程程序。

多線程就是允許一個進(jìn)程內(nèi)存在?多個控制權(quán),以便讓多個函數(shù)同時處于激活狀態(tài),從而讓多個函數(shù)的操作同時運(yùn)行。即使是單CPU的計算機(jī),也可以通過?不停地在不同線程的指令間切換,從而造成多線程同時運(yùn)行的效果。如下圖所示,就是一個多線程的流程:

main() 到 func3() 再到 main() 構(gòu)成一個線程,此外 func1() 和 func2() 構(gòu)成另外兩個線程。操作系統(tǒng)一般都有一些系統(tǒng)調(diào)用來讓你將一個函數(shù)運(yùn)行成為一個新的線程。

回憶我們在?Linux 從程序到進(jìn)程中提到的棧的功能和用途。一個棧,只有最下方的幀可被讀寫。相應(yīng)的,也只有該幀對應(yīng)的那個函數(shù)被激活,處于工作狀態(tài)。為了實現(xiàn)多線程,我們必須繞開棧的限制。為此,創(chuàng)建一個新的線程時,我們?yōu)檫@個線程建一個新的棧。每個棧對應(yīng)一個線程。當(dāng)某個棧執(zhí)行到全部彈出時,對應(yīng)線程完成任務(wù),并收工。所以,多線程的進(jìn)程在內(nèi)存中有多個棧。多個棧之間以一定的空白區(qū)域隔開,以備棧的增長。每個線程可調(diào)用自己棧最下方的幀中的參數(shù)和變量,并與其它線程共享內(nèi)存中的 Text,heap 和 global data 區(qū)域。對應(yīng)上面的例子,我們的進(jìn)程空間中需要有3個棧。

(要注意的是,對于多線程來說,由于同一個進(jìn)程空間中存在多個棧,任何一個空白區(qū)域被填滿都會導(dǎo)致stack overflow的問題。)


并發(fā)

多線程相當(dāng)于一個并發(fā)(concunrrency)系統(tǒng)。并發(fā)系統(tǒng)一般同時執(zhí)行多個任務(wù)。如果多個任務(wù)可以共享資源,特別是同時寫入某個變量的時候,就需要解決同步的問題。比如說,我們有一個多線程火車售票系統(tǒng),用全局變量i存儲剩余的票數(shù)。多個線程不斷地賣票(i = i - 1),直到剩余票數(shù)為0。所以每個都需要執(zhí)行如下操作:

如果只有一個線程執(zhí)行上面的程序的時候(相當(dāng)于一個窗口售票),則沒有問題。但如果多個線程都執(zhí)行上面的程序(相當(dāng)于多個窗口售票), 我們就會出現(xiàn)問題。我們會看到,其根本原因在于同時發(fā)生的各個線程都可以對i讀取和寫入。

我們這里的if結(jié)構(gòu)會給CPU兩個指令, 一個是判斷是否有剩余的票(i != 0), 一個是賣票 (i = i -1)。某個線程會先判斷是否有票(比如說此時i為1),但兩個指令之間存在一個時間窗口,其它線程可能在此時間窗口內(nèi)執(zhí)行賣票操作(i = i -1),導(dǎo)致該線程賣票的條件不再成立。但該線程由于已經(jīng)執(zhí)行過了判斷指令,所以無從知道i發(fā)生了變化,所以繼續(xù)執(zhí)行賣票指令,以至于賣出不存在的票 (i成為負(fù)數(shù))。對于一個真實的售票系統(tǒng)來說,這將成為一個嚴(yán)重的錯誤 (售出了過多的票,火車爆滿)。

在并發(fā)情況下,指令執(zhí)行的先后順序由內(nèi)核決定。同一個線程內(nèi)部,指令按照先后順序執(zhí)行,但不同線程之間的指令很難說清除哪一個會先執(zhí)行。如果運(yùn)行的結(jié)果依賴于不同線程執(zhí)行的先后的話,那么就會造成競爭條件(race condition),在這樣的狀況下,計算機(jī)的結(jié)果很難預(yù)知。我們應(yīng)該盡量避免競爭條件的形成。最常見的解決競爭條件的方法是將原先分離的兩個指令構(gòu)成不可分隔的一個原子操作(atomic operation),而其它任務(wù)不能插入到原子操作中。

多線程同步

對于多線程程序來說,同步(synchronization)是指在一定的時間內(nèi)只允許某一個線程訪問某個資源 。而在此時間內(nèi),不允許其它的線程訪問該資源。我們可以通過互斥鎖(mutex),條件變量(condition variable)和讀寫鎖(reader-writer lock)來同步資源。

1) 互斥鎖

互斥鎖是一個特殊的變量,它有鎖上(lock)和打開(unlock)兩個狀態(tài)?;コ怄i一般被設(shè)置成全局變量。打開的互斥鎖可以由某個線程獲得。一旦獲得,這個互斥鎖會鎖上,此后只有該線程有權(quán)打開。其它想要獲得互斥鎖的線程,會等待直到互斥鎖再次打開的時候。我們可以將互斥鎖想像成為一個只能容納一個人的洗手間,當(dāng)某個人進(jìn)入洗手間的時候,可以從里面將洗手間鎖上。其它人只能在互斥鎖外面等待那個人出來,才能進(jìn)去。在外面等候的人并沒有排隊,誰先看到洗手間空了,就可以首先沖進(jìn)去。

上面的問題很容易使用互斥鎖的問題解決,每個線程的程序可以改為:

第一個執(zhí)行mutex_lock()的線程會先獲得mu。其它想要獲得mu的線程必須等待,直到第一個線程執(zhí)行到mutex_unlock()釋放mu,才可以獲得mu,并繼續(xù)執(zhí)行線程。所以線程在mutex_lock()和mutex_unlock()之間的操作時,不會被其它線程影響,就構(gòu)成了一個原子操作。

需要注意的時候,如果存在某個線程依然使用原先的程序 (即不嘗試獲得mu,而直接修改i),互斥鎖不能阻止該程序修改i,互斥鎖就失去了保護(hù)資源的意義。所以,互斥鎖機(jī)制需要程序員自己來寫出完善的程序來實現(xiàn)互斥鎖的功能。我們下面講的其它機(jī)制也是如此。

2) 條件變量

條件變量是另一種常用的變量。它也常常被保存為全局變量,并和互斥鎖合作。

假設(shè)這樣一個狀況: 有100個工人,每人負(fù)責(zé)裝修一個房間。當(dāng)有10個房間裝修完成的時候,老板就通知相應(yīng)的十個工人一起去喝啤酒。

我們?nèi)绾螌崿F(xiàn)呢?老板讓工人在裝修好房間之后,去檢查已經(jīng)裝修好的房間數(shù)。但多線程條件下,會有競爭條件的危險。也就是說,其他工人有可能會在該工人裝修好房子和檢查之間完成工作。采用下面方式解決:

上面使用了條件變量。條件變量除了要和互斥鎖配合之外,還需要和另一個全局變量配合(這里的num, 也就是裝修好的房間數(shù))。這個全局變量用來構(gòu)成各個條件。

具體思路如下。我們讓工人在裝修好房間(num = num + 1)之后,去檢查已經(jīng)裝修好的房間數(shù)( num < 10 )。由于mu被鎖上,所以不會有其他工人在此期間裝修房間(改變num的值)。如果該工人是前十個完成的人,那么我們就調(diào)用cond_wait()函數(shù)。
cond_wait()做兩件事情,一個是釋放mu,從而讓別的工人可以建房。另一個是等待,直到cond的通知。這樣的話,符合條件的線程就開始等待。

當(dāng)有通知(第十個房間已經(jīng)修建好)到達(dá)的時候,condwait()會再次鎖上mu。線程的恢復(fù)運(yùn)行,執(zhí)行下一句prinft("drink beer")?(喝啤酒!)。從這里開始,直到mutex_unlock(),就構(gòu)成了另一個互斥鎖結(jié)構(gòu)。

那么,前面十個調(diào)用cond_wait()的線程如何得到的通知呢?我們注意到elif if,即修建好第11個房間的人,負(fù)責(zé)調(diào)用cond_broadcast()。這個函數(shù)會給所有調(diào)用cond_wait()的線程放送通知,以便讓那些線程恢復(fù)運(yùn)行。

條件變量特別適用于多個線程等待某個條件的發(fā)生。如果不使用條件變量,那么每個線程就需要不斷嘗試獲得互斥鎖并檢查條件是否發(fā)生,這樣大大浪費(fèi)了系統(tǒng)的資源。

3) 讀寫鎖

讀寫鎖與互斥鎖非常相似。r、RW lock有三種狀態(tài):?共享讀取鎖(shared-read),?互斥寫入鎖(exclusive-write lock),?打開(unlock)。后兩種狀態(tài)與之前的互斥鎖兩種狀態(tài)完全相同。

一個unlock的RW lock可以被某個線程獲取R鎖或者W鎖。

如果被一個線程獲得R鎖,RW lock可以被其它線程繼續(xù)獲得R鎖,而不必等待該線程釋放R鎖。但是,如果此時有其它線程想要獲得W鎖,它必須等到所有持有共享讀取鎖的線程釋放掉各自的R鎖。

如果一個鎖被一個線程獲得W鎖,那么其它線程,無論是想要獲取R鎖還是W鎖,都必須等待該線程釋放W鎖。

這樣,多個線程就可以同時讀取共享資源。而具有危險性的寫入操作則得到了互斥鎖的保護(hù)。

我們需要同步并發(fā)系統(tǒng),這為程序員編程帶來了難度。但是多線程系統(tǒng)可以很好的解決許多IO瓶頸的問題。比如我們監(jiān)聽網(wǎng)絡(luò)端口。如果我們只有一個線程,那么我們必須監(jiān)聽,接收請求,處理,回復(fù),再監(jiān)聽。如果我們使用多線程系統(tǒng),則可以讓多個線程監(jiān)聽。當(dāng)我們的某個線程進(jìn)行處理的時候,我們還可以有其他的線程繼續(xù)監(jiān)聽,這樣,就大大提高了系統(tǒng)的利用率。在數(shù)據(jù)越來越大,服務(wù)器讀寫操作越來越多的今天,這具有相當(dāng)?shù)囊饬x。多線程還可以更有效地利用多CPU的環(huán)境。


本文中的程序采用偽C的寫法。不同的語言有不同的函數(shù)名(比如mutex_lock)。這里關(guān)注的是邏輯上的概念,而不是具體的實現(xiàn)和語言規(guī)范。



Linux學(xué)習(xí) 并發(fā)與同步的評論 (共 條)

分享到微博請遵守國家法律
宜昌市| 英德市| 湖北省| 开封市| 资阳市| 鹿邑县| 黄龙县| 石渠县| 迁安市| 新郑市| 固始县| 肃宁县| 松溪县| 灵寿县| 兴安县| 怀来县| 托克逊县| 奇台县| 巴彦淖尔市| 澜沧| 勐海县| 庆阳市| 万荣县| 乾安县| 六盘水市| 长顺县| 东城区| 剑川县| 通州区| 安徽省| 松滋市| 石狮市| 淄博市| 余江县| 高唐县| 建昌县| 巴东县| 巫溪县| 丹棱县| 彭阳县| 仁寿县|