世界上,很多事情在同時(shí)發(fā)生湃交。
咖啡廳里同時(shí)坐著很多人熟空,門口的馬路上同時(shí)有多輛車通過。而盯著屏幕的你巡揍,還聽著耳機(jī)里迷人的音樂痛阻,寫代碼的同時(shí),還瀏覽新聞腮敌。
但不知道你有沒有注意到阱当,這里包含了兩個(gè)“同時(shí)”。
“多輛車同時(shí)通過”糜工,無論把時(shí)間切到多細(xì)弊添,一分鐘、一秒鐘捌木,甚至一微秒油坝,都有多輛車在動(dòng)作。這稱為“并行”刨裆。
而“寫代碼的同時(shí)澈圈,瀏覽新聞”,當(dāng)時(shí)間切到足夠細(xì)時(shí)帆啃,只有一個(gè)動(dòng)作在發(fā)生瞬女,在更長(zhǎng)的長(zhǎng)度上看,其實(shí)是兩個(gè)動(dòng)作交替進(jìn)行努潘。這稱為“并發(fā)”诽偷。
Rob Pike有個(gè)演講《Concurrency Is Not Parallelism》,詳細(xì)說明了并發(fā)和并行疯坤。簡(jiǎn)單而言报慕,并發(fā)是指程序的邏輯結(jié)構(gòu)。并發(fā)的程序有兩個(gè)或以上邏輯控制流压怠,如果把這些控制流畫成時(shí)序流程圖眠冈,它們?cè)跁r(shí)間線上是可以重疊的。并行是指程序的運(yùn)行狀態(tài):某一時(shí)刻被多個(gè)CPU流水線同時(shí)處理菌瘫。顯然蜗顽,并行需要硬件支持玄柠。
那么,不難理解:并發(fā)是并行的必要非充分條件诫舅。一個(gè)只有單一邏輯控制流的程序不可能被并行處理,而并發(fā)的程序在一個(gè)CPU流水線上執(zhí)行也不是并行的宫患。
并發(fā)是對(duì)問題更本質(zhì)的描述刊懈,現(xiàn)實(shí)世界很多事情是并發(fā)的,而編程是對(duì)現(xiàn)實(shí)世界的建模娃闲。我們希望寫出的代碼也能有親切的并發(fā)感虚汛。
實(shí)際上操作系統(tǒng)也是這么抽象的,“喝咖啡”皇帮、“寫代碼”卷哩、“看新聞”……這些完成特定動(dòng)作的代碼片段,稱為任務(wù)属拾。多個(gè)任務(wù)交替地在CPU中運(yùn)行著将谊,當(dāng)然,多顆核心時(shí)渐白,也可能并行地運(yùn)行尊浓。
要不要運(yùn)行某個(gè)任務(wù),什么時(shí)候運(yùn)行纯衍,運(yùn)行多久栋齿,操作系統(tǒng)回答這些問題的過程,稱為調(diào)度襟诸。需要注意的是瓦堵,操作系統(tǒng)調(diào)度的單位不是任務(wù),而是線程歌亲。線程是操作系統(tǒng)對(duì)并發(fā)的實(shí)現(xiàn)菇用。
在同一個(gè)線程內(nèi)的任務(wù)是嚴(yán)格按代碼順序執(zhí)行的,只有多個(gè)線程上的任務(wù)才會(huì)交替執(zhí)行应结。而我們寫出的代碼默認(rèn)是在一個(gè)線程內(nèi)的刨疼。這樣一來,如果沒有特別設(shè)計(jì)鹅龄,代碼是串行執(zhí)行揩慕,而不是并發(fā)執(zhí)行。
比如扮休,某段代碼先執(zhí)行A部分迎卤,并把A的結(jié)果打印出來,接著執(zhí)行與A沒有任何關(guān)系的B部分玷坠,示意如下:
整體是一個(gè)任務(wù)蜗搔,并按順序一步步執(zhí)行劲藐,總耗時(shí)是T(A)+T(print A) + T(B)。如果拆成兩個(gè)任務(wù)樟凄,分別在各自線程中執(zhí)行聘芜,示意如下:
B不必等A的print結(jié)束再執(zhí)行,哪怕只有一顆CPU核心缝龄。因?yàn)橐粋€(gè)調(diào)度單位變成了兩個(gè)汰现,當(dāng)其中一個(gè)不占用CPU時(shí)間(print)時(shí),另一個(gè)就可能被執(zhí)行叔壤∠顾牵總耗時(shí)不超過任一任務(wù)的耗時(shí)。
使用多個(gè)線程炼绘,讓更多核心派上用場(chǎng)嗅战,提高了資源的利用率,加快了程序執(zhí)行速度俺亮。同時(shí)還有助于獲得更高的吞吐率驮捍。當(dāng)某個(gè)線程在等待I/O操作完成,另一個(gè)線程可以繼續(xù)運(yùn)行脚曾,使得程序整體能在I/O阻塞時(shí)繼續(xù)工作厌漂。好比在等公交車到站的同時(shí)看手機(jī),而不是等上車后才開始看斟珊。
了解并發(fā)的好處之后苇倡,也得聽一聽反對(duì)的聲音,尤其是當(dāng)這個(gè)聲音來自于linux和git之父Linus時(shí)囤踩。2014年旨椒,Linus說道:
Where the hell do you envision that those magical parallel algorithms would be used?
The only place where parallelism matters is in graphics or on the server side, where we already largely have it. Pushing it anywhere else is just pointless.
Trust me, concurrency is hard. There's a reason all the examples of "look how easy it is to parallellize things" tend to use simple arrays and don't ever have allocations or freeing of the objects.
People who think that the future is highly parallel are invariably completely unaware of just how hard concurrency really is.
并發(fā)編程并不是沒有代價(jià)的,而且可以說有高昂的代價(jià)堵漱。雖然人可以同時(shí)做多件事综慎,但不意味著擅長(zhǎng)同時(shí)做多件事,也不代表同時(shí)做的效率更高勤庐。相反示惊,人擅長(zhǎng)的是串行模式,專注于一件事愉镰,做完之后再做另一件米罚。
并發(fā)程序的設(shè)計(jì)和實(shí)現(xiàn)異常復(fù)雜。如何恰當(dāng)?shù)厍蟹止δ芫鸵呀?jīng)很考驗(yàn)人丈探,再考慮到多線程的亂序執(zhí)行录择,以及線程之間的協(xié)調(diào)需求,只要稍不留神,就會(huì)失之毫厘隘竭、謬以千里塘秦。開發(fā)、閱讀和調(diào)試都相當(dāng)困難动看,即使是實(shí)戰(zhàn)多年的并發(fā)專家尊剔,也不敢掉以輕心。
因此菱皆,需要更加審慎地對(duì)待并發(fā)技術(shù)赋兵,不盲目使用。先實(shí)現(xiàn)功能搔预,再關(guān)心性能是否足夠。
The strategy is definitely: first make it work, then make it right, and, finally, make it fast.——Kent Beck
當(dāng)然叶组,Linus也認(rèn)可并發(fā)在顯卡和服務(wù)器編程領(lǐng)域的必要性拯田。對(duì)于前者,由于深度學(xué)習(xí)的全面流行甩十,顯卡廠商N(yùn)vidia股份一再創(chuàng)新高船庇,其火爆程度可見一斑。為并發(fā)而生的Golang(語(yǔ)言級(jí)別的并發(fā)支持侣监,簡(jiǎn)單的并發(fā)模型)在服務(wù)器端大行其道鸭轮,也證明了Linus所言非虛。
并發(fā)編程領(lǐng)域所關(guān)注的問題橄霉,主要包括:
- 如何抽象與建模
- 并發(fā)粒度的控制
- 工作者的調(diào)度
- 工作者間的協(xié)作
通常窃爷,對(duì)這些問題的考慮只發(fā)生在同一程序內(nèi),甚至只是其中某一模塊或功能點(diǎn)姓蜂。但如果縮小一下放大鏡按厘,從更高的位置看全景圖,則會(huì)發(fā)現(xiàn)并發(fā)編程和分布式編程頗有幾分相似钱慢。并發(fā)系統(tǒng)內(nèi)逮京,同一進(jìn)程內(nèi)不同線程之間的關(guān)系,對(duì)應(yīng)了分布式系統(tǒng)中束莫,不同機(jī)器上部署的進(jìn)程之間的關(guān)系懒棉。而線程和進(jìn)程有很多相似之處,只不過分布式系統(tǒng)多了網(wǎng)絡(luò)相關(guān)的故障览绿。因此策严,對(duì)并發(fā)編程的學(xué)習(xí)和研究,對(duì)分布式系統(tǒng)架構(gòu)的設(shè)計(jì)也有借鑒意義饿敲。
REF.