溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

程序員應(yīng)怎么理解高并發(fā)中的協(xié)程

發(fā)布時(shí)間:2021-10-23 14:43:15 來(lái)源:億速云 閱讀:147 作者:iii 欄目:開(kāi)發(fā)技術(shù)

本篇內(nèi)容主要講解“程序員應(yīng)怎么理解高并發(fā)中的協(xié)程”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“程序員應(yīng)怎么理解高并發(fā)中的協(xié)程”吧!

話不多說(shuō),今天的主題就是作為程序員,你應(yīng)該如何徹底理解協(xié)程。

普通的函數(shù)

我們先來(lái)看一個(gè)普通的函數(shù),這個(gè)函數(shù)非常簡(jiǎn)單:

def func():    print("a")    print("b")    print("c")

這是一個(gè)簡(jiǎn)單的普通函數(shù),當(dāng)我們調(diào)用這個(gè)函數(shù)時(shí)會(huì)發(fā)生什么?

調(diào)用func

func開(kāi)始執(zhí)行,直到return

func執(zhí)行完成,返回函數(shù)A

是不是很簡(jiǎn)單,函數(shù)func執(zhí)行直到返回,并打印出:

a b c

So easy,有沒(méi)有,有沒(méi)有!

很好!

注意這段代碼是用python寫(xiě)的,但本篇關(guān)于協(xié)程的討論適用于任何一門(mén)語(yǔ)言,因?yàn)閰f(xié)程并不是一種語(yǔ)言的特性。而我們只不過(guò)恰好使用了python來(lái)用作示例,因其足夠簡(jiǎn)單。

那么協(xié)程是什么呢?

從普通函數(shù)到協(xié)程

接下來(lái),我們就要從普通函數(shù)過(guò)渡到協(xié)程了。

和普通函數(shù)只有一個(gè)返回點(diǎn)不同,協(xié)程可以有多個(gè)返回點(diǎn)。

這是什么意思呢?

void func() {   print("a")   暫停并返回   print("b")   暫停并返回   print("c") }

普通函數(shù)下,只有當(dāng)執(zhí)行完print("c")這句話后函數(shù)才會(huì)返回,但是在協(xié)程下當(dāng)執(zhí)行完print("a")后func就會(huì)因“暫停并返回”這段代碼返回到調(diào)用函數(shù)。

有的同學(xué)可能會(huì)一臉懵逼,這有什么神奇的嗎?我寫(xiě)一個(gè)return也能返回,就像這樣:

void func() {   print("a")   return   print("b")   暫停并返回   print("c") }

直接寫(xiě)一個(gè)return語(yǔ)句確實(shí)也能返回,但這樣寫(xiě)的話return后面的代碼都不會(huì)被執(zhí)行到了。

協(xié)程之所以神奇就神奇在當(dāng)我們從協(xié)程返回后還能繼續(xù)調(diào)用該協(xié)程,并且是從該協(xié)程的上一個(gè)返回點(diǎn)后繼續(xù)執(zhí)行。

這足夠神奇吧,就好比孫悟空說(shuō)一聲“定”,函數(shù)就被暫停了:

void func() {   print("a")   定   print("b")   定   print("c") }

這時(shí)我們就可以返回到調(diào)用函數(shù),當(dāng)調(diào)用函數(shù)什么時(shí)候想起該協(xié)程后可以再次調(diào)用該協(xié)程,該協(xié)程會(huì)從上一個(gè)返回點(diǎn)繼續(xù)執(zhí)行。

Amazing,有沒(méi)有,集中注意力,千萬(wàn)不要翻車(chē)。

只不過(guò)孫大圣使用的口訣“定”字,在編程語(yǔ)言中一般叫做yield(其它語(yǔ)言中可能會(huì)有不同的實(shí)現(xiàn),但本質(zhì)都是一樣的)。

需要注意的是,當(dāng)普通函數(shù)返回后,進(jìn)程的地址空間中不會(huì)再保存該函數(shù)運(yùn)行時(shí)的任何信息,而協(xié)程返回后,函數(shù)的運(yùn)行時(shí)信息是需要保存下來(lái)的,那么函數(shù)的運(yùn)行時(shí)狀態(tài)到底在內(nèi)存中是什么樣子呢,關(guān)于這個(gè)問(wèn)題你可以參考這里。

接下來(lái),我們就用實(shí)際的代碼看一看協(xié)程。

Show Me The Code

下面我們使用一個(gè)真實(shí)的例子來(lái)講解,語(yǔ)言采用python,不熟悉的同學(xué)不用擔(dān)心,這里不會(huì)有理解上的門(mén)檻。

在python語(yǔ)言中,這個(gè)“定”字同樣使用關(guān)鍵詞yield,這樣我們的func函數(shù)就變成了:

void func() {   print("a")   yield   print("b")   yield   print("c") }

注意,這時(shí)我們的func就不再是簡(jiǎn)簡(jiǎn)單單的函數(shù)了,而是升級(jí)成為了協(xié)程,那么我們?cè)撛趺词褂媚?,很?jiǎn)單:

def A():   co = func() # 得到該協(xié)程   next(co)    # 調(diào)用協(xié)程   print("in function A") # do something   next(co)    # 再次調(diào)用該協(xié)程

我們看到雖然func函數(shù)沒(méi)有return語(yǔ)句,也就是說(shuō)雖然沒(méi)有返回任何值,但是我們依然可以寫(xiě)co =  func()這樣的代碼,意思是說(shuō)co就是我們拿到的協(xié)程了。

接下來(lái)我們調(diào)用該協(xié)程,使用next(co),運(yùn)行函數(shù)A看看執(zhí)行到第3行的結(jié)果是什么:

a

顯然,和我們的預(yù)期一樣,協(xié)程func在print("a")后因執(zhí)行yield而暫停并返回函數(shù)A。

接下來(lái)是第4行,這個(gè)毫無(wú)疑問(wèn),A函數(shù)在做一些自己的事情,因此會(huì)打?。?/p>

a in function A

接下來(lái)是重點(diǎn)的一行,當(dāng)執(zhí)行第5行再次調(diào)用協(xié)程時(shí)該打印什么呢?

如果func是普通函數(shù),那么會(huì)執(zhí)行func的第一行代碼,也就是打印a。

但func不是普通函數(shù),而是協(xié)程,我們之前說(shuō)過(guò),協(xié)程會(huì)在上一個(gè)返回點(diǎn)繼續(xù)運(yùn)行,因此這里應(yīng)該執(zhí)行的是func函數(shù)第一個(gè)yield之后的代碼,也就是print("b")。

a in function A b

看到了吧,協(xié)程是一個(gè)很神奇的函數(shù),它會(huì)自己記住之前的執(zhí)行狀態(tài),當(dāng)再次調(diào)用時(shí)會(huì)從上一次的返回點(diǎn)繼續(xù)執(zhí)行。

圖形化解釋

為了讓你更加徹底的理解協(xié)程,我們使用圖形化的方式再看一遍,首先是普通的函數(shù)調(diào)用:

程序員應(yīng)怎么理解高并發(fā)中的協(xié)程

在該圖中,方框內(nèi)表示該函數(shù)的指令序列,如果該函數(shù)不調(diào)用任何其它函數(shù),那么應(yīng)該從上到下依次執(zhí)行,但函數(shù)中可以調(diào)用其它函數(shù),因此其執(zhí)行并不是簡(jiǎn)單的從上到下,箭頭線表示執(zhí)行流的方向。

從圖中我們可以看到,我們首先來(lái)到funcA函數(shù),執(zhí)行一段時(shí)間后發(fā)現(xiàn)調(diào)用了另一個(gè)函數(shù)funcB,這時(shí)控制轉(zhuǎn)移到該函數(shù),執(zhí)行完成后回到main函數(shù)的調(diào)用點(diǎn)繼續(xù)執(zhí)行。

這是普通的函數(shù)調(diào)用。

接下來(lái)是協(xié)程。

程序員應(yīng)怎么理解高并發(fā)中的協(xié)程

在這里,我們依然首先在funcA函數(shù)中執(zhí)行,運(yùn)行一段時(shí)間后調(diào)用協(xié)程,協(xié)程開(kāi)始執(zhí)行,直到第一個(gè)掛起點(diǎn),此后就像普通函數(shù)一樣返回funcA函數(shù),funcA函數(shù)執(zhí)行一些代碼后再次調(diào)用該協(xié)程,注意,協(xié)程這時(shí)就和普通函數(shù)不一樣了,協(xié)程并不是從第一條指令開(kāi)始執(zhí)行而是從上一次的掛起點(diǎn)開(kāi)始執(zhí)行,執(zhí)行一段時(shí)間后遇到第二個(gè)掛起點(diǎn),這時(shí)協(xié)程再次像普通函數(shù)一樣返回funcA函數(shù),funcA函數(shù)執(zhí)行一段時(shí)間后整個(gè)程序結(jié)束。

程序員應(yīng)怎么理解高并發(fā)中的協(xié)程

函數(shù)只是協(xié)程的一種特例

怎么樣,神奇不神奇,和普通函數(shù)不同的是,協(xié)程能知道自己上一次執(zhí)行到了哪里。

現(xiàn)在你應(yīng)該明白了吧,協(xié)程會(huì)在函數(shù)被暫停運(yùn)行時(shí)保存函數(shù)的運(yùn)行狀態(tài),并可以從保存的狀態(tài)中恢復(fù)并繼續(xù)運(yùn)行。

很熟悉的味道有沒(méi)有,這不就是操作系統(tǒng)對(duì)線程的調(diào)度嘛,線程也可以被暫停,操作系統(tǒng)保存線程運(yùn)行狀態(tài)然后去調(diào)度其它線程,此后該線程再次被分配CPU時(shí)還可以繼續(xù)運(yùn)行,就像沒(méi)有被暫停過(guò)一樣。

只不過(guò)線程的調(diào)度是操作系統(tǒng)實(shí)現(xiàn)的,這些對(duì)程序員都不可見(jiàn),而協(xié)程是在用戶(hù)態(tài)實(shí)現(xiàn)的,對(duì)程序員可見(jiàn)。

這就是為什么有的人說(shuō)可以把協(xié)程理解為用戶(hù)態(tài)線程的原因。

此處應(yīng)該有掌聲。

也就是說(shuō)現(xiàn)在程序員可以扮演操作系統(tǒng)的角色了,你可以自己控制協(xié)程在什么時(shí)候運(yùn)行,什么時(shí)候暫停,也就是說(shuō)協(xié)程的調(diào)度權(quán)在你自己手上。

在協(xié)程這件事兒上,調(diào)度你說(shuō)了算。

當(dāng)你在協(xié)程中寫(xiě)下yield的時(shí)候就是想要暫停該協(xié)程,當(dāng)使用next()時(shí)就是要再次運(yùn)行該協(xié)程。

現(xiàn)在你應(yīng)該理解為什么說(shuō)函數(shù)只是協(xié)程的一種特例了吧,函數(shù)其實(shí)只是沒(méi)有掛起點(diǎn)的協(xié)程而已。

協(xié)程的歷史

有的同學(xué)可能認(rèn)為協(xié)程是一種比較新的技術(shù),然而其實(shí)協(xié)程這種概念早在1958年就已經(jīng)提出來(lái)了,要知道這時(shí)線程的概念都還沒(méi)有提出來(lái)。

到了1972年,終于有編程語(yǔ)言實(shí)現(xiàn)了這個(gè)概念,這兩門(mén)編程語(yǔ)言就是Simula 67 以及Scheme。

程序員應(yīng)怎么理解高并發(fā)中的協(xié)程

但協(xié)程這個(gè)概念始終沒(méi)有流行起來(lái),甚至在1993年還有人考古一樣專(zhuān)門(mén)寫(xiě)論文挖出協(xié)程這種古老的技術(shù)。

因?yàn)檫@一時(shí)期還沒(méi)有線程,如果你想在操作系統(tǒng)寫(xiě)出并發(fā)程序那么你將不得不使用類(lèi)似協(xié)程這樣的技術(shù),后來(lái)線程開(kāi)始出現(xiàn),操作系統(tǒng)終于開(kāi)始原生支持程序的并發(fā)執(zhí)行,就這樣,協(xié)程逐漸淡出了程序員的視線。

直到近些年,隨著互聯(lián)網(wǎng)的發(fā)展,尤其是移動(dòng)互聯(lián)網(wǎng)時(shí)代的到來(lái),服務(wù)端對(duì)高并發(fā)的要求越來(lái)越高,協(xié)程再一次重回技術(shù)主流,各大編程語(yǔ)言都已經(jīng)支持或計(jì)劃開(kāi)始支持協(xié)程。

那么協(xié)程到底是如何實(shí)現(xiàn)的呢?

協(xié)程是如何實(shí)現(xiàn)的

讓我們從問(wèn)題的本質(zhì)出發(fā)來(lái)思考這個(gè)問(wèn)題。

協(xié)程的本質(zhì)是什么呢?

其實(shí)就是可以被暫停以及可以被恢復(fù)運(yùn)行的函數(shù)。

那么可以被暫停以及可以被恢復(fù)意味著什么呢?

看過(guò)籃球比賽的同學(xué)想必都知道(沒(méi)看過(guò)的也能知道),籃球比賽也是可以被隨時(shí)暫停的,暫停時(shí)大家需要記住球在哪一方,各自的站位是什么,等到比賽繼續(xù)的時(shí)候大家回到各自的位置,裁判哨子一響比賽繼續(xù),就像比賽沒(méi)有被暫停過(guò)一樣。

看到問(wèn)題的關(guān)鍵了嗎,比賽之所以可以被暫停也可以繼續(xù)是因?yàn)楸荣悹顟B(tài)被記錄下來(lái)了(站位、球在哪一方),這里的狀態(tài)就是計(jì)算機(jī)科學(xué)中常說(shuō)的上下文,context。

回到協(xié)程。

協(xié)程之所以可以被暫停也可以繼續(xù),那么一定要記錄下被暫停時(shí)的狀態(tài),也就是上下文,當(dāng)繼續(xù)運(yùn)行的時(shí)候要恢復(fù)其上下文(狀態(tài)),那么接下來(lái)很自然的一個(gè)問(wèn)題就是,函數(shù)運(yùn)行時(shí)的狀態(tài)是什么?

這個(gè)關(guān)鍵的問(wèn)題的答案就在《函數(shù)運(yùn)行起來(lái)后在內(nèi)存中是什么樣子的》這篇文章中,函數(shù)運(yùn)行時(shí)所有的狀態(tài)信息都位于函數(shù)運(yùn)行時(shí)棧中。

函數(shù)運(yùn)行時(shí)棧就是我們需要保存的狀態(tài),也就是所謂的上下文,如圖所示:

程序員應(yīng)怎么理解高并發(fā)中的協(xié)程

從圖中我們可以看出,該進(jìn)程中只有一個(gè)線程,棧區(qū)中有四個(gè)棧幀,main函數(shù)調(diào)用A函數(shù),A函數(shù)調(diào)用B函數(shù),B函數(shù)調(diào)用C函數(shù),當(dāng)C函數(shù)在運(yùn)行時(shí)整個(gè)進(jìn)程的狀態(tài)就如圖所示。

現(xiàn)在我們已經(jīng)知道了函數(shù)的運(yùn)行時(shí)狀態(tài)就保存在棧區(qū)的棧幀中,接下來(lái)重點(diǎn)來(lái)了哦。

既然函數(shù)的運(yùn)行時(shí)狀態(tài)保存在棧區(qū)的棧幀中,那么如果我們想暫停協(xié)程的運(yùn)行就必須保存整個(gè)棧幀的數(shù)據(jù),那么我們?cè)搶⒄麄€(gè)棧幀中的數(shù)據(jù)保存在哪里呢?

想一想這個(gè)問(wèn)題,整個(gè)進(jìn)程的內(nèi)存區(qū)中哪一塊是專(zhuān)門(mén)用來(lái)長(zhǎng)時(shí)間(進(jìn)程生命周期)存儲(chǔ)數(shù)據(jù)的?是不是大腦又一片空白了?

先別空白!

很顯然,這就是堆區(qū)啊,heap,我們可以將棧幀保存在堆區(qū)中,那么我們?cè)撛趺丛诙褏^(qū)中保存數(shù)據(jù)呢?希望你還沒(méi)有暈,在堆區(qū)中開(kāi)辟空間就是我們常用的C語(yǔ)言中的malloc或者C++中的new。

我們需要做的就是在堆區(qū)中申請(qǐng)一段空間,讓后把協(xié)程的整個(gè)棧區(qū)保存下,當(dāng)需要恢復(fù)協(xié)程的運(yùn)行時(shí)再?gòu)亩褏^(qū)中copy出來(lái)恢復(fù)函數(shù)運(yùn)行時(shí)狀態(tài)。

再仔細(xì)想一想,為什么我們要這么麻煩的來(lái)回copy數(shù)據(jù)呢?

實(shí)際上,我們需要做的是直接把協(xié)程的運(yùn)行需要的棧幀空間直接開(kāi)辟在堆區(qū)中,這樣都不用來(lái)回copy數(shù)據(jù)了,如圖所示。

程序員應(yīng)怎么理解高并發(fā)中的協(xié)程

從圖中我們可以看到,該程序中開(kāi)啟了兩個(gè)協(xié)程,這兩個(gè)協(xié)程的棧區(qū)都是在堆上分配的,這樣我們就可以隨時(shí)中斷或者恢復(fù)協(xié)程的執(zhí)行了。

有的同學(xué)可能會(huì)問(wèn),那么進(jìn)程地址空間最上層的棧區(qū)現(xiàn)在的作用是什么呢?

這一區(qū)域依然是用來(lái)保存函數(shù)棧幀的,只不過(guò)這些函數(shù)并不是運(yùn)行在協(xié)程而是普通線程中的。

現(xiàn)在你應(yīng)該看到了吧,在上圖中實(shí)際上有3個(gè)執(zhí)行流:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 一個(gè)普通線程

  3. 兩個(gè)協(xié)程

雖然有3個(gè)執(zhí)行流但我們創(chuàng)建了幾個(gè)線程呢?

一個(gè)線程。

現(xiàn)在你應(yīng)該明白為什么要使用協(xié)程了吧,使用協(xié)程理論上我們可以開(kāi)啟無(wú)數(shù)并發(fā)執(zhí)行流,只要堆區(qū)空間足夠,同時(shí)還沒(méi)有創(chuàng)建線程的開(kāi)銷(xiāo),所有協(xié)程的調(diào)度、切換都發(fā)生在用戶(hù)態(tài),這就是為什么協(xié)程也被稱(chēng)作用戶(hù)態(tài)線程的原因所在。

掌聲在哪里?

因此即使你創(chuàng)建了N多協(xié)程,但在操作系統(tǒng)看來(lái)依然只有一個(gè)線程,也就是說(shuō)協(xié)程對(duì)操作系統(tǒng)來(lái)說(shuō)是不可見(jiàn)的。

這也許是為什么協(xié)程這個(gè)概念比線程提出的要早的原因,可能是寫(xiě)普通應(yīng)用的程序員比寫(xiě)操作系統(tǒng)的程序員最先遇到需要多個(gè)并行流的需求,那時(shí)可能都還沒(méi)有操作系統(tǒng)的概念,或者操作系統(tǒng)沒(méi)有并行這種需求,所以非操作系統(tǒng)程序員只能自己動(dòng)手實(shí)現(xiàn)執(zhí)行流,也就是協(xié)程。

現(xiàn)在你應(yīng)該對(duì)協(xié)程有一個(gè)清晰的認(rèn)知了吧。

到此,相信大家對(duì)“程序員應(yīng)怎么理解高并發(fā)中的協(xié)程”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI