Go語言潛力有目共睹,但它的Goroutine機制底層原理你了解嗎?

2020-12-21 CSDN

來源 | 後端技術指南針(ID:gh_ed1e2b37dcb6)

Go語言的巨大潛力有目共睹,今天我們來學習Go語言的Goroutine機制,這也可能是Go語言最為吸引人的特性了,理解它對於掌握Go語言大有裨益,話不多說開始吧!

通過本文你將了解到以下內容:

什麼是協程以及橫向對比優勢Go語言的Goroutine機制底層原理和特點

聊聊協程

大家對於進程、線程二位明星都很熟悉,但協程就沒有火了,是協程不是攜程哦!

協程並不是Go語言特有的機制,相反像Lua、Ruby、Python、Kotlin、C/C++等也都有協程的支持,區別在於有的是從語言層面支持、有的通過插件類庫支持。Go語言是原生語言層面支持,本文也是從Go角度去理解協程。

1.1 協程基本概念和提出者

協程英文是Coroutine譯為協同程序,我們來看下維基百科對Coroutine的介紹:

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.

Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes.

According to Donald Knuth, Melvin Conway coined the term coroutine in 1958 when he applied it to construction of an assembly program.The first published explanation of the coroutine appeared later, in 1963.

簡單翻譯一下:

協同程序是一種電腦程式組件,它允許暫停和恢復執行,從而可以作為通用化的非搶佔式多任務處理子程序。

協同程序非常適合實現例如協作任務、異常、事件循環、迭代器、管道等熟悉的程序組件。

根據唐納德·克努特的說法,梅爾文·康威在1958年將Coroutine這個術語應用於裝配程序的構建,直到在1963年才首次發表了闡述Coroutine的論文。

協程的提出者梅爾文·愛德華·康威是一位計算機科學家,除了協程之外他還創造了Conway's Law康威定律,他基於社會學觀察提出了系統設計的一些觀點,本文就不展開了,感興趣的可以看下作者的論文How Do Committees Invent?:

http://www.melconway.com/Home/Committees_Paper.html

1.2 協程和進線程的對比

我們來複習一下進線程和協程的一些基本特點吧:

進程是系統資源分配的最小單位, 進程包括文本段text region、數據段data region和堆棧段stack region等。進程的創建和銷毀都是系統資源級別的,因此是一種比較昂貴的操作,進程是搶佔式調度其有三個狀態:等待態、就緒態、運行態。進程之間是相互隔離的,它們各自擁有自己的系統資源, 更加安全但是也存在進程間通信不便的問題。

進程是線程的載體容器,多個線程除了共享進程的資源還擁有自己的一少部分獨立的資源,因此相比進程而言更加輕量,進程內的多個線程間的通信比進程容易,但是也同樣帶來了同步和互斥的問題和線程安全問題,儘管如此多線程編程仍然是當前服務端編程的主流,線程也是CPU調度的最小單位,多線程運行時就存在線程切換問題,其狀態轉移如圖:

協程在有的資料中稱為微線程或者用戶態輕量級線程,協程調度不需要內核參與而是完全由用戶態程序來決定,因此協程對於系統而言是無感知的。協程由用戶態控制就不存在搶佔式調度那樣強制的CPU控制權切換到其他進線程,多個協程進行協作式調度,協程自己主動把控制權轉讓出去之後,其他協程才能被執行到,這樣就避免了系統切換開銷提高了CPU的使用效率。

搶佔式調度和協作式調度的簡單對比:

看到這裡我們不免去想:看著協作式調度優點更多,那麼為什麼一直是搶佔式調度佔上風呢?讓我們繼續一起學習,可能就能解答這個問題了。

1.3 實際工作中的我們

我們寫程序的時經常需要考慮的因素就是提高機器使用率,這個非常好理解。當然機器使用率和開發效率維護成本往往存在權衡,說句大白話就是:要麼費人力要麼費機器,選一個吧!

機器成本裡面最貴的就是CPU了,程序一般分為CPU密集型和IO密集型,對於CPU密集型我們的優化空間可能沒那麼多,但對於IO密集型卻有非常大的優化空間,試想我們的程序總是處於IO等待中讓CPU呼呼睡大覺,那該多糟糕。

為了提高IO密集型程序的CPU使用率,我們嘗試多進程/多線程編程等讓多個任務一起跑分時復用搶佔式調度,這樣提高了CPU的利用率,但由於多個進線程存在調度切換,這也有一定的資源消耗,因此進線程數量不可能無限增大。

我們現在寫的程序大部分都是同步IO的,效率還不夠高,因此出現了一些異步IO框架,但是異步框架的編程難度比同步框架要大,但不可否認異步是一個很好的優化方向,先不要暈,來看下同步IO和異步IO就知道了:

同步是指應用程式發起I/O請求後需要等待或者輪詢內核I/O操作完成後才能繼續執行,異步是指應用程式發起I/O請求後仍繼續執行,當內核I/O操作完成後會通知應用程式或者調用應用程式註冊的回調函數。

我們以C/C++開發的服務端程序為例,Linux的異步IO出現的比較晚,因此像epoll之類的IO復用技術仍然有相當大的地盤,但是同步IO的效率畢竟不如異步IO,因此當前的優化方向包括:異步IO框架(像boost.asio框架)和協程方案(騰訊libco)。

Go和協程

我們知道協程是Coroutine,Go語言在語言層面對協程進行了原生支持並且稱之為Goroutine,這也是Go語言強大並發能力的重要支撐,Go的CSP並發模型是通過Goroutine和channel來實現的,後續會專門寫一下CSP並發模型。

2.1 協作式調度和調度器

協作式調度中用戶態協程會主動讓出CPU控制權來讓其他協程使用,確實提高了CPU的使用率,但是不由得去思考用戶態協程不夠智能怎麼辦?不知道何時讓出控制權也不知道何時恢復執行。

讀到這裡忽然明白了搶佔式調度的優勢了,在搶佔式調度中都是由系統內核來完成的,用戶態不需要參與,並且內核參與使得平臺移植好,說到底還是各有千秋啊!

為了解決這個問題我們需要一個中間層來調度這些協程,這樣才能讓用戶態的成千上萬個協程穩定有序地跑起來,我們姑且把這個中間層稱為用戶態協程調度器吧!

2.2 Goroutine和Go的調度器模型

Go語言從2007年底開發直到今天已經發展了12年,Go的調度器也不是一蹴而就的,在最初的幾個版本中Go的調度器也非常簡陋,無法支撐大並發。

經過多個版本的迭代和優化,目前已經有很優異的性能了,不過我們還是來回顧一下Go調度器的發展歷程(詳見參考一):

Go的調度器非常複雜,篇幅所限本文只提一些基本的概念和原理,後續會深入去展開Go的調度器。

最近幾個版本的Go調度器採用GPM模型,其中有幾個概念先看下:

GPM模型使用一種M:N的調度器來調度任意數量的協程運行於任意數量的系統線程中,從而保證了上下文切換的速度並且利用多核,但是增加了調度器的複雜度。

來看兩張圖來進一步理解一下:

整個GPM調度的簡單過程如下:

新創建的Goroutine會先存放在Global全局隊列中,等待Go調度器進行調度,隨後Goroutine被分配給其中的一個邏輯處理器P,並放到這個邏輯處理器對應的Local本地運行隊列中,最終等待被邏輯處理器P執行即可。

在M與P綁定後,M會不斷從P的Local隊列中無鎖地取出G,並切換到G的堆棧執行,當P的Local隊列中沒有G時,再從Global隊列中獲取一個G,當Global隊列中也沒有待運行的G時,則嘗試從其它的P竊取部分G來執行相當於P之間的負載均衡。

Goroutine在整個生存期也存在不同的狀態切換,主要的有以下幾種狀態:

畫個狀態圖看下:

巨人的肩膀

https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-goroutine/https://www.flysnow.org/2017/04/11/go-in-action-go-goroutine.htmlhttps://segmentfault.com/a/1190000018150987https://tiancaiamao.gitbooks.io/go-internals/content/zh/05.2.htmlhttps://wudaijun.com/2018/01/go-scheduler/https://zhuanlan.zhihu.com/p/77620605【End】

相關焦點

  • 一篇文章帶你入門Go語言基礎之並發
    引言Go語言,專門為並發而生的語言,每啟動一個微線程創建一個代價大概2KB起步假設一個內存條大小4G,一個微線程2kb,1G=1024M=1048576kb,1048576/2=524288,五十多萬個但是你知道像Java,Python等語言,一個線程代價多大嗎???
  • 經驗之談:學習Go語言的利與弊
    在這個競爭越來越烈的社會,掌握一門新語言或新技能,意味著你能比別人多一個機會。但萬事開頭難,學習新東西亦如此。如果開發員想學一門新的程式語言,該選擇什麼呢?
  • 為什麼golang語言會變得越來越流行
    作為一個開發者,如果你細心觀察,就會發現越來越多的公司開始使用go語言進行業務的開發。從知乎到b站,很多公司都把業務全面轉向了go語言。那麼為什麼這麼多公司選擇了go語言,為什麼這麼多開發者選擇了go語言,golang變得越來越流行的原因到底是什麼?
  • Go 通道是糟糕的,你應該也覺得很糟糕 | Linux 中國
    它很快就成為了 Space Monkey 開發的核心部分。目前,我們在 Space Monkey 的生產系統有超過 42.5 萬行的純 Go 代碼(不 包括我們所有的 vendored 庫中的代碼量,這將使它接近 150 萬行),所以也並不是你見過的最多的 Go 代碼,但是對於相對年輕的語言,我們是重度用戶。我們之前 。
  • 來複習一波,HashMap底層實現原理解析
    前言HashMa是Java中最常用的集合類框架,也是Java語言中非常典型的數據結構,同時也是我們需要掌握的數據結構,更重要的是進大廠面試必問之一。第二步它的底層會調用K的hashCode()方法得出hash值。第三步通過哈希表函數/哈希算法,將hash值轉換成數組的下標,下標位置上如果沒有任何元素,就把Node添加到這個位置上。如果說下標對應的位置上有鍊表。
  • 為什麼很多公司轉型 Go 語言開發?Go 語言能做什麼
    微服務代表項目:go-kit、micro、monzo bank的typhon、bilibili等。網際網路基礎設施代表項目:以太坊、hyperledger等。用這些標尺來量一下 Go 語言,我們可以清楚地看到:Go 語言容易上手;Go 語言解決了並發編程和寫底層應用開發效率的痛點;Go 語言有 Google 這個世界一流的技術公司在後面;
  • Go 經典入門系列 22:Channel
    如果你看不懂,建議你閱讀上一教程 Go 協程[5]。我們接下來使用信道來重寫上面代碼。該信道作為參數傳遞給了協程 hello,hello 列印出 Hello world goroutine,接下來向 done 寫入數據。當完成寫入時,Go 主協程會通過信道 done 接收數據,於是它解除阻塞狀態,列印出文本 main function。
  • 女朋友問我:小松子,你知道Go語言參數傳遞是傳值還是傳引用嗎?
    今天女朋友問我,小松子,你知道Go語言參數傳遞是傳值還是傳引用嗎?哎呀哈,我竟然被瞧不起了,我立馬一頓操作,給他講的明明白白的,小丫頭片子,還是太嫩,大家且聽我細細道來~~~。文末留了一道思考題,請留下你的答案!!!實參與形參數我們使用go定義方法時是可以定義參數的。
  • Rust能取代C語言嗎?
    Rust是目前開發者圈子裡最火的詞之一,今天帶大家來簡單了解一下什麼是Rust語言、Rust語言的特點、Rust語言在編程界中的地位以及大家常討論的問題——Rust語言能取代C語言嗎?不了解該怎麼看API文檔,怎麼用第三方文檔,迷茫了,就會轉而放棄Rust。當看完C,轉頭來看Rust,用C的思維去查閱文檔,便有了思路,知道和了解怎麼用Rust開發如Firefox般優秀的跨平臺桌面應用。Rust能取代C語言嗎?
  • 你已經是個成熟的985大學了,請不要在大一教 C 語言!
    所以,以下所有討論皆不涉及語言優劣,一切論述以怎樣才是有利於學好 Computer Science 為原則(求生欲滿滿大學教育,尤其是 985、211 這種國內最頂尖的一批高校,應該注重通識教育而不是專項教育,在專業上更要注重基礎、底層、偏向原理。
  • 適合Go 新手學習的開源項目——在 GitHub 學編程
    正如 Go 語言之父的 Rob Pike 所說的 Less is more or less is less,本文也本著這一原則:大道至簡。下面的每一個 Go 語言開源項目都能獨當一面,成為新手的學習項目而且合在一起可以為你提供更為全面的 Go 認知——1 + 1 > 2 。
  • web開發我更喜歡使用GO語言
    go語言可能是Google開發的程式語言,迅速受到開發的關注並願意使用它,在2016年被TIOBE 選為「TIOBE 年最佳程式語言」,可想而知go能被開發者認可一定有它獨特的優勢,而我更喜歡使用GO語言web開發。
  • 我被迫選擇了Go語言,但是現在發現它真的「很香」
    我用兩天的時間熟悉完了這個Go項目,在熟悉項目的過程中我當然也對它的語法有了一定的了解。然後我就開始接受TL給我的新需求並開始編碼工作。我「照貓畫虎」式的完成了一個個的新需求,在編寫代碼的時候,我會去查資料,例如:Go中的strconv中的各種方法,凡是你在Java中關於String操作的方法都能在Go語言找到,如果找不到,那就再找一遍,如果還是找不到,那就自己實現一個。通過一邊編寫Go語言代碼一邊查相關資料,我很快對這門語言了解的更深入了,我知道它天生就是為分布式高並發系統設計的語言。
  • 七牛雲姜智:Go+ 演進之路
    如果說在雲計算的時代裡,沒有人可以錯過 Go 語言;那麼在 Go 語言的領域裡,沒有人可以錯過 Gopher China Con。近日,Gopher China 2020 大會在上海盛大開幕!七牛雲作為中國 Go 語言第一個吃螃蟹的企業,CEO 許式偉是國內 Go 語言大中華區首席布道師,他在今年面向全球發布了為數據而生的 Go+ 。
  • Go 語言之 defer 的前世今生 - CSDN
    作者 | 歐長坤來源 | 碼農桃花源延遲語句 defer 在最早期的 Go 語言設計中並不存在,後來才單獨增加了這一特性,由 Robert Griesemer 完成語言規範的編寫 [Griesemer, 2009], 並由 Ken Thompson 完成最早期的實現 [Thompson, 2009],兩人合作完成這一語言特性。
  • 我為什麼放棄了 Python ,選擇了 Go?
    你知道我們一共有多少種程式語言嗎?根據維基百科數據統計( https://en.wikipedia.org/wiki/List_of_programming_languages ),一共有 600 餘種不同的程式語言。對於五花八門的開發語言,每門語言都有自己的優缺點。而勵志成為一名博學多廣的開發者,術業也有專攻,怎奈何也不會學習完百餘種語言。
  • 程式設計師:5年php開發,發展有限準備轉go,網友:你還會轉回來的!
    這名程式設計師網友目前坐標深圳,已經從事5年php開發工作,最近他抽空研究了一下網站招聘信息,感覺php的後期發展還是很有限,因此他決定轉go(也可以和php結合),他認為go是靜態語言,而python與php都是動態語言,他準備學習一個不一樣類型的語言,這是他的初步想法,那麼怎麼走出第一步,怎麼開始比較好呢?針對他的這種情況,讓我們看看網友們都是怎麼說的吧!
  • Go語言開發環境:Goland安裝
    一、下載安裝開發環境golandgo語言sdk(go的標準庫)安裝sdk的時候,注意安裝的位置,後面要用到,我這裡安裝到了C盤,目錄為: C:\Go:在go裡面,有兩個目錄非常重要,要事先配置:這兩個目錄不需要在一起。
  • 學好C語言的7個步驟,你都了解嗎?
    C語言是如今非常熱門的程式語言,許多人都想學習它,但是,一開始往往無從下手,今天,小編就給大家介紹學好7語言的7個步驟,幫助你明白應該如何學習它。學習C語言之初,遇到的問題都很簡單。但是,隨著要處理的情況越來越複雜,需要決策和考慮的方面也越來越多。通常,選擇一個合適的方式表示信息可以更容易地設計程序和處理數據。三、編寫代碼設計好程序後,就可以著手編寫代碼了。這一步就是把你設計的程序翻譯成C語言。