摘要:隨著我們對(duì)于效率的追求不斷提高,基于單線程來(lái)實(shí)現(xiàn)并發(fā)又成為一個(gè)新的課題,即只用一個(gè)主線程很明顯可利用的只有一個(gè)情況下實(shí)現(xiàn)并發(fā)。作為的補(bǔ)充可以檢測(cè)操作,在遇到操作的情況下才發(fā)生切換協(xié)程介紹協(xié)程是單線程下的并發(fā),又稱(chēng)微線程,纖程。
引子
之前我們學(xué)習(xí)了線程、進(jìn)程的概念,了解了在操作系統(tǒng)中進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位。按道理來(lái)說(shuō)我們已經(jīng)算是把cpu的利用率提高很多了。但是我們知道無(wú)論是創(chuàng)建多進(jìn)程還是創(chuàng)建多線程來(lái)解決問(wèn)題,都要消耗一定的時(shí)間來(lái)創(chuàng)建進(jìn)程、創(chuàng)建線程、以及管理他們之間的切換。
隨著我們對(duì)于效率的追求不斷提高,基于單線程來(lái)實(shí)現(xiàn)并發(fā)又成為一個(gè)新的課題,即只用一個(gè)主線程(很明顯可利用的cpu只有一個(gè))情況下實(shí)現(xiàn)并發(fā)。這樣就可以節(jié)省創(chuàng)建線進(jìn)程所消耗的時(shí)間。
為此我們需要先回顧下并發(fā)的本質(zhì):切換+保存狀態(tài)
cpu正在運(yùn)行一個(gè)任務(wù),會(huì)在兩種情況下切走去執(zhí)行其他的任務(wù)(切換由操作系統(tǒng)強(qiáng)制控制),一種情況是該任務(wù)發(fā)生了阻塞,另外一種情況是該任務(wù)計(jì)算的時(shí)間過(guò)長(zhǎng)
在介紹進(jìn)程理論時(shí),提及進(jìn)程的三種執(zhí)行狀態(tài),而線程才是執(zhí)行單位,所以也可以將上圖理解為線程的三種狀態(tài)
一:其中第二種情況并不能提升效率,只是為了讓cpu能夠雨露均沾,實(shí)現(xiàn)看起來(lái)所有任務(wù)都被“同時(shí)”執(zhí)行的效果,如果多個(gè)任務(wù)都是純計(jì)算的,這種切換反而會(huì)降低效率。
為此我們可以基于yield來(lái)驗(yàn)證。yield本身就是一種在單線程下可以保存任務(wù)運(yùn)行狀態(tài)的方法:
1 yiled可以保存狀態(tài),yield的狀態(tài)保存與操作系統(tǒng)的保存線程狀態(tài)很像,但是yield是代碼級(jí)別控制的,更輕量級(jí) 2 send可以把一個(gè)函數(shù)的結(jié)果傳給另外一個(gè)函數(shù),以此實(shí)現(xiàn)單線程內(nèi)程序之間的切換
#串行執(zhí)行 import time def consumer(res): """任務(wù)1:接收數(shù)據(jù),處理數(shù)據(jù)""" pass def producer(): """任務(wù)2:生產(chǎn)數(shù)據(jù)""" res=[] for i in range(10000000): res.append(i) return res start=time.time() #串行執(zhí)行 res=producer() consumer(res) #寫(xiě)成consumer(producer())會(huì)降低執(zhí)行效率 stop=time.time() print(stop-start) #1.874000072479248 #基于yield并發(fā)執(zhí)行 import time def consumer(): """任務(wù)1:接收數(shù)據(jù),處理數(shù)據(jù)""" while True: print("this is consumer") x=yield def producer(): """任務(wù)2:生產(chǎn)數(shù)據(jù)""" g=consumer() next(g) for i in range(10): print("this is prodecer") g.send(i) start=time.time() #基于yield保存狀態(tài),實(shí)現(xiàn)兩個(gè)任務(wù)直接來(lái)回切換,即并發(fā)的效果 #PS:如果每個(gè)任務(wù)中都加上打印,那么明顯地看到兩個(gè)任務(wù)的打印是你一次我一次,即并發(fā)執(zhí)行的. producer() stop=time.time() print(stop-start) #2.0272178649902344
二:第一種情況的切換。在任務(wù)一遇到io情況下,切到任務(wù)二去執(zhí)行,這樣就可以利用任務(wù)一阻塞的時(shí)間完成任務(wù)二的計(jì)算,效率的提升就在于此。
import time def consumer(): """任務(wù)1:接收數(shù)據(jù),處理數(shù)據(jù)""" while True: x=yield def producer(): """任務(wù)2:生產(chǎn)數(shù)據(jù)""" g=consumer() next(g) for i in range(10000000): g.send(i) time.sleep(2) start=time.time() producer() #并發(fā)執(zhí)行,但是任務(wù)producer遇到io就會(huì)阻塞住,并不會(huì)切到該線程內(nèi)的其他任務(wù)去執(zhí)行 stop=time.time() print(stop-start)
對(duì)于單線程下,我們不可避免程序中出現(xiàn)io操作,但如果我們能在自己的程序中(即用戶程序級(jí)別,而非操作系統(tǒng)級(jí)別)控制單線程下的多個(gè)任務(wù)能在一個(gè)任務(wù)遇到io阻塞時(shí)就切換到另外一個(gè)任務(wù)去計(jì)算,這樣就保證了該線程能夠最大限度地處于就緒態(tài),即隨時(shí)都可以被cpu執(zhí)行的狀態(tài),相當(dāng)于我們?cè)谟脩舫绦蚣?jí)別將自己的io操作最大限度地隱藏起來(lái),從而可以迷惑操作系統(tǒng),讓其看到:該線程好像是一直在計(jì)算,io比較少,從而更多的將cpu的執(zhí)行權(quán)限分配給我們的線程。
協(xié)程的本質(zhì)就是在單線程下,由用戶自己控制一個(gè)任務(wù)遇到io阻塞了就切換另外一個(gè)任務(wù)去執(zhí)行,以此來(lái)提升效率。為了實(shí)現(xiàn)它,我們需要找尋一種可以同時(shí)滿足以下條件的解決方案:
1. 可以控制多個(gè)任務(wù)之間的切換,切換之前將任務(wù)的狀態(tài)保存下來(lái),以便重新運(yùn)行時(shí),可以基于暫停的位置繼續(xù)執(zhí)行。 2. 作為1的補(bǔ)充:可以檢測(cè)io操作,在遇到io操作的情況下才發(fā)生切換協(xié)程介紹
協(xié)程:是單線程下的并發(fā),又稱(chēng)微線程,纖程。英文名Coroutine。一句話說(shuō)明什么是線程:協(xié)程是一種用戶態(tài)的輕量級(jí)線程,即協(xié)程是由用戶程序自己控制調(diào)度的。
需要強(qiáng)調(diào)的是:
1. python的線程屬于內(nèi)核級(jí)別的,即由操作系統(tǒng)控制調(diào)度(如單線程遇到io或執(zhí)行時(shí)間過(guò)長(zhǎng)就會(huì)被迫交出cpu執(zhí)行權(quán)限,切換其他線程運(yùn)行) 2. 單線程內(nèi)開(kāi)啟協(xié)程,一旦遇到io,就會(huì)從應(yīng)用程序級(jí)別(而非操作系統(tǒng))控制切換,以此來(lái)提升效率(?。。》莍o操作的切換與效率無(wú)關(guān))
對(duì)比操作系統(tǒng)控制線程的切換,用戶在單線程內(nèi)控制協(xié)程的切換
優(yōu)點(diǎn)如下:
1. 協(xié)程的切換開(kāi)銷(xiāo)更小,屬于程序級(jí)別的切換,操作系統(tǒng)完全感知不到,因而更加輕量級(jí) 2. 單線程內(nèi)就可以實(shí)現(xiàn)并發(fā)的效果,最大限度地利用cpu
缺點(diǎn)如下:
1. 協(xié)程的本質(zhì)是單線程下,無(wú)法利用多核,可以是一個(gè)程序開(kāi)啟多個(gè)進(jìn)程,每個(gè)進(jìn)程內(nèi)開(kāi)啟多個(gè)線程,每個(gè)線程內(nèi)開(kāi)啟協(xié)程 2. 協(xié)程指的是單個(gè)線程,因而一旦協(xié)程出現(xiàn)阻塞,將會(huì)阻塞整個(gè)線程
總結(jié)協(xié)程特點(diǎn):
1.必須在只有一個(gè)單線程里實(shí)現(xiàn)并發(fā) 2.修改共享數(shù)據(jù)不需加鎖 3.用戶程序里自己保存多個(gè)控制流的上下文棧 4.一個(gè)協(xié)程遇到IO操作自動(dòng)切換到其它協(xié)程(如何實(shí)現(xiàn)檢測(cè)IO,yield、greenlet都無(wú)法實(shí)現(xiàn),就用到了gevent模塊(select機(jī)制))Gevent模塊
安裝:pip3 install gevent
Gevent 是一個(gè)第三方庫(kù),可以輕松通過(guò)gevent實(shí)現(xiàn)并發(fā)同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴(kuò)展模塊形式接入Python的輕量級(jí)協(xié)程。 Greenlet全部運(yùn)行在主程序操作系統(tǒng)進(jìn)程的內(nèi)部,但它們被協(xié)作式地調(diào)度。
用法介紹
g1=gevent.spawn(func,1,,2,3,x=4,y=5)創(chuàng)建一個(gè)協(xié)程對(duì)象g1,spawn括號(hào)內(nèi)第一個(gè)參數(shù)是函數(shù)名, 如eat,后面可以有多個(gè)參數(shù),可以是位置實(shí)參或關(guān)鍵字實(shí)參,都是傳給函數(shù)eat的 g2=gevent.spawn(func2) g1.join() #等待g1結(jié)束 g2.join() #等待g2結(jié)束 #或者上述兩步合作一步:gevent.joinall([g1,g2]) g1.value#拿到func1的返回值
例:遇到io主動(dòng)切換
import gevent def eat(name): print("%s eat 1" %name) gevent.sleep(2) print("%s eat 2" %name) def play(name): print("%s play 1" %name) gevent.sleep(1) print("%s play 2" %name) g1=gevent.spawn(eat,"egon") g2=gevent.spawn(play,"egon") g1.join() g2.join() #或者gevent.joinall([g1,g2]) print("主")
上例gevent.sleep(2)模擬的是gevent可以識(shí)別的io阻塞,而time.sleep(2)或其他的阻塞,gevent是不能直接識(shí)別的需要用下面一行代碼,打補(bǔ)丁,就可以識(shí)別了
from gevent import monkey;monkey.patch_all()必須放到被打補(bǔ)丁者的前面,如time,socket模塊之前
或者我們干脆記憶成:要用gevent,需要將from gevent import monkey;monkey.patch_all()放到文件的開(kāi)頭
from gevent import monkey;monkey.patch_all() import gevent import time def eat(): print("eat food 1") time.sleep(2) print("eat food 2") def play(): print("play 1") time.sleep(1) print("play 2") g1=gevent.spawn(eat) g2=gevent.spawn(play) gevent.joinall([g1,g2]) print("主")
我們可以用threading.current_thread().getName()來(lái)查看每個(gè)g1和g2,查看的結(jié)果為DummyThread-n,即假線程
from gevent import monkey;monkey.patch_all() import threading import gevent import time def eat(): print(threading.current_thread().getName()) print("eat food 1") time.sleep(2) print("eat food 2") def play(): print(threading.current_thread().getName()) print("play 1") time.sleep(1) print("play 2") g1=gevent.spawn(eat) g2=gevent.spawn(play) gevent.joinall([g1,g2]) print("主")Gevent之同步與異步
from gevent import spawn, joinall, monkey;monkey.patch_all() import time def task(pid): """ Some non-deterministic task """ time.sleep(0.5) print("Task %s done" % pid) def synchronous(): # 同步 for i in range(10): task(i) def asynchronous(): # 異步 g_l = [spawn(task, i) for i in range(10)] joinall(g_l) print("DONE") if __name__ == "__main__": print("Synchronous:") synchronous() print("Asynchronous:") asynchronous() # 上面程序的重要部分是將task函數(shù)封裝到Greenlet內(nèi)部線程的gevent.spawn。 # 初始化的greenlet列表存放在數(shù)組threads中,此數(shù)組被傳給gevent.joinall 函數(shù), # 后者阻塞當(dāng)前流程,并執(zhí)行所有給定的greenlet任務(wù)。執(zhí)行流程只會(huì)在 所有g(shù)reenlet執(zhí)行完后才會(huì)繼續(xù)向下走。協(xié)程socket
from gevent import monkey;monkey.patch_all() import socket import gevent def talk(conn): while True: conn.send(b"hello") print(conn.recv(1024)) sk = socket.socket() sk.bind(("127.0.0.1",9090)) sk.listen() while True: conn,addr = sk.accept() g = gevent.spawn(talk,conn)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/42185.html
摘要:所以與多線程相比,線程的數(shù)量越多,協(xié)程性能的優(yōu)勢(shì)越明顯。值得一提的是,在此過(guò)程中,只有一個(gè)線程在執(zhí)行,因此這與多線程的概念是不一樣的。 真正有知識(shí)的人的成長(zhǎng)過(guò)程,就像麥穗的成長(zhǎng)過(guò)程:麥穗空的時(shí)候,麥子長(zhǎng)得很快,麥穗驕傲地高高昂起,但是,麥穗成熟飽滿時(shí),它們開(kāi)始謙虛,垂下麥芒。 ——蒙田《蒙田隨筆全集》 上篇論述了關(guān)于python多線程是否是雞肋的問(wèn)題,得到了一些網(wǎng)友的認(rèn)可,當(dāng)然也有...
摘要:輔之以事件循環(huán),協(xié)程可用于異步處理,尤其是在中。當(dāng)前支持的協(xié)程基于增強(qiáng)型生成器,于版本開(kāi)始采用。新的特性中,異步還有兩種新用途異步內(nèi)容管理器和迭代器。 現(xiàn)在 Python 已經(jīng)支持用協(xié)程進(jìn)行異步處理。但最近有建議稱(chēng)添加協(xié)程以全面完善 Python 的語(yǔ)言結(jié)構(gòu),而不是像現(xiàn)在這樣把他們作為生成器的一個(gè)類(lèi)型。此外,兩個(gè)新的關(guān)鍵字———異步(async)和等待(await),都該添加到 Pyt...
摘要:進(jìn)程線程和協(xié)程進(jìn)程的定義進(jìn)程,是計(jì)算機(jī)中已運(yùn)行程序的實(shí)體。協(xié)程和線程的關(guān)系協(xié)程是在語(yǔ)言層面實(shí)現(xiàn)對(duì)線程的調(diào)度,避免了內(nèi)核級(jí)別的上下文消耗。和都引入了消息調(diào)度系統(tǒng)模型,來(lái)避免鎖的影響和進(jìn)程線程開(kāi)銷(xiāo)大的問(wèn)題。 進(jìn)程、線程和協(xié)程 進(jìn)程的定義: 進(jìn)程,是計(jì)算機(jī)中已運(yùn)行程序的實(shí)體。程序本身只是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程才是程序的真正運(yùn)行實(shí)例。 線程的定義: 操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單...
摘要:創(chuàng)建第一個(gè)協(xié)程推薦使用語(yǔ)法來(lái)聲明協(xié)程,來(lái)編寫(xiě)異步應(yīng)用程序。協(xié)程兩個(gè)緊密相關(guān)的概念是協(xié)程函數(shù)通過(guò)定義的函數(shù)協(xié)程對(duì)象調(diào)用協(xié)程函數(shù)返回的對(duì)象。它是一個(gè)低層級(jí)的可等待對(duì)象,表示一個(gè)異步操作的最終結(jié)果。 我們講以Python 3.7 上的asyncio為例講解如何使用Python的異步IO。 showImg(https://segmentfault.com/img/remote/14600000...
摘要:協(xié)程,又稱(chēng)微線程,纖程。最大的優(yōu)勢(shì)就是協(xié)程極高的執(zhí)行效率。生產(chǎn)者產(chǎn)出第條數(shù)據(jù)返回更新值更新消費(fèi)者正在調(diào)用第條數(shù)據(jù)查看當(dāng)前進(jìn)行的線程函數(shù)中有,返回值為生成器庫(kù)實(shí)現(xiàn)協(xié)程通過(guò)提供了對(duì)協(xié)程的基本支持,但是不完全。 協(xié)程,又稱(chēng)微線程,纖程。英文名Coroutine協(xié)程看上去也是子程序,但執(zhí)行過(guò)程中,在子程序內(nèi)部可中斷,然后轉(zhuǎn)而執(zhí)行別的子程序,在適當(dāng)?shù)臅r(shí)候再返回來(lái)接著執(zhí)行。 最大的優(yōu)勢(shì)就是協(xié)程極高...
摘要:事件循環(huán)是異步編程的底層基石。對(duì)事件集合進(jìn)行輪詢(xún),調(diào)用回調(diào)函數(shù)等一輪事件循環(huán)結(jié)束,循環(huán)往復(fù)。協(xié)程直接利用代碼的執(zhí)行位置來(lái)表示狀態(tài),而回調(diào)則是維護(hù)了一堆數(shù)據(jù)結(jié)構(gòu)來(lái)處理狀態(tài)。時(shí)代的協(xié)程技術(shù)主要是,另一個(gè)比較小眾。 Coding Crush Python開(kāi)發(fā)工程師 主要負(fù)責(zé)豈安科技業(yè)務(wù)風(fēng)險(xiǎn)情報(bào)系統(tǒng)redq。 引言 1.1. 存儲(chǔ)器山 存儲(chǔ)器山是 Randal Bryant 在《深入...
閱讀 1355·2021-11-11 16:54
閱讀 2398·2021-09-22 10:51
閱讀 2663·2019-08-30 15:44
閱讀 3214·2019-08-29 17:05
閱讀 1459·2019-08-29 17:01
閱讀 2918·2019-08-29 12:28
閱讀 2481·2019-08-26 13:50
閱讀 1738·2019-08-23 16:47