php协程和goroutine,浅谈协程和Go语言的Goroutine
0x00.前言
前面寫了一篇
今天來學(xué)習(xí)Go語言的Goroutine機(jī)制,這也可能是Go語言最為吸引人的特性了,理解它對于掌握Go語言大有裨益,話不多說開始吧!
通過本文你將了解到以下內(nèi)容:什么是協(xié)程以及橫向?qū)Ρ葍?yōu)勢
Go語言的Goroutine機(jī)制底層原理和特點(diǎn)
0x01.聊聊協(xié)程
大家對于進(jìn)程、線程二位明星都很熟悉,但協(xié)程就沒有火了,是協(xié)程不是攜程哦!
協(xié)程并不是Go語言特有的機(jī)制,相反像Lua、Ruby、Python、Kotlin、C/C++等也都有協(xié)程的支持,區(qū)別在于有的是從語言層面支持、有的通過插件類庫支持。Go語言是原生語言層面支持,本文也是從Go角度去理解協(xié)程。
1.1 協(xié)程基本概念和提出者
協(xié)程英文是Coroutine譯為協(xié)同程序,我們來看下維基百科對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.
簡單翻譯一下:協(xié)同程序是一種計(jì)算機(jī)程序組件,它允許暫停和恢復(fù)執(zhí)行,從而可以作為通用化的非搶占式多任務(wù)處理子程序。
協(xié)同程序非常適合實(shí)現(xiàn)例如協(xié)作任務(wù)、異常、事件循環(huán)、迭代器、管道等熟悉的程序組件。
根據(jù)唐納德·克努特的說法,梅爾文·康威在1958年將Coroutine這個(gè)術(shù)語應(yīng)用于裝配程序的構(gòu)建,直到在1963年才首次發(fā)表了闡述Coroutine的論文。
協(xié)程的提出者梅爾文·愛德華·康威是一位計(jì)算機(jī)科學(xué)家,除了協(xié)程之外他還創(chuàng)造了Conway's Law康威定律,他基于社會學(xué)觀察提出了系統(tǒng)設(shè)計(jì)的一些觀點(diǎn),本文就不展開了,感興趣的可以看下作者的論文How Do Committees Invent?:
1.2 協(xié)程和進(jìn)線程的對比
我們來復(fù)習(xí)一下進(jìn)線程和協(xié)程的一些基本特點(diǎn)吧:進(jìn)程是系統(tǒng)資源分配的最小單位, 進(jìn)程包括文本段text region、數(shù)據(jù)段data region和堆棧段stack region等。進(jìn)程的創(chuàng)建和銷毀都是系統(tǒng)資源級別的,因此是一種比較昂貴的操作,進(jìn)程是搶占式調(diào)度其有三個(gè)狀態(tài):等待態(tài)、就緒態(tài)、運(yùn)行態(tài)。進(jìn)程之間是相互隔離的,它們各自擁有自己的系統(tǒng)資源, 更加安全但是也存在進(jìn)程間通信不便的問題。
進(jìn)程是線程的載體容器,多個(gè)線程除了共享進(jìn)程的資源還擁有自己的一少部分獨(dú)立的資源,因此相比進(jìn)程而言更加輕量,進(jìn)程內(nèi)的多個(gè)線程間的通信比進(jìn)程容易,但是也同樣帶來了同步和互斥的問題和線程安全問題,盡管如此多線程編程仍然是當(dāng)前服務(wù)端編程的主流,線程也是CPU調(diào)度的最小單位,多線程運(yùn)行時(shí)就存在線程切換問題,其狀態(tài)轉(zhuǎn)移如圖:
協(xié)程在有的資料中稱為微線程或者用戶態(tài)輕量級線程,協(xié)程調(diào)度不需要內(nèi)核參與而是完全由用戶態(tài)程序來決定,因此協(xié)程對于系統(tǒng)而言是無感知的。協(xié)程由用戶態(tài)控制就不存在搶占式調(diào)度那樣強(qiáng)制的CPU控制權(quán)切換到其他進(jìn)線程,多個(gè)協(xié)程進(jìn)行協(xié)作式調(diào)度,協(xié)程自己主動把控制權(quán)轉(zhuǎn)讓出去之后,其他協(xié)程才能被執(zhí)行到,這樣就避免了系統(tǒng)切換開銷提高了CPU的使用效率。
搶占式調(diào)度和協(xié)作式調(diào)度的簡單對比:
看到這里我們不免去想:看著協(xié)作式調(diào)度優(yōu)點(diǎn)更多,那么為什么一直是搶占式調(diào)度占上風(fēng)呢?讓我們繼續(xù)一起學(xué)習(xí),可能就能解答這個(gè)問題了。
1.3 實(shí)際工作中的我們
我們寫程序的時(shí)經(jīng)常需要考慮的因素就是提高機(jī)器使用率,這個(gè)非常好理解。當(dāng)然機(jī)器使用率和開發(fā)效率維護(hù)成本往往存在權(quán)衡,說句大白話就是:要么費(fèi)人力要么費(fèi)機(jī)器,選一個(gè)吧!
機(jī)器成本里面最貴的就是CPU了,程序一般分為CPU密集型和IO密集型,對于CPU密集型我們的優(yōu)化空間可能沒那么多,但對于IO密集型卻有非常大的優(yōu)化空間,試想我們的程序總是處于IO等待中讓CPU呼呼睡大覺,那該多糟糕。
為了提高IO密集型程序的CPU使用率,我們嘗試多進(jìn)程/多線程編程等讓多個(gè)任務(wù)一起跑分時(shí)復(fù)用搶占式調(diào)度,這樣提高了CPU的利用率,但由于多個(gè)進(jìn)線程存在調(diào)度切換,這也有一定的資源消耗,因此進(jìn)線程數(shù)量不可能無限增大。
我們現(xiàn)在寫的程序大部分都是同步IO的,效率還不夠高,因此出現(xiàn)了一些異步IO框架,但是異步框架的編程難度比同步框架要大,但不可否認(rèn)異步是一個(gè)很好的優(yōu)化方向,先不要暈,來看下同步IO和異步IO就知道了:同步是指應(yīng)用程序發(fā)起I/O請求后需要等待或者輪詢內(nèi)核I/O操作完成后才能繼續(xù)執(zhí)行,異步是指應(yīng)用程序發(fā)起I/O請求后仍繼續(xù)執(zhí)行,當(dāng)內(nèi)核I/O操作完成后會通知應(yīng)用程序或者調(diào)用應(yīng)用程序注冊的回調(diào)函數(shù)。
我們以C/C++開發(fā)的服務(wù)端程序?yàn)槔?#xff0c;Linux的異步IO出現(xiàn)的比較晚,因此像epoll之類的IO復(fù)用技術(shù)仍然有相當(dāng)大的地盤,但是同步IO的效率畢竟不如異步IO,因此當(dāng)前的優(yōu)化方向包括:異步IO框架(像boost.asio框架)和協(xié)程方案(騰訊libco)。
0x02.Go和協(xié)程
我們知道協(xié)程是Coroutine,Go語言在語言層面對協(xié)程進(jìn)行了原生支持并且稱之為Goroutine,這也是Go語言強(qiáng)大并發(fā)能力的重要支撐,Go的CSP并發(fā)模型是通過Goroutine和channel來實(shí)現(xiàn)的,后續(xù)會專門寫一下CSP并發(fā)模型。
2.1 協(xié)作式調(diào)度和調(diào)度器
協(xié)作式調(diào)度中用戶態(tài)協(xié)程會主動讓出CPU控制權(quán)來讓其他協(xié)程使用,確實(shí)提高了CPU的使用率,但是不由得去思考用戶態(tài)協(xié)程不夠智能怎么辦?不知道何時(shí)讓出控制權(quán)也不知道何時(shí)恢復(fù)執(zhí)行。
讀到這里忽然明白了搶占式調(diào)度的優(yōu)勢了,在搶占式調(diào)度中都是由系統(tǒng)內(nèi)核來完成的,用戶態(tài)不需要參與,并且內(nèi)核參與使得平臺移植好,說到底還是各有千秋啊!
為了解決這個(gè)問題我們需要一個(gè)中間層來調(diào)度這些協(xié)程,這樣才能讓用戶態(tài)的成千上萬個(gè)協(xié)程穩(wěn)定有序地跑起來,我們姑且把這個(gè)中間層稱為用戶態(tài)協(xié)程調(diào)度器吧!
2.2 Goroutine和Go的調(diào)度器模型
Go語言從2007年底開發(fā)直到今天已經(jīng)發(fā)展了12年,Go的調(diào)度器也不是一蹴而就的,在最初的幾個(gè)版本中Go的調(diào)度器也非常簡陋,無法支撐大并發(fā)。
經(jīng)過多個(gè)版本的迭代和優(yōu)化,目前已經(jīng)有很優(yōu)異的性能了,不過我們還是來回顧一下Go調(diào)度器的發(fā)展歷程(詳見參考一):
圖片來自網(wǎng)絡(luò)
Go的調(diào)度器非常復(fù)雜,篇幅所限本文只提一些基本的概念和原理,后續(xù)會深入去展開Go的調(diào)度器。
最近幾個(gè)版本的Go調(diào)度器采用GPM模型,其中有幾個(gè)概念先看下:
圖片來自網(wǎng)絡(luò)
GPM模型使用一種M:N的調(diào)度器來調(diào)度任意數(shù)量的協(xié)程運(yùn)行于任意數(shù)量的系統(tǒng)線程中,從而保證了上下文切換的速度并且利用多核,但是增加了調(diào)度器的復(fù)雜度。
來看兩張圖來進(jìn)一步理解一下:
圖片來自網(wǎng)絡(luò)
整個(gè)GPM調(diào)度的簡單過程如下:新創(chuàng)建的Goroutine會先存放在Global全局隊(duì)列中,等待Go調(diào)度器進(jìn)行調(diào)度,隨后Goroutine被分配給其中的一個(gè)邏輯處理器P,并放到這個(gè)邏輯處理器對應(yīng)的Local本地運(yùn)行隊(duì)列中,最終等待被邏輯處理器P執(zhí)行即可。
在M與P綁定后,M會不斷從P的Local隊(duì)列中無鎖地取出G,并切換到G的堆棧執(zhí)行,當(dāng)P的Local隊(duì)列中沒有G時(shí),再從Global隊(duì)列中獲取一個(gè)G,當(dāng)Global隊(duì)列中也沒有待運(yùn)行的G時(shí),則嘗試從其它的P竊取部分G來執(zhí)行相當(dāng)于P之間的負(fù)載均衡。
Goroutine在整個(gè)生存期也存在不同的狀態(tài)切換,主要的有以下幾種狀態(tài):
畫個(gè)狀態(tài)圖看下:
0x03.巨人的肩膀
0x04.往期精彩
0x05.關(guān)于我
有疑問加站長微信聯(lián)系(非本文作者)
總結(jié)
以上是生活随笔為你收集整理的php协程和goroutine,浅谈协程和Go语言的Goroutine的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信用卡突然降低额度没法还款怎么办?可以试
- 下一篇: 新手用信用卡注意:这几个省钱的方法不知道