www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 公眾號精選 > 小林coding
[導(dǎo)讀]前言大家好,我的朋友們!干了這么年后端,寫過C/C、Python、Go,每次說到協(xié)程的時候,腦海里就只能浮現(xiàn)一些關(guān)鍵字yeild、async、go等等。但是對于協(xié)程這個知識點,我理解的一直比較模糊,于是決定搞清楚。全文閱讀預(yù)計耗時10分鐘,少刷幾個小視頻的時間,多學(xué)點知識,想想就...

前言

大家好,我的朋友們!

干了這么年后端,寫過C/C 、Python、Go,每次說到協(xié)程的時候,腦海里就只能浮現(xiàn)一些關(guān)鍵字yeild、async、go等等。

但是對于協(xié)程這個知識點,我理解的一直比較模糊,于是決定搞清楚。

全文閱讀預(yù)計耗時10分鐘,少刷幾個小視頻的時間,多學(xué)點知識,想想就很劃算噻!

協(xié)程概念的誕生

先拋一個粗淺的結(jié)論:協(xié)程從廣義來說是一種設(shè)計理念,我們常說的只是具體的實現(xiàn)。

理解好思想,技術(shù)點就很簡單了,關(guān)于協(xié)程道與術(shù)的區(qū)別:

上古神器COBOL

協(xié)程概念的出現(xiàn)比線程更早,甚至可以追溯到20世紀50年代,提協(xié)程就必須要說到一門生命力極強的最早的高級編程語言COBOL。

最開始我以為COBOL這門語言早就消失在歷史長河中,但是我錯了。

COBOL語言,是一種面向過程的高級程序設(shè)計語言,主要用于數(shù)據(jù)處理,是國際上應(yīng)用最廣泛的一種高級語言。COBOL是英文Common Business-Oriented Language的縮寫,原意是面向商業(yè)的通用語言。

截止到今年在全球范圍內(nèi)大約有1w臺大型機中有3.8w 遺留系統(tǒng)中約2000億行代碼是由COBOL寫的,占比高達65%,同時在美國很多政府和企業(yè)機構(gòu)都是基于COBOL打造的,影響力巨大。

時間拉回1958年,美國計算機科學(xué)家梅爾文·康威(Melvin Conway)就開始鉆研基于磁帶存儲的COBOL的編譯器優(yōu)化問題,這在當時是個非常熱門的話題,不少青年才俊都撲進去了,包括圖靈獎得主唐納德·爾文·克努斯教授(Donald Ervin Knuth)也寫了一個優(yōu)化后的編譯器。

看看這兩位的簡介,我沉默了:

梅爾文·康威(Melvin Conway)也是一位超級大佬,著名的康威定律提出者。

唐納德·爾文·克努斯是算法和程序設(shè)計技術(shù)的先驅(qū)者,1974年的圖靈獎得主,計算機排版系統(tǒng)TeX和字型設(shè)計系統(tǒng)METAFONT的發(fā)明者,他因這些成就和大量創(chuàng)造性的影響深遠的著作而譽滿全球,《計算機程序設(shè)計的藝術(shù)》被《美國科學(xué)家》雜志列為20世紀最重要的12本物理科學(xué)類專著之一。

那究竟是什么問題讓這群天才們投入這么大的精力呢?快來看看!

COBOL編譯器的技術(shù)難題

我們都是知道高級編程語言需要借助編譯器來生成二進制可執(zhí)行文件,編譯器的基本步驟包括:讀取字符流、詞法分析、語法分析、語義分析、代碼生成器、代碼優(yōu)化器等。

這種管道式的流程,上一步的輸出作為下一步的輸入,將中間結(jié)果存儲在內(nèi)存即可,這在現(xiàn)代計算機上毫無壓力,但是受限于軟硬件水平,在幾十年前的COBOL語言卻是很難的。

在1958年的時候,當時的存儲還不發(fā)達,磁帶作為存儲器是1951年在計算機中得到應(yīng)用的,所以那個時代的COBOL很依賴于磁帶。

其實,我在網(wǎng)上找了很多資料去看當時的編譯器有什么問題,只找到了一條:編譯器無法做到讀一次磁帶就可以完成整個編譯過程,也就是所謂的one-pass編譯器還沒有產(chǎn)生。

當時的COBOL程序被寫在一個磁帶上,而磁帶不支持隨機讀寫,只能順序讀,而當時的內(nèi)存又不可能把整個磁帶的內(nèi)容都裝進去,所以一次讀取沒編譯完就要再從頭讀。

于是,我腦補了COBOL編譯器和磁帶之間可能的兩種multi-pass形式的交互情況:

  • 可能情況一
    對于COBOL的編譯器來說,要完成詞法分析、語法分析就要從磁帶上讀取程序的源代碼,在之前的編譯器中詞法分析和語法分析是相互獨立的,這就意味著:

    • 詞法分析時需要將磁帶從頭到尾過一遍
    • 語法分析時需要將磁帶從頭到尾過一遍
  • 可能情況二
    聽過磁帶的朋友們一定知道磁帶的兩個基本操作:倒帶和快進。
    在完成編譯器的詞法分析和語法分析兩件事情時,需要磁帶反復(fù)的倒帶和快進去尋找兩類分析所需的部分,類似于磁盤的尋道,磁頭需要反復(fù)移動橫跳,并且當時的磁帶不一定支持隨機讀寫。

從一些資料可以看到,COBOL當時編譯器各個環(huán)節(jié)相互獨立的,這種軟硬件的綜合限制導(dǎo)致無法實現(xiàn)one-pass編譯。

協(xié)同式解決方案

在梅爾文·康威的編譯器設(shè)計中將詞法分析和語法分析合作運行,而不再像其他編譯器那樣相互獨立,兩個模塊交織運行,編譯器的控制流在詞法分析和語法分析之間來回切換:

  • 當詞法分析模塊基于詞素產(chǎn)生足夠多的詞法單元Token時就控制流轉(zhuǎn)給語法分析
  • 當語法分析模塊處理完所有的詞法單元Token時將控制流轉(zhuǎn)給詞法分析模塊
  • 詞法分析和語法分析各自維護自身的運行狀態(tài),并且具備主動讓出和恢復(fù)的能力
可以看到這個方案的核心思想在于:

梅爾文·康威構(gòu)建的這種協(xié)同工作機制,需要參與者讓出(yield)控制流時,記住自身狀態(tài),以便在控制流返回時能從上次讓出的位置恢復(fù)(resume)執(zhí)行。簡言之,協(xié)程的全部精神就在于控制流的主動讓出和恢復(fù)。

這種協(xié)作式的任務(wù)流和計算機中斷非常像,在當時條件的限制下,由梅爾文·康威提出的這種讓出/恢復(fù)模式的協(xié)作程序被認為是最早的協(xié)程概念,并且基于這種思想可以打造新的COBOL編譯器。

在1963年,梅爾文·康威也發(fā)表了一篇論文來說明自己的這種思想,雖然半個多世紀過去了,有幸我還是找到了這篇論文:

https://melconway.com/Home/pdf/compiler.pdf

說實話這paper真是有點難,時間過于久遠,很難有共鳴,最后我放棄了,要不然我或許能搞明白之前編譯器的具體問題了。

懷才不遇的協(xié)程

雖然協(xié)程概念出現(xiàn)的時間比線程還要早,但是協(xié)程一直都沒有正是登上舞臺,真是有點懷才不遇的趕腳。

我們上學(xué)的時候,老師就講過一些軟件設(shè)計思想,其中主流語言崇尚自頂向下top-down的編程思想:

對要完成的任務(wù)進行分解,先對最高層次中的問題進行定義、設(shè)計、編程和測試,而將其中未解決的問題作為一個子任務(wù)放到下一層次中去解決。

這樣逐層、逐個地進行定義、設(shè)計、編程和測試,直到所有層次上的問題均由實用程序來解決,就能設(shè)計出具有層次結(jié)構(gòu)的程序。

C語言就是典型的top-down思想的代表,在main函數(shù)作為入口,各個模塊依次形成層次化的調(diào)用關(guān)系,同時各個模塊還有下級的子模塊,同樣有層次調(diào)用關(guān)系。

但是協(xié)程這種相互協(xié)作調(diào)度的思想和top-down是不合的,在協(xié)程中各個模塊之間存在很大的耦合關(guān)系,并不符合高內(nèi)聚低耦合的編程思想,相比之下top-down使程序結(jié)構(gòu)清晰、層次調(diào)度明確,代碼可讀性和維護性都很不錯。

與線程相比,協(xié)作式任務(wù)系統(tǒng)讓調(diào)用者自己來決定什么時候讓出,比操作系統(tǒng)的搶占式調(diào)度所需要的時間代價要小很多,后者為了能恢復(fù)現(xiàn)場會在切換線程時保存相當多的狀態(tài),并且會非常頻繁地進行切換,資源消耗更大。

綜合來說,協(xié)程完全是用戶態(tài)的行為,由程序員自己決定什么時候讓出控制權(quán),保存現(xiàn)場和切換恢復(fù)使用的資源也非常少,同時對提高處理器效率來說也是完全符合的。

那么不禁要問:協(xié)程看著不錯,為啥沒成為主流呢?

  • 協(xié)程的思想和當時的主流不符合
  • 搶占式的線程可以解決大部分的問題,讓使用者感受的痛點不足
換句話說:協(xié)程能干的線程干得也不錯,線程干的不好的地方,使用者暫時也不太需要,所以協(xié)程就這樣懷才不遇了。

其實,協(xié)程雖然在x86架構(gòu)上沒有折騰出大風(fēng)浪,由于搶占式任務(wù)系統(tǒng)依賴于CPU硬件的支持,對硬件要求比較高,對于一些嵌入式設(shè)備來說,協(xié)同調(diào)度再合適不過了,所以協(xié)程在另外一個領(lǐng)域也施展了拳腳。

協(xié)程的雄起

我們對于CPU的壓榨從未停止。

對于CPU來說,任務(wù)分為兩大類:計算密集型和IO密集型。

計算密集型已經(jīng)可以最大程度發(fā)揮CPU的作用,但是IO密集型一直是提高CPU利用率的難點。

IO密集型任務(wù)之痛

對于IO密集型任務(wù),在搶占式調(diào)度中也有對應(yīng)的解決方案:異步 回調(diào)。

也就是遇到IO阻塞,比如下載圖片時會立即返回,等待下載完成將結(jié)果進行回調(diào)處理,交付給發(fā)起者。

就像你常去早餐店,油條還沒好,你和老板很熟悉就先交了錢去座位玩手機了,等你的油條好了,服務(wù)員就端過去了,這就是典型的異步 回調(diào)。

雖然異步 回調(diào)在現(xiàn)實生活中看著也很簡單,但是在程序設(shè)計上卻很讓人頭痛,在某些場景下會讓整個程序的可讀性非常差,而且也不好寫,相反同步IO雖然效率低,但是很好寫,

還是以為異步圖片下載為例,圖片服務(wù)中臺提供了異步接口,發(fā)起者請求之后立即返回,圖片服務(wù)此時給了發(fā)起者一個唯一標識ID,等圖片服務(wù)完成下載后把結(jié)果放到一個消息隊列,此時需要發(fā)起者不斷消費這個MQ才能拿到下載結(jié)果。

整個過程相比同步IO來說,原來整體的邏輯被拆分為好幾個部分,各個子部分有狀態(tài)的遷移,對大部分程序員來說維護狀態(tài)簡直就是噩夢,日后必然是bug的高發(fā)地。

用戶態(tài)協(xié)同調(diào)度

隨著網(wǎng)絡(luò)技術(shù)的發(fā)展和高并發(fā)要求,對于搶占式調(diào)度對IO型任務(wù)處理的低效逐漸受到重視,終于協(xié)程的機會來了。

協(xié)程將IO的處理權(quán)交給了程序員,遇到IO被阻塞時就交出控制權(quán)給其他協(xié)程,等其他協(xié)程處理完再把控制權(quán)交回來。

通過yield方式轉(zhuǎn)移執(zhí)行權(quán)的多個協(xié)程之間并非調(diào)用者和被調(diào)用者的關(guān)系,而是彼此平等、對稱、合作的關(guān)系。

協(xié)程一直沒有占上風(fēng)的原因,除了設(shè)計思想的矛盾,還有一些其他原因,畢竟協(xié)程也不是銀彈,來看看協(xié)程有什么問題:

  • 協(xié)程無法利用多核,需要配合進程來使用才可以在多CPU上發(fā)揮作用
  • 線程的回調(diào)機制仍然有巨大生命力,協(xié)程無法全部替代
  • 控制權(quán)需要轉(zhuǎn)移可能造成某些協(xié)程的饑餓,搶占式更加公平
  • 協(xié)程的控制權(quán)由用戶態(tài)決定可能轉(zhuǎn)移給某些惡意的代碼,搶占式由操作系統(tǒng)來調(diào)度更加安全
綜上來說,協(xié)程和線程并非矛盾,協(xié)程的威力在于IO的處理,恰好這部分是線程的軟肋,由對立轉(zhuǎn)換為合作才能開辟新局面。

擁抱協(xié)程的編程語言

網(wǎng)絡(luò)操作、文件操作、數(shù)據(jù)庫操作、消息隊列操作等重IO操作,是任何高級編程語言無法避開的問題,也是提高程序效率的關(guān)鍵。

像Java、C/C 、Python這些老牌語言也陸續(xù)開始借助于第三方包來支持協(xié)程,來解決自身語言的不足。

像Golang這種新生選手,在語言層面原生支持了協(xié)程,可以說是徹底擁抱協(xié)程,這也造就了Go的高并發(fā)能力。

我們來分別看看它們是怎么實現(xiàn)協(xié)程的,以及實現(xiàn)協(xié)程的關(guān)鍵點是什么。

Python

Python對協(xié)程的支持也經(jīng)歷了多個版本,從部分支持到完善支持一直在演進:

  • Python2.x對協(xié)程的支持比較有限,生成器yield實現(xiàn)了一部分但不完全
  • 第三方庫gevent對協(xié)程的實現(xiàn)有比較好,但不是官方的
  • Python3.4加入了asyncio模塊
  • 在Python3.5中又提供了async/await語法層面的支持
  • Python3.6中asyncio模塊更加完善和穩(wěn)
  • Python3.7開始async/await成為保留關(guān)鍵字
我們以最新的async/await來說明Python的協(xié)程是如何使用的:

import?asyncio
from?pathlib?import?Path
import?logging
from?urllib.request?import?urlopen,?Request
import?os
from?time?import?time
import?aiohttp
?
logging.basicConfig(level=logging.INFO,?format='%(asctime)s?-?%(name)s?-?%(levelname)s?-?%(message)s')
logger?=?logging.getLogger(__name__)
?
?
CODEFLEX_IMAGES_URLS?=?['https://codeflex.co/wp-content/uploads/2021/01/pandas-dataframe-python-1024x512.png',
????????????????????????'https://codeflex.co/wp-content/uploads/2021/02/github-actions-deployment-to-eks-with-kustomize-1024x536.jpg',
????????????????????????'https://codeflex.co/wp-content/uploads/2021/02/boto3-s3-multipart-upload-1024x536.jpg',
????????????????????????'https://codeflex.co/wp-content/uploads/2018/02/kafka-cluster-architecture.jpg',
????????????????????????'https://codeflex.co/wp-content/uploads/2016/09/redis-cluster-topology.png']
?
?
async?def?download_image_async(session,?dir,?img_url):
????download_path?=?dir?/?os.path.basename(img_url)
????async?with?session.get(img_url)?as?response:
????????with?download_path.open('wb')?as?f:
????????????while?True:
????????????????chunk?=?await?response.content.read(512)
????????????????if?not?chunk:
????????????????????break
????????????????f.write(chunk)
????logger.info('Downloaded:?'? ?img_url)
?
?
async?def?main():
????images_dir?=?Path("codeflex_images")
????Path("codeflex_images").mkdir(parents=False,?exist_ok=True)
?
????async?with?aiohttp.ClientSession()?as?session:
????????tasks?=?[(download_image_async(session,?images_dir,?img_url))?for?img_url?in?CODEFLEX_IMAGES_URLS]
????????await?asyncio.gather(*tasks,?return_exceptions=True)
?
?
if?__name__?==?'__main__':
????start?=?time()
?????
????event_loop?=?asyncio.get_event_loop()
????try:
????????event_loop.run_until_complete(main())
????finally:
????????event_loop.close()
?
????logger.info('Download?time:?%s?seconds',?time()?-?start)
這段代碼展示了如何使用async/await來實現(xiàn)圖片的并發(fā)下載功能。

  • 在普通的函數(shù)def前面加async關(guān)鍵字就變成異步/協(xié)程函數(shù),調(diào)用該函數(shù)并不會運行,而是返回一個協(xié)程對象,后續(xù)在event_loop中執(zhí)行
  • await表示等待task執(zhí)行完成,也就是yeild讓出控制權(quán),同時asyncio使用事件循環(huán)event_loop來實現(xiàn)整個過程,await需要在async標注的函數(shù)中使用
  • event_loop事件循環(huán)充當管理者的角色,將控制權(quán)在幾個協(xié)程函數(shù)之間切換

C

在C 20引入?yún)f(xié)程框架,但是很不成熟,換句話說是給寫協(xié)程庫的大佬用的最底層的東西,用起來就很復(fù)雜門檻比較高。

C 作為高性能服務(wù)器開發(fā)語言的無冕之王,各大公司也做了很多嘗試來使用協(xié)程功能,比如boost.coroutine、微信的libco、libgo、云風(fēng)用C實現(xiàn)的協(xié)程庫等。

說實話,C 協(xié)程相關(guān)的東西有點復(fù)雜,后面專門寫一下,在此不展開了。

Go

go中的協(xié)程被稱為goroutine,被認為是用戶態(tài)更輕量級的線程,協(xié)程對操作系統(tǒng)而言是透明的,也就是操作系統(tǒng)無法直接調(diào)度協(xié)程,因此必須有個中間層來接管goroutine。

goroutine仍然是基于線程來實現(xiàn)的,因為線程才是CPU調(diào)度的基本單位,在go語言內(nèi)部維護了一組數(shù)據(jù)結(jié)構(gòu)和N個線程,協(xié)程的代碼被放進隊列中來由線程來實現(xiàn)調(diào)度執(zhí)行,這就是著名的GMP模型。

  • G:Goroutine
每個Gotoutine對應(yīng)一個G結(jié)構(gòu)體,G存儲Goroutine的運行堆棧,狀態(tài),以及任務(wù)函數(shù),可重用函數(shù)實體G需要保存到P的隊列或者全局隊列才能被調(diào)度執(zhí)行。

  • M:machine
M是線程的抽象,代表真正執(zhí)行計算的資源,在綁定有效的P后,進入調(diào)度執(zhí)行循環(huán),M會從P的本地隊列來執(zhí)行,

  • P:Processor
P是一個抽象的概念,不是物理上的CPU而是表示邏輯處理器。當一個P有任務(wù),需要創(chuàng)建或者喚醒一個系統(tǒng)線程M去處理它隊列中的任務(wù)。

P決定同時執(zhí)行的任務(wù)的數(shù)量,GOMAXPROCS限制系統(tǒng)線程執(zhí)行用戶層面的任務(wù)的數(shù)量。

對M來說,P提供了相關(guān)的執(zhí)行環(huán)境,入內(nèi)存分配狀態(tài),任務(wù)隊列等。

GMP模型運行的基本過程:

  • 首先創(chuàng)建一個G對象,然后G被保存在P的本地隊列或者全局隊列
  • 這時P會喚醒一個M,M尋找一個空閑的P將G移動到它自己,然后M執(zhí)行一個調(diào)度循環(huán):調(diào)用G對象->執(zhí)行->清理線程->繼續(xù)尋找Goroutine。
  • 在M的執(zhí)行過程中,上下文切換隨時發(fā)生。當切換發(fā)生,任務(wù)的執(zhí)行現(xiàn)場需要被保護,這樣在下一次調(diào)度執(zhí)行可以進行現(xiàn)場恢復(fù)。
  • M的棧保存在G對象,只有現(xiàn)場恢復(fù)需要的寄存器(SP,PC等),需要被保存到G對象。

總結(jié)

本文通過1960年對COBOL語言編譯器的one-pass問題的介紹,讓大家看到了協(xié)同式程序的最早背景以及主動讓出/恢復(fù)的重要理念。

緊接著介紹了主流的自頂向下的軟件設(shè)計思想和協(xié)程思想的矛盾所在,并且搶占式程序調(diào)度的蓬勃發(fā)展,以及存在的問題。

繼續(xù)介紹了關(guān)于IO密集型任務(wù)對于提升CPU效率的阻礙,搶占式調(diào)度對于IO密集型問題的異步 回調(diào)的解決方案,以及協(xié)程的處理,展示了協(xié)程在IO密集型任務(wù)上處理的重大優(yōu)勢。

最后說明了當前搶占式調(diào)度 協(xié)程IO密集型處理的方案,包括Python、C 和go的語言層面對于協(xié)程的支持和實現(xiàn)。

本文特別具體的內(nèi)容并不多,旨在介紹協(xié)程思想及其優(yōu)勢所在,對于各個語言的協(xié)程實現(xiàn)細節(jié)并未展開。

最后依然是感謝大家的耐心閱讀,我們下期見!


本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉
關(guān)閉