❶ python協程gevent怎麼用
在學習gevent之前,你肯定要知道你學的這個東西是什麼。
官方描述gevent
gevent is a coroutine-based Python networking library that uses greenlet to provide a high-level synchronous API on top of the libev event loop.
翻譯:gevent是一個基於協程的Python網路庫。我們先理解這句,也是這次學習的重點——協程。
wiki描述協程
與子常式一樣,協程也是一種程序組件。相對子常式而言,協程更為一般和靈活,但在實踐中使用沒有子常式那樣廣泛。子常式的起始處是惟一的入口點,一旦退出即完成了子常式的執行,子常式的一個實例只會返回一次;協程可以通過yield來調用其它協程。通過yield方式轉移執行權的協程之間不是調用者與被調用者的關系,而是彼此對稱、平等的。協程允許多個入口點,可以在指定位置掛起和恢復執行。
沒看懂?沒關系,我也沒看懂,不過算是有點線索:子常式。
子常式
過程有兩種,一種叫子常式(Subroutine),通常叫Sub;另一種叫函數(Function)。底層實現機制是一樣的,區別在於,Sub只執行操作,沒有返回值;Function不但執行操作,並且有返回值。用過VB的應該會比較清楚這點。(原諒我用了網路)說到底子常式就是過程,我們一般叫它函數。
說到函數,我就想吐槽了,不明白為什麼要叫函數。很多時候我們寫一個函數是為了封裝、模塊化某個功能,它是一個功能、或者說是一個過程。因為它包含的是類似於流程圖那樣的具體邏輯,先怎樣做,然後怎樣做;如果遇到A情況則怎樣,如果遇到B情況又怎樣。個人覺得還是叫過程比較好,叫做函數就讓人很糾結了,難道因為回歸到底層還是計算問題,出於數學的角度把它稱為函數?這個略坑啊!為了符合大家的口味,我還是稱之為函數好了(其實我也習慣叫函數了%>_
講到函數,我們就往底層深入一點,看看下面的代碼:
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def a():
print "a start"
b()
print "a end"
def b():
print "b start"
c()
print "b end"
def c():
print "c start"
print "c end"
if __name__ == "__main__":
a()
a start
b start
c start
c end
b end
a end
對於這樣的結果大家肯定不會意外的。每當函數被調用,就會在棧中開辟一個棧空間,調用結束後再回收該空間。
假設一個這樣的場景:有個講台,每個人都可以上去發表言論,但是每次講台只能站一個人。現在a在上面演講,當他說到「大家好!」的時候,b有個緊急通知要告訴大家,所以a就先下來讓b講完通知,然後a再上講台繼續演講。如果用函數的思想模擬這個問題,堆棧示意圖是這樣的:
那什麼東西有這樣的能力呢?我們很快就可以想到進程、線程,但是你真的想使用進程、線程如此重量級的東西在這么簡單的程序上嗎?野蠻的搶占式機制和笨重的上下文切換!
還有一種程序組件,那就是協程。它能保留上一次調用時的狀態,每次重新進入該過程的時候,就相當於回到上一次離開時所處邏輯流的位置。協程的起始處是第一個入口點,在協程里,返回點之後是接下來的入口點。協程的生命期完全由他們的使用的需要決定。每個協程在用yield命令向另一個協程交出控制時都盡可能做了更多的工作,放棄控制使得另一個協程從這個協程停止的地方開始,接下來的每次協程被調用時,都是從協程返回(或yield)的位置接著執行。
從上面這些你就可以知道其實協程是模擬了多線程(或多進程)的操作,多線程在切換的時候都會有一個上下文切換,在退出的時候將現場保存起來,等到下一次進入的時候從保存的現場開始,繼續執行。
看下協程是怎樣實現的:
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import random
from time import sleep
from greenlet import greenlet
from Queue import Queue
queue = Queue(1)
@greenlet
def procer():
chars = ['a', 'b', 'c', 'd', 'e']
global queue
while True:
char = random.choice(chars)
queue.put(char)
print "Proced: ", char
sleep(1)
consumer.switch()
@greenlet
def consumer():
global queue
while True:
char = queue.get()
print "Consumed: ", char
sleep(1)
procer.switch()
if __name__ == "__main__":
procer.run()
consumer.run()
應用場景
我們一直都在大談協程是什麼樣一個東西,卻從沒有提起協程用來幹嘛,這個其實大家分析一下就能夠知道。從上面的生產者——消費者問題應該能看出,它分別有兩個任務,假設交給兩個人去執行,但每次只能允許一個人行動。當緩沖區滿的時候,生產者是出於等待狀態的,這個時候可以將執行任務的權利轉交給消費者,當緩沖區空得時候,消費者是出於等待狀態的,這個時候可以將執行任務的權利轉交給生產者,是不是很容易聯想到多任務切換?然後想到線程?最後想到高並發?
但同學們又會問,既然有了線程為什麼還要協程呢?因為線程是系統級別的,在做切換的時候消耗是特別大的,具體為什麼這么大等我研究好了再告訴你;同時線程的切換是由CPU決定的,可能你剛好執行到一個地方的時候就要被迫終止,這個時候你需要用各種措施來保證你的數據不出錯,所以線程對於數據安全的操作是比較復雜的。而協程是用戶級別的切換,且切換是由自己控制,不受外力終止。
總結
協程其實模擬了人類活動的一種過程。例如:你准備先寫文檔,然後修復bug。這時候接到電話說這個bug很嚴重,必須立即修復(可以看作CPU通知)。於是你暫停寫文檔,開始去填坑,終於你把坑填完了,你回來寫文檔,這個時候你肯定是接著之前寫的文檔繼續,難道你要把之前寫的給刪了,重新寫?這就是協程。那如果是子常式呢?那你就必須重新寫了,因為退出之後,棧幀就會被彈出銷毀,再次調用就是開辟新的棧空間了。
總結:協程就是用戶態下的線程,是人們在有了進程、線程之後仍覺得效率不夠,而追求的又一種高並發解決方案。為什麼說是用戶態,是因為操作系統並不知道它的存在,它是由程序員自己控制、互相協作的讓出控制權而不是像進程、線程那樣由操作系統調度決定是否讓出控制權。
❷ python協程為什麼不需要枷鎖
Python是一門動態的腳本語言,它有一個鎖叫做全局解釋器鎖,它這個鎖是加在cpython解釋器上的,我們說的Python多線程,再線程切換的時候加了鎖,用了控制同步。所以多線程不是真正意義的並發,而協程是在線程裡面的,線程並沒有鎖,一個線程可以有多個協程,協程又叫微線程,它的切換完全由自己創建,它有幾種實現方式,一種是yield和send,一種是gevent,一種是greenlet,線程的並發不好,協程可以有上萬次並發。回到之前的問題,因為協程在線程內,而線程本身沒有鎖,所以攜程沒有鎖。
❸ python中的協程是怎麼實現多任務的
協程也稱為微線程,是在一個線程中,通過不斷的切換任務函數實現了多任務的效果。
協程在python實現的原理主要是通過yield這個關鍵字實現
但是真正在開發時,可以不需要自己實現,可以通過很多成熟的第三方模塊來實現協程,比如greenlet,gevent等模塊。黑馬程序員可學習Python哦,有免費的學習視頻,學習路線圖,學習工具!
❹ python里怎麼實現多個協程一起執行,只要完成
importasyncio
asyncdefphase(i):
print('inphase{}'.format(i))
awaitasyncio.sleep(0.5-(0.1*i))
print('donewithphase{}'.format(i))
return'phase{}result'.format(i)
asyncdefmain(num_phases):
print('startingmain')
phases=[
phase(i)
foriinrange(num_phases)
]
print('waitingforphasestocomplete')
results=[]
fornext_to_completeinasyncio.as_completed(phases):
answer=awaitnext_to_complete
print('receivedanswer{!r}'.format(answer))
results.append(answer)
print('results:{!r}'.format(results))
returnresults
event_loop=asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(3))
finally:
event_loop.close()
❺ 協程不會你怎麼會Python
這個問題我要一本正經的告訴你,協程是直到python3.4才被引入python中,在python3.7中用再次被強化了一些功能。那麼python2.7開發的人沒接觸也是正常,但語言共通性強,學一個新概念,也許一小時就夠了。
❻ python 中的協程是怎麼實現多任務的
協程也稱為微線程,是在一個線程中,通過不斷的切換任務函數實現了多任務的效果。
協程在python實現的原理主要是通過yield這個關鍵字實現
但是真正在開發時,可以不需要自己實現,可以通過很多成熟的第三方模塊來實現協程,比如greenlet,gevent等模塊。多線程的課程我記得是在黑馬程序員裡面找的,一套,還有資料。
❼ Python 3.5 協程究竟是個啥
用 async def 可以定義得到 協程 。定義協程的另一種方式是通過 types.coroutine 修飾器 -- 從技術實現的角度來說就是添加了 CO_ITERABLE_COROUTINE 標記 -- 或者是 collections.abc.Coroutine 的子類。你只能通過基於生成器的定義來實現協程的暫停。
awaitable 對象 要麼是一個協程要麼是一個定義了 __await__() 方法的對象 -- 也就是 collections.abc.Awaitable -- 且 __await__() 必須返回一個不是協程的迭代器。 await 表達式基本上與 yield from 相同但只能接受awaitable對象(普通迭代器不行)。 async 定義的函數要麼包含 return 語句 -- 包括所有Pytohn函數預設的 return None -- 和/或者 await 表達式( yield 表達式不行)。 async 函數的限制確保你不會將基於生成器的協程與普通的生成器混合使用,因為對這兩種生成器的期望是非常不同的。
❽ 如何用python寫一個協程
作者:LittleCoder
鏈接:https://www.hu.com/question/54483694/answer/139785021
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請註明出處。
yield`和`yield from`的區別
`yield`題主肯定不陌生,而`yield from`是PEP 380中新增的一個特性。
PEP 380的名字是嵌套子迭代器的語法糖(我喜歡這么翻譯,原文是:Syntax for Delegating to a Subgenerator)。
既然是語法糖,那麼肯定本來是有別的寫法的,這里給出本來的寫法:
def subgen():
for i in range(3):
yield 'subgen: %s' % i
return 'subgen returned'def gen():
r = yield from subgen()
print('r = "%s"' % r)
yield rdef gen_without_yield_from():
sg = subgen()
try:
while 1:
yield sg.send(None)
except StopIteration as e:
yield e.valueprint('* [gen] get all values')for v in gen_without_yield_from():
print('get value: %s' % v)print('* [gen_without_yield_from] get all values')for v in gen_without_yield_from():
print('get value: %s' % v)
不難看出,`yield`子迭代器是把子迭代器直接傳遞出去,`yield from`子迭代器是把子迭代器的值一個一個傳出去。
雖然實際把子迭代器當做一個對象直接傳遞出去也沒有問題,也有使用場景(生成迭代器的迭代器)。
但在協程中相較於這個令人愉快的語法糖而言,直接傳遞就顯得沒有必要且礙事了。
畢竟我希望使用一個子迭代器是把子迭代器中的代碼都運行一遍而不是直接就把這個子迭代器傳出來讓我自己操作。
所以如果你把子迭代器直接傳了出去,asyncio就判斷你在做一件奇怪的事情並報了錯。
那麼,回到問題,給出的程序要怎麼通過`yield`調用呢?
# 源程序@asyncio.coroutinedef hello():
print("Hello world!")
yield from asyncio.sleep(1)
print("Hello again!")# 使用[email protected] hello():
print("Hello world!")
for v in asyncio.sleep(1):
yield v
print("Hello again!")
協程和迭代器的區別
舉個比喻,迭代器和協程就像火葯和槍械,利用火葯的特性輔助各種其他東西才造出了槍械。
迭代器就最簡單的本質而言就是一個可以暫停的程序。
那麼就有這樣一個合理的聯想,我是不是可以節省下所有不必要的例如等待網站響應的等待時間。
就是我把我的請求發過去以後就把這個程序暫停下來,開啟別的程序,等到響應來了再叫我回到這個程序。
那麼等待網站響應的時間也就完全沒有浪費了,比原來傻傻的等著網站響應真是優秀了許多。
這就是協程。
所以,為什麼看上去都是`generator`,迭代器不會天生成為協程呢?
因為沒有一個知道什麼時候應該叫你回到這個程序的人。
這個人就是`event_loop`(消息循環)。
回到問題,協程是否可以脫離`event_loop`(消息循環)調用。
講道理是不可以的,但合理聯想一下是不是一直不停的告訴程序又到你了就行了。
像這樣:
@asyncio.coroutinedef gen():
for i in range(3):
yield ifor i in gen():
print(i)print('end')
的確有些協程這樣是可以運行的(這些協程為什麼要寫成協程?)。
但終究你是在不應該告訴程序到你的時候告訴了他這件事情。
所以顯然獲取數據的話當時數據根本沒有傳到,`sleep`的話就根本沒有了`sleep`的效果。
只是看上去能夠運行,實際完全沒有用。
asyncio還為此特地加了一個斷言,如果你這樣調用`asyncio.sleep`,asyncio會發現你在偽裝消息循環騙他。
協程的原理
這是另一個看上去能夠運行,實際上完全沒有用的事情。
這雖然不是你想問的問題,但你已經碰到了也遲早會意識到,所以一並講了。
這個問題應該是這樣的:為什麼我寫出來的協程完全沒有協程的效果?
import time, [email protected] sleep(symbol, i):
time.sleep(i)
print('[%s] finished')loop = asyncio.get_event_loop()tasks = [sleep('A', 2), sleep('B', 2)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
看到這里你起碼可以簡單的講出來,因為顯然我們在傻傻的等。
我們沒有在開始等待的時候把程序暫停下來,然後在等待結束後繼續運行程序,我們一心一意的在等。
我們真的`time.sleep`了兩秒,而不是去做了兩秒其他的事情。
你有各種選擇,可以花式等待。我這里給你兩個最基本的例子:
* get請求
* 同步變為協程(線程池)
get請求
為了讓你更好的了解asyncio,我從最底層的socket開始寫一個協程的get請求給你。
為了模擬延時很大的網站,我在本地開了一個延時伺服器,這是伺服器程序。
import tornado.ioloopimport tornado.webfrom tornado.gen import coroutine, sleepclass MainHandler(tornado.web.RequestHandler):
@coroutine
def get(self, waitTime=3):
yield sleep(int(waitTime))
self.write('you have waited for %ss' % waitTime)if __name__ == "__main__":
application = tornado.web.Application([
('/([0-9])', MainHandler),
], debug=True)
application.listen(5000)
try:
tornado.ioloop.IOLoop.current().start()
except:
tornado.ioloop.IOLoop.current().stop()
記得打開了這個伺服器再運行下面的程序。
import socket, asyncio, timedata = 'GET /%s HTTP/1.1\r\n\r\n'loop = asyncio.get_event_loop()@asyncio.coroutinedef get(i):
future = asyncio.futures.Future(loop=loop)
s = socket.socket()
s.connect(('127.0.0.1', 5000))
s.sendall((data % i).encode('utf8'))
s.setblocking(False)
def callback(future):
future.set_result(s.recv(999).split(b'\r\n\r\n')[-1])
loop.add_reader(s.fileno(), callback, future)
r = yield from future
print('Return value: %s' % r)tasks = [get(3), get(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
同步變為協程(線程池)
這里拿sleep模擬耗時的程序,原理就是開了5個新的線程處理耗時程序。
當然實際的`asyncio.sleep`只需要告訴消息循環一定時間後叫醒我就好了。
import asyncio, sleep, [email protected] sleep(i):
executor = concurrent.futures.ThreadPoolExecutor(5)
future = asyncio.futures.wrap_future(executor.submit(time.sleep, i), loop=loop)
yield from future
print('Slept for %s seconds' % i)tasks = [sleep(3), sleep(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
❾ 在python中線程和協程的區別是什麼
在python中線程和協程的區別:1、一個線程可以擁有多個協程,這樣在python中就能使用多核CPU;2、線程是同步機制,而協程是非同步;3、 協程能保留上一次調用時的狀態,每次過程重入時,就相當於進入上一次調用的狀態。
一、首先我們來了解一下線程和協程的概念
1、線程
線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。
2、協程
協程是一種用戶態的輕量級線程,協程的調度完全由用戶控制。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變數,所以上下文的切換非常快。
二、協程與線程的比較
1) 一個線程可以擁有多個協程,一個進程也可以單獨擁有多個協程,這樣python中則能使用多核CPU。
2) 線程進程都是同步機制,而協程則是非同步。
3)協程能保留上一次調用時的狀態,每次過程重入時,就相當於進入上一次調用的狀態。
三、線程、協程在python中的使用
1、多線程一般是使用threading庫,完成一些IO密集型並發操作。多線程的優勢是切換快,資源消耗低,但一個線程掛掉則會影響到所有線程,所以不夠穩定。現實中使用線程池的場景會比較多,具體可參考《python線程池實現》。
2、協程一般是使用gevent庫,當然這個庫用起來比較麻煩,所以使用的並不是很多。相反,協程在tornado的運用就多得多了,使用協程讓tornado做到單線程非同步,據說還能解決C10K的問題。所以協程使用的地方最多的是在web應用上。
總結一下:
IO密集型一般使用多線程或者多進程,CPU密集型一般使用多進程,強調非阻塞非同步並發的一般都是使用協程,當然有時候也是需要多進程線程池結合的,或者是其他組合方式。
推薦課程:Python高級進階視頻教程