久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

并發(fā)之痛 Thread,Goroutine,,Actor

 ycwu314 2016-03-13

編者按:本文是王淵命在 2 月 27 日 Gopher 北京聚會(huì)演講基礎(chǔ)上整理而成,,進(jìn)行了一些補(bǔ)充以及調(diào)整,,投稿給高可用架構(gòu)首發(fā)。轉(zhuǎn)載請(qǐng)注明來自高可用架構(gòu)公眾號(hào)「ArchNotes」,。


王淵命,,團(tuán)隊(duì)協(xié)作 IM 服務(wù) Grouk 聯(lián)合創(chuàng)始人及 CTO,技術(shù)極客,,曾任新浪微博架構(gòu)師,、微米技術(shù)總監(jiān)。2014 年作為聯(lián)合創(chuàng)始人創(chuàng)立團(tuán)隊(duì)協(xié)作 IM 服務(wù) Grouk,,長(zhǎng)期關(guān)注團(tuán)隊(duì)協(xié)作基礎(chǔ)工具和研發(fā)環(huán)境建設(shè),,Docker 深度實(shí)踐者。


聊這個(gè)話題之前,,先梳理下兩個(gè)概念,,幾乎所有講并發(fā)的文章都要先講這兩個(gè)概念:




  • 并發(fā)(concurrency) 并發(fā)的關(guān)注點(diǎn)在于任務(wù)切分。舉例來說,,你是一個(gè)創(chuàng)業(yè)公司的CEO,,開始只有你一個(gè)人,你一人分飾多角,,一會(huì)做產(chǎn)品規(guī)劃,,一會(huì)寫代碼,一會(huì)見客戶,,雖然你不能見客戶的同時(shí)寫代碼,,但由于你切分了任務(wù),分配了時(shí)間片,,表現(xiàn)出來好像是多個(gè)任務(wù)一起在執(zhí)行,。

  • 并行(parallelism) 并行的關(guān)注點(diǎn)在于同時(shí)執(zhí)行。還是上面的例子,,你發(fā)現(xiàn)你自己太忙了,,時(shí)間分配不過來,于是請(qǐng)了工程師,,產(chǎn)品經(jīng)理,,市場(chǎng)總監(jiān),各司一職,,這時(shí)候多個(gè)任務(wù)可以同時(shí)執(zhí)行了,。


所以總結(jié)下,并發(fā)并不要求必須并行,,可以用時(shí)間片切分的方式模擬,,比如單核 CPU 上的多任務(wù)系統(tǒng),并發(fā)的要求是任務(wù)能切分成獨(dú)立執(zhí)行的片段。而并行關(guān)注的是同時(shí)執(zhí)行,,必須是多(核)CPU,,要能并行的程序必須是支持并發(fā)的。本文大多數(shù)情況下不會(huì)嚴(yán)格區(qū)分這兩個(gè)概念,,默認(rèn)并發(fā)就是指并行機(jī)制下的并發(fā),。


為什么并發(fā)程序這么難?

We believe that writing correct concurrent, fault-tolerant and scalable applications is too hard. Most of the time it’s because we are using the wrong tools and the wrong level of abstraction. —— Akka


Akka 官方文檔開篇這句話說的好,之所以寫正確的并發(fā),,容錯(cuò),,可擴(kuò)展的程序如此之難,是因?yàn)槲覀冇昧隋e(cuò)誤的工具和錯(cuò)誤的抽象,。(當(dāng)然該文檔本來的意思是 Akka 是正確的工具,,但我們可以獨(dú)立的看待這句話)。


那我們從最開始梳理下程序的抽象,。開始我們的程序是面向過程的,,數(shù)據(jù)結(jié)構(gòu) + func。后來有了面向?qū)ο?,?duì)象組合了數(shù)結(jié)構(gòu)和 func,,我們想用模擬現(xiàn)實(shí)世界的方式,抽象出對(duì)象,,有狀態(tài)和行為。但無論是面向過程的 func 還是面向?qū)ο蟮?func,,本質(zhì)上都是代碼塊的組織單元,,本身并沒有包含代碼塊的并發(fā)策略的定義。于是為了解決并發(fā)的需求,,引入了 Thread(線程)的概念,。


線程(Thread)


  1. 系統(tǒng)內(nèi)核態(tài),更輕量的進(jìn)程

  2. 由系統(tǒng)內(nèi)核進(jìn)行調(diào)度

  3. 同一進(jìn)程的多個(gè)線程可共享資源


線程的出現(xiàn)解決了兩個(gè)問題,,一個(gè)是 GUI 出現(xiàn)后急切需要并發(fā)機(jī)制來保證用戶界面的響應(yīng),。第二是互聯(lián)網(wǎng)發(fā)展后帶來的多用戶問題。最早的 CGI 程序很簡(jiǎn)單,,將通過腳本將原來單機(jī)版的程序包裝在一個(gè)進(jìn)程里,,來一個(gè)用戶就啟動(dòng)一個(gè)進(jìn)程。但明顯這樣承載不了多少用戶,,并且如果進(jìn)程間需要共享資源還得通過進(jìn)程間的通信機(jī)制,,線程的出現(xiàn)緩解了這個(gè)問題。


線程的使用比較簡(jiǎn)單,,如果你覺得這塊代碼需要并發(fā),,就把它放在單獨(dú)的線程里執(zhí)行,由系統(tǒng)負(fù)責(zé)調(diào)度,具體什么時(shí)候使用線程,,要用多少個(gè)線程,,由調(diào)用方?jīng)Q定,但定義方并不清楚調(diào)用方會(huì)如何使用自己的代碼,,很多并發(fā)問題都是因?yàn)檎`用導(dǎo)致的,,比如 Go 中的 map 以及 Java 的 HashMap 都不是并發(fā)安全的,誤用在多線程環(huán)境就會(huì)導(dǎo)致問題,。另外也帶來復(fù)雜度:


  1. 競(jìng)態(tài)條件(race conditions) 如果每個(gè)任務(wù)都是獨(dú)立的,,不需要共享任何資源,那線程也就非常簡(jiǎn)單,。但世界往往是復(fù)雜的,,總有一些資源需要共享,比如前面的例子,,開發(fā)人員和市場(chǎng)人員同時(shí)需要和 CEO 商量一個(gè)方案,,這時(shí)候 CEO 就成了競(jìng)態(tài)條件。

  2. 依賴關(guān)系以及執(zhí)行順序 如果線程之間的任務(wù)有依賴關(guān)系,,需要等待以及通知機(jī)制來進(jìn)行協(xié)調(diào),。比如前面的例子,如果產(chǎn)品和 CEO 討論的方案依賴于市場(chǎng)和 CEO 討論的方案,,這時(shí)候就需要協(xié)調(diào)機(jī)制保證順序,。


為了解決上述問題,我們引入了許多復(fù)雜機(jī)制來保證:


  • Mutex(Lock)(Go 里的 sync 包, Java 的 concurrent 包)通過互斥量來保護(hù)數(shù)據(jù),,但有了鎖,,明顯就降低了并發(fā)度。

  • Semaphore 通過信號(hào)量來控制并發(fā)度或者作為線程間信號(hào)(signal)通知,。

  • Volatile Java 專門引入了 volatile 關(guān)鍵詞來,,來降低只讀情況下的鎖的使用。

  • Compare-and-swap 通過硬件提供的 CAS 機(jī)制保證原子性(atomic),,也是降低鎖的成本的機(jī)制,。


如果說上面兩個(gè)問題只是增加了復(fù)雜度,我們通過深入學(xué)習(xí),,嚴(yán)謹(jǐn)?shù)?Code Review,,全面的并發(fā)測(cè)試(比如 Go 語言中單元測(cè)試的時(shí)候加上 -race 參數(shù)),一定程度上能解決(當(dāng)然這個(gè)也是有爭(zhēng)議的,,有論文認(rèn)為當(dāng)前的大多數(shù)并發(fā)程序沒出問題只是并發(fā)度不夠,,如果 CPU 核數(shù)繼續(xù)增加,程序運(yùn)行的時(shí)間更長(zhǎng),,很難保證不出問題),。但最讓人頭痛的還是下面這個(gè)問題:


系統(tǒng)里到底需要多少線程,?


這個(gè)問題我們先從硬件資源入手,考慮下線程的成本:


  • 內(nèi)存(線程的??臻g)

    每個(gè)線程都需要一個(gè)棧(Stack)空間來保存掛起(suspending)時(shí)的狀態(tài),。Java 的棧空間(64位VM)默認(rèn)是 1024k,,不算別的內(nèi)存,,只是棧空間,,啟動(dòng) 1024 個(gè)線程就要 1G 內(nèi)存,。雖然可以用 -Xss 參數(shù)控制,但由于線程是本質(zhì)上也是進(jìn)程,,系統(tǒng)假定是要長(zhǎng)期運(yùn)行的,,棧空間太小會(huì)導(dǎo)致稍復(fù)雜的遞歸調(diào)用(比如復(fù)雜點(diǎn)的正則表達(dá)式匹配)導(dǎo)致棧溢出,。所以調(diào)整參數(shù)治標(biāo)不治本,。

  • 調(diào)度成本(context-switch)

    我在個(gè)人電腦上做的一個(gè)非嚴(yán)格測(cè)試,模擬兩個(gè)線程互相喚醒輪流掛起,,線程切換成本大約 6000 納秒/次,。這個(gè)還沒考慮棧空間大小的影響,。國(guó)外一篇論文專門分析線程切換的成本,,基本上得出的結(jié)論是切換成本和棧空間使用大小直接相關(guān),。




  • CPU使用率

    我們搞并發(fā)最主要的一個(gè)目標(biāo)就是我們有了多核,,想提高CPU利用率,最大限度的壓榨硬件資源,,從這個(gè)角度考慮,我們應(yīng)該用多少線程呢,?



這個(gè)我們可以通過一個(gè)公式計(jì)算出來,,100 / (15 + 5) * 4 = 20,用 20 個(gè)線程最合適,。但一方面網(wǎng)絡(luò)的時(shí)間不是固定的,,另外一方面,如果考慮到其他瓶頸資源呢,?比如鎖,,比如數(shù)據(jù)庫連接池,就會(huì)更復(fù)雜,。


作為一個(gè) 1 歲多孩子的父親,,認(rèn)為這個(gè)問題的難度好比你要寫個(gè)給孩子喂飯的程序,需要考慮『給孩子喂多少飯合適?』,,這個(gè)問題有以下回答以及策略:


  • 孩子不吃了就好了(但孩子貪玩,,不吃了可能是想去玩了)

  • 孩子吃飽了就好了(廢話,你怎么知道孩子吃飽了,?孩子又不會(huì)說話)

  • 逐漸增量,,長(zhǎng)期觀察,然后計(jì)算一個(gè)平均值(這可能是我們調(diào)整線程常用的策略,,但增量增加到多少合適呢,?)

  • 孩子吃吐了就別喂了(如果用逐漸增量的模式,通過外部觀察,,可能會(huì)到達(dá)這個(gè)邊界條件,。系統(tǒng)性能如果因?yàn)榫€程的增加倒退了,就別增加線程了)

  • 沒控制好邊界,,把孩子給給撐壞了 (這熊爸爸也太恐怖了,。但調(diào)整線程的時(shí)候往往不小心可能就把系統(tǒng)搞掛了)


通過這個(gè)例子我們可以看出,從外部系統(tǒng)來觀察,,或者以經(jīng)驗(yàn)的方式進(jìn)行計(jì)算,,都是非常困難的。于是結(jié)論是:


讓孩子會(huì)說話,,吃飽了自己說,,自己學(xué)會(huì)吃飯,自管理是最佳方案,。


然并卵,,計(jì)算機(jī)不會(huì)自己說話,如何自管理,?


但我們從以上的討論可以得出一個(gè)結(jié)論:


  • 線程的成本較高(內(nèi)存,,調(diào)度)不可能大規(guī)模創(chuàng)建

  • 應(yīng)該由語言或者框架動(dòng)態(tài)解決這個(gè)問題


線程池方案

Java 1.5 后,Doug Lea 的 Executor 系列被包含在默認(rèn)的 JDK 內(nèi),,是典型的線程池方案,。


線程池一定程度上控制了線程的數(shù)量,實(shí)現(xiàn)了線程復(fù)用,,降低了線程的使用成本,。但還是沒有解決數(shù)量的問題,線程池初始化的時(shí)候還是要設(shè)置一個(gè)最小和最大線程數(shù),,以及任務(wù)隊(duì)列的長(zhǎng)度,,自管理只是在設(shè)定范圍內(nèi)的動(dòng)態(tài)調(diào)整。另外不同的任務(wù)可能有不同的并發(fā)需求,,為了避免互相影響可能需要多個(gè)線程池,,最后導(dǎo)致的結(jié)果就是 Java 的系統(tǒng)里充斥了大量的線程池,。


新的思路

從前面的分析我們可以看出,如果線程是一直處于運(yùn)行狀態(tài),,我們只需設(shè)置和 CPU 核數(shù)相等的線程數(shù)即可,,這樣就可以最大化的利用 CPU,并且降低切換成本以及內(nèi)存使用,。但如何做到這一點(diǎn)呢,?


陳力就列,不能者止


這句話是說,,能干活的代碼片段就放在線程里,,如果干不了活(需要等待,被阻塞等),,就摘下來,。通俗的說就是不要占著茅坑不拉屎,如果拉不出來,,需要醞釀下,,先把茅坑讓出來,因?yàn)槊┛邮窍∪辟Y源,。


要做到這點(diǎn)一般有兩種方案:


  1. 異步回調(diào)方案 典型如 NodeJS,,遇到阻塞的情況,比如網(wǎng)絡(luò)調(diào)用,,則注冊(cè)一個(gè)回調(diào)方法(其實(shí)還包括了一些上下文數(shù)據(jù)對(duì)象)給 IO 調(diào)度器(Linux 下是 Libev,,調(diào)度器在另外的線程里),當(dāng)前線程就被釋放了,,去干別的事情了,。等數(shù)據(jù)準(zhǔn)備好,調(diào)度器會(huì)將結(jié)果傳遞給回調(diào)方法然后執(zhí)行,,執(zhí)行其實(shí)不在原來發(fā)起請(qǐng)求的線程里了,,但對(duì)用戶來說無感知。但這種方式的問題就是很容易遇到 callback hell,,因?yàn)樗械淖枞僮鞫急仨毊惒?,否則系統(tǒng)就卡死了。還有就是異步的方式有點(diǎn)違反人類思維習(xí)慣,,人類還是習(xí)慣同步的方式。

  2. GreenThread/Coroutine/Fiber 方案 這種方案其實(shí)和上面的方案本質(zhì)上區(qū)別不大,,關(guān)鍵在于回調(diào)上下文的保存以及執(zhí)行機(jī)制,。為了解決回調(diào)方法帶來的難題,這種方案的思路是寫代碼的時(shí)候還是按順序?qū)?,但遇?IO 等阻塞調(diào)用時(shí),,將當(dāng)前的代碼片段暫停,,保存上下文,讓出當(dāng)前線程,。等 IO 事件回來,,然后再找個(gè)線程讓當(dāng)前代碼片段恢復(fù)上下文繼續(xù)執(zhí)行,寫代碼的時(shí)候感覺好像是同步的,,仿佛在同一個(gè)線程完成的,,但實(shí)際上系統(tǒng)可能切換了線程,但對(duì)程序無感,。


GreenThread


  • 用戶空間 首先是在用戶空間,,避免內(nèi)核態(tài)和用戶態(tài)的切換導(dǎo)致的成本。

  • 由語言或者框架層調(diào)度

  • 更小的??臻g允許創(chuàng)建大量實(shí)例(百萬級(jí)別)


幾個(gè)概念


  • Continuation 這個(gè)概念不熟悉 FP 編程的人可能不太熟悉,,不過這里可以簡(jiǎn)單的顧名思義,可以理解為讓我們的程序可以暫停,,然后下次調(diào)用繼續(xù)(contine)從上次暫停的地方開始的一種機(jī)制,。相當(dāng)于程序調(diào)用多了一種入口。

  • Coroutine 是 Continuation 的一種實(shí)現(xiàn),,一般表現(xiàn)為語言層面的組件或者類庫,。主要提供 yield,resume 機(jī)制,。

  • Fiber 和 Coroutine 其實(shí)是一體兩面的,,主要是從系統(tǒng)層面描述,可以理解成 Coroutine 運(yùn)行之后的東西就是 Fiber,。


Goroutine

Goroutine 其實(shí)就是前面 GreenThread 系列解決方案的一種演進(jìn)和實(shí)現(xiàn),。


  • 首先,它內(nèi)置了 Coroutine 機(jī)制,。因?yàn)橐脩魬B(tài)的調(diào)度,,必須有可以讓代碼片段可以暫停/繼續(xù)的機(jī)制。

  • 其次,,它內(nèi)置了一個(gè)調(diào)度器,,實(shí)現(xiàn)了 Coroutine 的多線程并行調(diào)度,同時(shí)通過對(duì)網(wǎng)絡(luò)等庫的封裝,,對(duì)用戶屏蔽了調(diào)度細(xì)節(jié),。

  • 最后,提供了 Channel 機(jī)制,,用于 Goroutine 之間通信,,實(shí)現(xiàn) CSP 并發(fā)模型(Communicating Sequential Processes)。因?yàn)?Go 的 Channel 是通過語法關(guān)鍵詞提供的,,對(duì)用戶屏蔽了許多細(xì)節(jié),。其實(shí) Go 的 Channel 和 Java 中的 SynchronousQueue 是一樣的機(jī)制,,如果有 buffer 其實(shí)就是 ArrayBlockQueue。


Goroutine 調(diào)度器




這個(gè)圖一般講 Goroutine 調(diào)度器的地方都會(huì)引用,,想要仔細(xì)了解的可以看看原博客(小編:點(diǎn)擊閱讀原文獲?。_@里只說明幾點(diǎn):


  1. M 代表系統(tǒng)線程,,P 代表處理器(核),,G 代表 Goroutine。Go 實(shí)現(xiàn)了 M : N 的調(diào)度,,也就是說線程和 Goroutine 之間是多對(duì)多的關(guān)系,。這點(diǎn)在許多GreenThread / Coroutine 的調(diào)度器并沒有實(shí)現(xiàn)。比如 Java 1.1 版本之前的線程其實(shí)是 GreenThread(這個(gè)詞就來源于 Java),,但由于沒實(shí)現(xiàn)多對(duì)多的調(diào)度,,也就是沒有真正實(shí)現(xiàn)并行,發(fā)揮不了多核的優(yōu)勢(shì),,所以后來改成基于系統(tǒng)內(nèi)核的 Thread 實(shí)現(xiàn)了,。

  2. 某個(gè)系統(tǒng)線程如果被阻塞,排列在該線程上的 Goroutine 會(huì)被遷移,。當(dāng)然還有其他機(jī)制,,比如 M 空閑了,如果全局隊(duì)列沒有任務(wù),,可能會(huì)從其他 M 偷任務(wù)執(zhí)行,,相當(dāng)于一種 rebalance 機(jī)制。這里不再細(xì)說,,有需要看專門的分析文章,。

  3. 具體的實(shí)現(xiàn)策略和我們前面分析的機(jī)制類似。系統(tǒng)啟動(dòng)時(shí),,會(huì)啟動(dòng)一個(gè)獨(dú)立的后臺(tái)線程(不在 Goroutine 的調(diào)度線程池里),,啟動(dòng) netpoll 的輪詢。當(dāng)有 Goroutine 發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí),,網(wǎng)絡(luò)庫會(huì)將 fd(文件描述符)和 pollDesc(用于描述 netpoll 的結(jié)構(gòu)體,,包含因?yàn)樽x / 寫這個(gè) fd 而阻塞的 Goroutine)關(guān)聯(lián)起來,然后調(diào)用 runtime.gopark 方法,,掛起當(dāng)前的 Goroutine,。當(dāng)后臺(tái)的 netpoll 輪詢獲取到 epoll(Linux 環(huán)境下)的 event,會(huì)將 event 中的 pollDesc 取出來,,找到關(guān)聯(lián)的阻塞 Goroutine,,并進(jìn)行恢復(fù)。


Goroutine 是銀彈么?


Goroutine 很大程度上降低了并發(fā)的開發(fā)成本,,是不是我們所有需要并發(fā)的地方直接 go func 就搞定了呢?


Go 通過 Goroutine 的調(diào)度解決了 CPU 利用率的問題,。但遇到其他的瓶頸資源如何處理,?比如帶鎖的共享資源,比如數(shù)據(jù)庫連接等,?;ヂ?lián)網(wǎng)在線應(yīng)用場(chǎng)景下,如果每個(gè)請(qǐng)求都扔到一個(gè) Goroutine 里,,當(dāng)資源出現(xiàn)瓶頸的時(shí)候,,會(huì)導(dǎo)致大量的 Goroutine 阻塞,最后用戶請(qǐng)求超時(shí),。這時(shí)候就需要用 Goroutine 池來進(jìn)行控流,,同時(shí)問題又來了:池子里設(shè)置多少個(gè) Goroutine 合適?


所以這個(gè)問題還是沒有從更本上解決,。


Actor 模型

Actor 對(duì)沒接觸過這個(gè)概念的人可能不太好理解,,Actor 的概念其實(shí)和 OO 里的對(duì)象類似,是一種抽象,。面對(duì)對(duì)象編程對(duì)現(xiàn)實(shí)的抽象是對(duì)象 = 屬性 + 行為(method),,但當(dāng)使用方調(diào)用對(duì)象行為(method)的時(shí)候,其實(shí)占用的是調(diào)用方的 CPU 時(shí)間片,,是否并發(fā)也是由調(diào)用方?jīng)Q定的,。這個(gè)抽象其實(shí)和現(xiàn)實(shí)世界是有差異的。現(xiàn)實(shí)世界更像 Actor 的抽象,,互相都是通過異步消息通信的,。比如你對(duì)一個(gè)美女 say hi,美女是否回應(yīng),,如何回應(yīng)是由美女自己決定的,,運(yùn)行在美女自己的大腦里,并不會(huì)占用發(fā)送者的大腦,。


所以 Actor 有以下特征:


  • Processing – actor 可以做計(jì)算的,,不需要占用調(diào)用方的 CPU 時(shí)間片,并發(fā)策略也是由自己決定,。

  • Storage – actor 可以保存狀態(tài)

  • Communication – actor 之間可以通過發(fā)送消息通訊


Actor 遵循以下規(guī)則:


  • 發(fā)送消息給其他的 Actor

  • 創(chuàng)建其他的 Actor

  • 接受并處理消息,,修改自己的狀態(tài)


Actor 的目標(biāo):


  • Actor 可獨(dú)立更新,實(shí)現(xiàn)熱升級(jí),。因?yàn)?Actor 互相之間沒有直接的耦合,,是相對(duì)獨(dú)立的實(shí)體,可能實(shí)現(xiàn)熱升級(jí),。

  • 無縫彌合本地和遠(yuǎn)程調(diào)用 因?yàn)?Actor 使用基于消息的通訊機(jī)制,,無論是和本地的 Actor,,還是遠(yuǎn)程 Actor 交互,都是通過消息,,這樣就彌合了本地和遠(yuǎn)程的差異,。

  • 容錯(cuò) Actor 之間的通信是異步的,發(fā)送方只管發(fā)送,,不關(guān)心超時(shí)以及錯(cuò)誤,,這些都由框架層和獨(dú)立的錯(cuò)誤處理機(jī)制接管。

  • 易擴(kuò)展,,天然分布式 因?yàn)?Actor 的通信機(jī)制彌合了本地和遠(yuǎn)程調(diào)用,,本地Actor 處理不過來的時(shí)候,可以在遠(yuǎn)程節(jié)點(diǎn)上啟動(dòng) Actor 然后轉(zhuǎn)發(fā)消息過去,。


Actor 的實(shí)現(xiàn):


  • Erlang/OTP Actor 模型的標(biāo)桿,,其他的實(shí)現(xiàn)基本上都一定程度參照了 Erlang 的模式。實(shí)現(xiàn)了熱升級(jí)以及分布式,。

  • Akka(Scala,Java)基于線程和異步回調(diào)模式實(shí)現(xiàn),。由于 Java 中沒有 Fiber,所以是基于線程的,。為了避免線程被阻塞,,Akka 中所有的阻塞操作都需要異步化。要么是 Akka 提供的異步框架,,要么通過 Future-callback 機(jī)制,,轉(zhuǎn)換成回調(diào)模式。實(shí)現(xiàn)了分布式,,但還不支持熱升級(jí),。

  • Quasar (Java) 為了解決 Akka 的阻塞回調(diào)問題,Quasar 通過字節(jié)碼增強(qiáng)的方式,,在 Java 中實(shí)現(xiàn)了 Coroutine/Fiber,。同時(shí)通過 ClassLoader 的機(jī)制實(shí)現(xiàn)了熱升級(jí)。缺點(diǎn)是系統(tǒng)啟動(dòng)的時(shí)候要通過 javaagent 機(jī)制進(jìn)行字節(jié)碼增強(qiáng),。


Golang CSP VS Actor

二者的格言都是:


Don’t communicate by sharing memory, share memory by communicating


通過消息通信的機(jī)制來避免競(jìng)態(tài)條件,,但具體的抽象和實(shí)現(xiàn)上有些差異。


  • CSP 模型里消息和 Channel 是主體,,處理器是匿名的,。

    也就是說發(fā)送方需要關(guān)心自己的消息類型以及應(yīng)該寫到哪個(gè) Channel,但不需要關(guān)心誰消費(fèi)了它,,以及有多少個(gè)消費(fèi)者,。Channel 一般都是類型綁定的,一個(gè) Channel 只寫同一種類型的消息,所以 CSP 需要支持 alt/select 機(jī)制,,同時(shí)監(jiān)聽多個(gè) Channel,。Channel 是同步的模式(Golang 的 Channel 支持 buffer,支持一定數(shù)量的異步),,背后的邏輯是發(fā)送方非常關(guān)心消息是否被處理,,CSP 要保證每個(gè)消息都被正常處理了,沒被處理就阻塞著,。

  • Actor 模型里 Actor 是主體,Mailbox(類似于 CSP 的 Channel)是透明的,。

    也就是說它假定發(fā)送方會(huì)關(guān)心消息發(fā)給誰消費(fèi)了,,但不關(guān)心消息類型以及通道。所以 Mailbox 是異步模式,,發(fā)送者不能假定發(fā)送的消息一定被收到和處理,。Actor 模型必須支持強(qiáng)大的模式匹配機(jī)制,因?yàn)闊o論什么類型的消息都會(huì)通過同一個(gè)通道發(fā)送過來,,需要通過模式匹配機(jī)制做分發(fā),。它背后的邏輯是現(xiàn)實(shí)世界本來就是異步的,不確定(non-deterministic)的,,所以程序也要適應(yīng)面對(duì)不確定的機(jī)制編程,。自從有了并行之后,原來的確定編程思維模式已經(jīng)受到了挑戰(zhàn),,而 Actor 直接在模式中蘊(yùn)含了這點(diǎn),。


從這樣看來,CSP 的模式比較適合 Boss-Worker 模式的任務(wù)分發(fā)機(jī)制,,它的侵入性沒那么強(qiáng),,可以在現(xiàn)有的系統(tǒng)中通過 CSP 解決某個(gè)具體的問題。它并不試圖解決通信的超時(shí)容錯(cuò)問題,,這個(gè)還是需要發(fā)起方進(jìn)行處理,。同時(shí)由于 Channel 是顯式的,雖然可以通過 netchan(原來 Go 提供的 netchan 機(jī)制由于過于復(fù)雜,,被廢棄,,在討論新的 netchan)實(shí)現(xiàn)遠(yuǎn)程 Channel,但很難做到對(duì)使用方透明,。而 Actor 則是一種全新的抽象,,使用 Actor 要面臨整個(gè)應(yīng)用架構(gòu)機(jī)制和思維方式的變更。它試圖要解決的問題要更廣一些,,比如容錯(cuò),,比如分布式。但Actor的問題在于以當(dāng)前的調(diào)度效率,哪怕是用 Goroutine 這樣的機(jī)制,,也很難達(dá)到直接方法調(diào)用的效率,。當(dāng)前要像 OO 的『一切皆對(duì)象』一樣實(shí)現(xiàn)一個(gè)『一切皆 Actor』的語言,效率上肯定有問題,。所以折中的方式是在 OO 的基礎(chǔ)上,,將系統(tǒng)的某個(gè)層面的組件抽象為 Actor。


再扯一下 Rust

Rust 解決并發(fā)問題的思路是首先承認(rèn)現(xiàn)實(shí)世界的資源總是有限的,,想徹底避免資源共享是很難的,,不試圖完全避免資源共享,它認(rèn)為并發(fā)的問題不在于資源共享,,而在于錯(cuò)誤的使用資源共享,。比如我們前面提到的,大多數(shù)語言定義類型的時(shí)候,,并不能限制調(diào)用方如何使用,,只能通過文檔或者標(biāo)記的方式(比如 Java 中的 @ThreadSafe, @NotThreadSafe annotation)說明是否并發(fā)安全,但也只能僅僅做到提示的作用,,不能阻止調(diào)用方誤用,。雖然 Go 提供了 -race 機(jī)制,可以通過運(yùn)行單元測(cè)試的時(shí)候帶上這個(gè)參數(shù)來檢測(cè)競(jìng)態(tài)條件,,但如果你的單元測(cè)試并發(fā)度不夠,,覆蓋面不到也檢測(cè)不出來。所以 Rust 的解決方案就是:


  • 定義類型的時(shí)候要明確指定該類型是否是并發(fā)安全的

  • 引入了變量的所有權(quán)(Ownership)概念 非并發(fā)安全的數(shù)據(jù)結(jié)構(gòu)在多個(gè)線程間轉(zhuǎn)移,,也不一定就會(huì)導(dǎo)致問題,,導(dǎo)致問題的是多個(gè)線程同時(shí)操作,也就是說是因?yàn)檫@個(gè)變量的所有權(quán)不明確導(dǎo)致的,。有了所有權(quán)的概念后,,變量只能由擁有所有權(quán)的作用域代碼操作,而變量傳遞會(huì)導(dǎo)致所有權(quán)變更,,從語言層面限制了競(jìng)態(tài)條件出現(xiàn)的情況,。


有了這機(jī)制,Rust 可以在編譯期而不是運(yùn)行期對(duì)競(jìng)態(tài)條件做檢查和限制,。雖然開發(fā)的時(shí)候增加了心智成本,,但降低了調(diào)用方以及排查并發(fā)問題的心智成本,也是一種有特色的解決方案,。


結(jié)論

革命尚未成功 同志任需努力


本文帶大家一起回顧了并發(fā)的問題,,和各種解決方案。雖然各家有各家的優(yōu)勢(shì)以及使用場(chǎng)景,,但并發(fā)帶來的問題還遠(yuǎn)遠(yuǎn)沒到解決的程度,。所以還需努力,,大家也有機(jī)會(huì)。


最后拋個(gè)磚,,構(gòu)想:在 Goroutine 上實(shí)現(xiàn) Actor,?

  • 分布式 解決了單機(jī)效率問題,是不是可以嘗試解決下分布式效率問題,?

  • 和容器集群融合 當(dāng)前的自動(dòng)伸縮方案基本上都是通過監(jiān)控服務(wù)器或者 LoadBalancer,,設(shè)置一個(gè)閥值來實(shí)現(xiàn)的。類似于我前面提到的喂飯的例子,,是基于經(jīng)驗(yàn)的方案,,但如果系統(tǒng)內(nèi)和外部集群結(jié)合,這個(gè)事情就可以做的更細(xì)致和智能,。

  • 自管理 前面的兩點(diǎn)最終的目標(biāo)都是實(shí)現(xiàn)一個(gè)可以自管理的系統(tǒng),。做過系統(tǒng)運(yùn)維的同學(xué)都知道,我們照顧系統(tǒng)就像照顧孩子一樣,,時(shí)刻要監(jiān)控系統(tǒng)的各種狀態(tài),接受系統(tǒng)的各種報(bào)警,,然后排查問題,,進(jìn)行緊急處理。孩子有長(zhǎng)大的一天,,那能不能讓系統(tǒng)也自己成長(zhǎng),,做到自管理呢?雖然這個(gè)目標(biāo)現(xiàn)在看來還比較遠(yuǎn),,但我覺得是可以期待的,。


想寫好并發(fā)編程的工程師,建議掃碼訂閱王淵命個(gè)人公眾號(hào)「午夜咖啡」,,其定位是「工具 · 架構(gòu) · 成長(zhǎng) · 思考」,,不但有深度的技術(shù)文章,也有對(duì)社會(huì)事件分析及個(gè)人成長(zhǎng)的思考,。



閱讀老王在高可用架構(gòu)發(fā)表的其他文章



點(diǎn)擊閱讀原文可觀看本次演講視頻,、下載 PPT 及進(jìn)一步閱讀本文相關(guān)參考資料鏈接。


- EOF -

(頭圖作者: Sameer Ajmani)


轉(zhuǎn)載本文請(qǐng)注明來自高可用架構(gòu) 「ArchNotes」微信公眾號(hào)及包含以下二維碼,。


    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多