<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python 的協(xié)程和 goroutine 有什么區(qū)別?

          共 7949字,需瀏覽 16分鐘

           ·

          2021-09-13 12:40

          最近在做后端服務(wù)python到go的遷移和重構(gòu),這兩種語(yǔ)言里,最大的特色和優(yōu)勢(shì)就是都支持協(xié)程。之前主要做python的性能優(yōu)化和架構(gòu)優(yōu)化,一開(kāi)始覺(jué)得兩個(gè)協(xié)程原理和應(yīng)用應(yīng)該差不多,后來(lái)發(fā)現(xiàn)還是有很大的區(qū)別,今天就在這里總結(jié)一下。

           什么是協(xié)程

          在說(shuō)它們兩者區(qū)別前,我們首先聊一下什么是協(xié)程,好像它沒(méi)有一個(gè)官方的定義,那就結(jié)合平時(shí)的應(yīng)用經(jīng)驗(yàn)和學(xué)習(xí)內(nèi)容來(lái)談?wù)勛约旱睦斫狻?/p>

          協(xié)程,其實(shí)可以理解為一種特殊的程序調(diào)用。特殊的是在執(zhí)行過(guò)程中,在子程序(或者說(shuō)函數(shù))內(nèi)部可中斷,然后轉(zhuǎn)而執(zhí)行別的子程序,在適當(dāng)?shù)臅r(shí)候再返回來(lái)接著執(zhí)行。
          注意,它有兩個(gè)特征:

          可中斷,這里的中斷不是普通的函數(shù)調(diào)用,而是類(lèi)似CPU的中斷,CPU在這里直接釋放轉(zhuǎn)到其他程序斷點(diǎn)繼續(xù)執(zhí)行。

          可恢復(fù),等到合適的時(shí)候,可以恢復(fù)到中斷的地方繼續(xù)執(zhí)行,至于什么是合適的時(shí)候,我們后面再探討。

           和進(jìn)程線(xiàn)程的區(qū)別

          上面兩個(gè)特點(diǎn)就導(dǎo)致了它相對(duì)于線(xiàn)程和進(jìn)程切換來(lái)說(shuō)極高的執(zhí)行效率,為什么這么說(shuō)呢?我們先老生常談地說(shuō)一下進(jìn)程和線(xiàn)程。

          進(jìn)程是操作系統(tǒng)資源分配的基本單位,線(xiàn)程是操作系統(tǒng)調(diào)度和執(zhí)行的最小單位。這兩句應(yīng)該是我們最常聽(tīng)到的兩句話(huà),拆開(kāi)來(lái)說(shuō),進(jìn)程是程序的啟動(dòng)實(shí)例,擁有代碼和打開(kāi)的文件資源、數(shù)據(jù)資源、獨(dú)立的內(nèi)存空間。線(xiàn)程從屬于進(jìn)程,是程序的實(shí)際執(zhí)行者,一個(gè)進(jìn)程至少包含一個(gè)主線(xiàn)程,也可以有更多的子線(xiàn)程,線(xiàn)程擁有自己的??臻g。無(wú)論是進(jìn)程還是線(xiàn)程,都是由操作系統(tǒng)所管理和切換的。

          我們?cè)賮?lái)看協(xié)程,它又叫做微線(xiàn)程,但其實(shí)它和進(jìn)程還有線(xiàn)程完全不是一個(gè)維度上的概念。進(jìn)程和線(xiàn)程的切換完全是用戶(hù)無(wú)感,由操作系統(tǒng)控制,從用戶(hù)態(tài)到內(nèi)核態(tài)再到用戶(hù)態(tài)。而協(xié)程的切換完全是程序代碼控制的,在用戶(hù)態(tài)的切換,就像函數(shù)回調(diào)的消耗一樣,在線(xiàn)程的棧內(nèi)即可完成。

           python的協(xié)程(coroutine)

          python的協(xié)程其實(shí)是我們通常意義上的協(xié)程Goroutine。

          從概念上來(lái)講,python的協(xié)程同樣是在適當(dāng)?shù)臅r(shí)候可中斷可恢復(fù)。那么什么是適當(dāng)?shù)臅r(shí)候呢,就是你認(rèn)為適當(dāng)?shù)臅r(shí)候,因?yàn)槌绦蛟谀睦锇l(fā)生協(xié)程切換完全控制在開(kāi)發(fā)者手里。當(dāng)然,對(duì)于python來(lái)說(shuō),由于GIL鎖,在CPU密集的代碼上做協(xié)程切換是沒(méi)啥意義的,CPU本來(lái)就在忙著沒(méi)偷懶,切換到其他協(xié)程,也只是在單核內(nèi)換個(gè)地方忙而已。很明顯,我們應(yīng)該在IO密集的地方來(lái)起協(xié)程,這樣可以讓CPU不再空等轉(zhuǎn)而去別的地方干活,才能真正發(fā)揮協(xié)程的威力。

          從實(shí)現(xiàn)上來(lái)講,如果熟知了python生成器,還可以將協(xié)程理解為生成器+調(diào)度策略,生成器中的yield關(guān)鍵字,就可以讓生成器函數(shù)發(fā)生中斷,而調(diào)度策略,可以驅(qū)動(dòng)著協(xié)程的執(zhí)行和恢復(fù)。這樣就實(shí)現(xiàn)了協(xié)程的概念。這里的調(diào)度策略可能有很多種,簡(jiǎn)單的例如忙輪循:while True,更簡(jiǎn)單的甚至是一個(gè)for循環(huán)。就可以驅(qū)動(dòng)生成器的運(yùn)行,因?yàn)樯善鞅旧硪彩强傻?。?fù)雜的比如可能是基于epool的事件循環(huán),在python2的tornado中,以及python3的asyncio中,都對(duì)協(xié)程的用法做了更好的封裝,通過(guò)yield和await就可以使用協(xié)程,通過(guò)事件循環(huán)監(jiān)控文件描述符狀態(tài)來(lái)驅(qū)動(dòng)協(xié)程恢復(fù)執(zhí)行。

          我們看一個(gè)簡(jiǎn)單的協(xié)程:

          import time


          def consumer():
              r = ''

              while True:
                  n = yield r
                  if not n:
                      return

                  print('[CONSUMER] Consuming %s...' % n)
                  time.sleep(1)
                  r = '200 OK'

          def produce(c):
              c.next()
              n = 0
              while n < 5:
                  n = n + 1
                  print('[PRODUCER] Producing %s...' % n)
                  r = c.send(n)
                  print('[PRODUCER] Consumer return: %s' % r)
              c.close()


          if __name__=='__main__':
              c = consumer()
              produce(c)

          很明顯這是一個(gè)傳統(tǒng)的生產(chǎn)者-消費(fèi)者模型,這里consumer函數(shù)就是一個(gè)協(xié)程(生成器),它在n = yield r 的地方發(fā)生中斷,生產(chǎn)者produce中的c.send(n),可以驅(qū)動(dòng)協(xié)程的恢復(fù),并且向協(xié)程函數(shù)傳遞數(shù)據(jù)n,接收返回結(jié)果r。而while n < 5,就是我們所說(shuō)的調(diào)度策略。在生產(chǎn)中,這種模式很適合我們來(lái)做一些pipeline數(shù)據(jù)的消費(fèi),我們不需要寫(xiě)死幾個(gè)生產(chǎn)者進(jìn)程幾個(gè)消費(fèi)者進(jìn)程,而是用這種協(xié)程的方式,來(lái)實(shí)現(xiàn)CPU動(dòng)態(tài)地分配調(diào)度。

          如果你看過(guò)上篇文章的話(huà),是不是發(fā)現(xiàn)這個(gè)golang中流水線(xiàn)模型有點(diǎn)像呢,也是生產(chǎn)者和消費(fèi)者間進(jìn)行通信,但go是通過(guò)channel這種安全的數(shù)據(jù)結(jié)構(gòu),為什么python不需要呢,因?yàn)閜ython的協(xié)程是在單線(xiàn)程內(nèi)切換本身就是安全的,換句話(huà)說(shuō),協(xié)程間本身就是串行執(zhí)行的。而golang則不然。思考一個(gè)有意思的問(wèn)題,如果我們將go流水線(xiàn)模型中channel設(shè)置為無(wú)緩沖區(qū)時(shí),生產(chǎn)者絕對(duì)驅(qū)動(dòng)消費(fèi)者的執(zhí)行,是不是就跟python很像了呢。所以python的協(xié)程從某種意義來(lái)說(shuō),是不是golang協(xié)程的一種特殊情況呢?
          后端在線(xiàn)服務(wù)中我們更常用的python協(xié)程其實(shí)是在異步IO框架中使用,之前我們也提過(guò)python協(xié)程在IO密集的系統(tǒng)中使用才能發(fā)揮它的威力。并且大多數(shù)的數(shù)據(jù)中間件都已經(jīng)提供支持了異步包的支持,這里順便貼一個(gè)python3支持的異步IO庫(kù),基本支持了常見(jiàn)的異步數(shù)據(jù)中間件。

          再看一個(gè)我們業(yè)務(wù)代碼中的片段,asyncio支持的原生協(xié)程:
          asyncio支持的基于epool的事件循環(huán):

          def main():
              define_options()
              options.parse_command_line()
              # 使用uvloop代替原生事件循環(huán)
              # asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
              app = tornado.web.Application(handlers=handlers, debug=options.debug)
              http_server = tornado.httpserver.HTTPServer(app)
              http_server.listen(options.port)
              asyncio.get_event_loop().run_forever()

          async/await支持的原生協(xié)程:

          class RcOutputHandler(BaseHandler):
              async def post(self):
                  status, msg, user = self.check_args('uid''order_no''mid''phone''name''apply_id',
                                                      'product_id')

                  if status != ErrorCodeConfig.SUCCESS:
                      status, msg, report = status, msg, None
                  else:
                      rcoutput_flow_instance = ZHANRONG_CUSTOM_PRODUCTID_RCFLOW_MAP.get(user.product_id,
                                                                                        RcOutputFlowControler())
                      status, msg, report = await rcoutput_flow_instance.get_rcoutput_result(user)

                  res = self.generate_response_data(status, msg, report)
                  await self.finish(res)

                  # 陪跑流程
                  await AccompanyRunningFlowControler().get_accompany_data(user)

           python協(xié)程的特點(diǎn)

          單線(xiàn)程內(nèi)切換,適用于IO密集型程序中,可以最大化IO多路復(fù)用的效果。

          無(wú)法利用多核。

          協(xié)程間完全同步,不會(huì)并行。不需要考慮數(shù)據(jù)安全。

          用法多樣,可以用在web服務(wù)中,也可用在pipeline數(shù)據(jù)/任務(wù)消費(fèi)中

           golang的協(xié)程(goroutine)

          golang的協(xié)程就和傳統(tǒng)意義上的協(xié)程不大一樣了,兼具協(xié)程和線(xiàn)程的優(yōu)勢(shì)。這也是go最大的特色,就是從語(yǔ)言層面支持并發(fā)。Go語(yǔ)言里,啟動(dòng)一個(gè)goroutine很容易:go function 就行。

          同樣從概念上來(lái)講,golang的協(xié)程同樣是在適當(dāng)?shù)臅r(shí)候可中斷可恢復(fù)。當(dāng)協(xié)程中發(fā)生channel讀寫(xiě)的阻塞或者系統(tǒng)調(diào)用時(shí),就會(huì)切換到其他協(xié)程。具體的代碼示例可以看上篇文章,就不再贅述了。

          從實(shí)現(xiàn)上來(lái)說(shuō),goroutine可以在多核上運(yùn)行,從而實(shí)現(xiàn)協(xié)程并行,我們先直接看下go的調(diào)度模型MPG。

          如上圖,M指的是Machine,一個(gè)M直接關(guān)聯(lián)了一個(gè)內(nèi)核線(xiàn)程。由操作系統(tǒng)管理。
          P指的是”processor”,代表了M所需的上下文環(huán)境,也是處理用戶(hù)級(jí)代碼邏輯的處理器。它負(fù)責(zé)銜接M和G的調(diào)度上下文,將等待執(zhí)行的G與M對(duì)接。
          G指的是Goroutine,其實(shí)本質(zhì)上也是一種輕量級(jí)的線(xiàn)程。包括了調(diào)用棧,重要的調(diào)度信息,例如channel等。

          每次go調(diào)用的時(shí)候,都會(huì):

          1. 創(chuàng)建一個(gè)G對(duì)象,加入到本地隊(duì)列或者全局隊(duì)列

          2. 如果還有空閑的P,則創(chuàng)建一個(gè)M

          3. M會(huì)啟動(dòng)一個(gè)底層線(xiàn)程,循環(huán)執(zhí)行能找到的G任務(wù)

          4. G任務(wù)的執(zhí)行順序是,先從本地隊(duì)列找,本地沒(méi)有則從全局隊(duì)列找(一次性轉(zhuǎn)移(全局G個(gè)數(shù)/P個(gè)數(shù))個(gè),再去其它P中找(一次性轉(zhuǎn)移一半)

          對(duì)于上面的第2-3步,創(chuàng)建一個(gè)M,其過(guò)程:

          1. 先找到一個(gè)空閑的P,如果沒(méi)有則直接返回,(哈哈,這個(gè)地方就保證了進(jìn)程不會(huì)占用超過(guò)自己設(shè)定的cpu個(gè)數(shù))

          2. 調(diào)用系統(tǒng)api創(chuàng)建線(xiàn)程,不同的操作系統(tǒng),調(diào)用不一樣,其實(shí)就是和c語(yǔ)言創(chuàng)建過(guò)程是一致的

          3. 然后創(chuàng)建的這個(gè)線(xiàn)程里面才是真正做事的,循環(huán)執(zhí)行G任務(wù)

          當(dāng)協(xié)程發(fā)生阻塞切換時(shí):

          1. M0出讓P

          2. 創(chuàng)建M1接管P及其任務(wù)隊(duì)列繼續(xù)執(zhí)行其他G。

          3. 當(dāng)阻塞結(jié)束后,M0會(huì)嘗試獲取空閑的P,失敗的話(huà),就把當(dāng)前的G放到全局隊(duì)列的隊(duì)尾。

          這里我們需要注意三點(diǎn):
          1、M與P的數(shù)量沒(méi)有絕對(duì)關(guān)系,一個(gè)M阻塞,P就會(huì)去創(chuàng)建或者切換另一個(gè)M,所以,即使P的默認(rèn)數(shù)量是1,也有可能會(huì)創(chuàng)建很多個(gè)M出來(lái)。

          2、P何時(shí)創(chuàng)建:在確定了P的最大數(shù)量n后,運(yùn)行時(shí)系統(tǒng)會(huì)根據(jù)這個(gè)數(shù)量創(chuàng)建n個(gè)P。

          3、M何時(shí)創(chuàng)建:沒(méi)有足夠的M來(lái)關(guān)聯(lián)P并運(yùn)行其中的可運(yùn)行的G。比如所有的M此時(shí)都阻塞住了,而P中還有很多就緒任務(wù),就會(huì)去尋找空閑的M,而沒(méi)有空閑的,就會(huì)去創(chuàng)建新的M。

           Go協(xié)程的特點(diǎn)

          協(xié)程間需要保證數(shù)據(jù)安全,比如通過(guò)channel或鎖。

          可以利用多核并行執(zhí)行。

          協(xié)程間不完全同步,可以并行運(yùn)行,具體要看channel的設(shè)計(jì)。

          搶占式調(diào)度,可能無(wú)法實(shí)現(xiàn)公平。

           coroutine(python)和goroutine(go)的區(qū)別

          除了python,C#, Lua語(yǔ)言都支持 coroutine 特性。coroutine 與 goroutine 在名字上類(lèi)似,都是可中斷可恢復(fù)的協(xié)程,它們之間最大的不同是,goroutine 可能在多核上發(fā)生并行執(zhí)行,單但 coroutine 始終是順序執(zhí)行。也基于此,我們應(yīng)該清楚coroutine適用于IO密集程序中,而goroutine在 IO密集和CPU密集中都有很好的表現(xiàn)。不過(guò)話(huà)說(shuō)回來(lái),go就一定比python快么,假如在完全I(xiàn)O并發(fā)密集的程序中,python的表現(xiàn)反而更好,因?yàn)閱尉€(xiàn)程內(nèi)的協(xié)程切換效率更高。

          從運(yùn)行機(jī)制上來(lái)說(shuō),coroutine 的運(yùn)行機(jī)制屬于協(xié)作式任務(wù)處理, 程序需要主動(dòng)交出控制權(quán),宿主才能獲得控制權(quán)并將控制權(quán)交給其他 coroutine。如果開(kāi)發(fā)者無(wú)意間或者故意讓?xiě)?yīng)用程序長(zhǎng)時(shí)間占用 CPU,操作系統(tǒng)也無(wú)能為力,表現(xiàn)出來(lái)的效果就是計(jì)算機(jī)很容易失去響應(yīng)或者死機(jī)。goroutine 屬于搶占式任務(wù)處理,已經(jīng)和現(xiàn)有的多線(xiàn)程和多進(jìn)程任務(wù)處理非常類(lèi)似, 雖然無(wú)法控制自己獲取高優(yōu)先度支持。但如果發(fā)現(xiàn)一個(gè)應(yīng)用程序長(zhǎng)時(shí)間大量地占用 CPU,那么用戶(hù)有權(quán)終止這個(gè)任務(wù)。

          從協(xié)程:線(xiàn)程的對(duì)應(yīng)方式來(lái)看

          N:1,Python協(xié)程模式,多個(gè)協(xié)程在一個(gè)線(xiàn)程中切換。在IO密集時(shí)切換效率高,但沒(méi)有用到多核

          1:1,Java多線(xiàn)程模式,每個(gè)協(xié)程只在一個(gè)線(xiàn)程中運(yùn)行,這樣協(xié)程和線(xiàn)程沒(méi)區(qū)別,雖然用了多核,但是線(xiàn)程切換開(kāi)銷(xiāo)大。

          1:1,go模式,多個(gè)協(xié)程在多個(gè)線(xiàn)程上切換,既可以用到多核,又可以減少切換開(kāi)銷(xiāo)。(當(dāng)都是cpu密集時(shí),在多核上切換好,當(dāng)都是io密集時(shí),在單核上切換好)。

          從協(xié)程通信和調(diào)度機(jī)制來(lái)看

          作者:屈天航

          原鏈接:https://segmentfault.com/a/1190000038241863

          瀏覽 121
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国内视频精品 | 香蕉女人久久 | 免费在线看黄的网站 | 日日操视频 | 国产成人在线免费视频 |