因爲 cpu和 磁盤讀寫的 效率有很大的差距,每每cpu執行代碼,而後遇到須要從磁盤中讀寫文件的操做,此時主線程會中止運行,等待IO操做完成後再繼續進行,這要就致使cpu的利用率很是的低。服務器
協程能夠實現單線程同時執行多個任務,可是須要本身手動的經過send函數和yield關鍵字配合來傳遞消息,asyncio模塊可以自動幫咱們傳遞消息。app
python中協程主要經歷了以下三個階段dom
1)生成器變形 yield/send異步
2)asyncio.coroutine和yield fromsocket
3)async/await關鍵字async
Python中函數若是把return換成了yield,那麼這個函數就再也不普通函數了,而是一個生成器函數
簡單生成器示例:oop
def mygen(alist): # define a generator while alist: c = alist.pop() yield c lst = [1, 2, 3] g = mygen(lst) # get a generator object print(g) # <generator object mygen at 0x0000020225555F10> while True: try: print(next(g)) # 3 2 1 except StopIteration: break
生成器本質上也是迭代器,所以不只可使用next()取值,還可使用for循環取值編碼
for item in g: print(item) # 3 2 1
生成器函數最大的特色是能夠接收一個外部傳入的變量,並根據變量內容計算結果後返回,這個特色是根據send()函數實現的
send()函數使用示例:
def gen(): value = 0 while True: receive = yield value if receive == "Q" or receive == "q": break value = "got:%s" % receive g = gen() print(g.send(None)) # 第一個必須是None,不然會報錯 print(g.send("hello~")) print(g.send(123)) print(g.send([1, 2, 3]))
執行結果
0 got:hello~ got:123 got:[1, 2, 3]
注意:第一個send()裏傳入的變量必須是None,不然會報錯TypeError: can't send non-None value to a just-started generator
這裏最關鍵的一步就是receive = yield value,這一句實際上分爲三步
1)向函數外拋出(返回)value
2)暫停,等待next()或send()恢復
3)將等號右邊的表達式的值(這個值是傳入的)賦值給receive
下面來梳理一下執行流程
1)經過g.send(None)或者next(g)啓動生成器函數,並執行到第一個yield的位置
2)執行yield value,程序返回value,也就是0,以後暫停,等待下一個next()或send(),注意這時並無給receive賦值
3)gen返回value以後跳出,執行主程序裏面的g.send("hello~"),執行這一句會傳入"hello~",從以前暫停的位置繼續執行,也就是賦值給receive,繼續往下執行,value變成"got:hello~",而後判斷while,執行到yield value,返回value,因此打印出"got:hello~",以後進入暫停,等待下一個send()激活
4)後續的g.send(123)執行流程相似,若是傳入"q",gen會執行到break,整個函數執行完畢,會得StopIteration
從上面能夠看出,在第一次send(None)啓動生成器(執行1>2,一般第一次返回的值並無什麼用)以後,對於外部的每一次send(),生成器的實際在循環中的運行順序是3–>1–>2,也就是先獲取值,而後do something,而後返回一個值,再暫停等待。
yield from是Python3.3引入的,先來看一段代碼
def gen1(): yield range(5) def gen2(): yield from range(5) iter1 = gen1() iter2 = gen2() for item in iter1: print(item) for item in iter2: print(item)
執行結果
range(0, 5) 0 1 2 3 4
從上面的示例能夠看出來yield是將range這個可迭代對象直接返回,而yield from解析range對象,將其中每個item返回,yield from本質上等於
for item in iterable: yield item
注意yield from後面只能接可迭代對象
下面來看一個例子,咱們編寫一個斐波那契數列函數
def fab(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a+b n += 1 f = fab(5)
fab不是一個普通函數,而是一個生成器。所以fab(5)並無執行函數,而是返回一個生成器對象,假設要在fab()的基礎上實現一個函數,調用起始都要記錄日誌
def wrapper(func_iter): print("start") for item in func_iter: yield item print("end") wrap = wrapper(fab(5)) for i in wrap: print(i)
下面使用yield from代替for循環
def wrapper(func_iter): print("start") yield from func_iter print("end") wrap = wrapper(fab(5)) for i in wrap: print(i)
yield from在asyncio模塊(python3.4引入)中得以發揚光大。以前都是手動的經過send函數和yield關鍵字配合來傳遞消息,如今當聲明函數爲協程後,咱們經過事件循環來調度協程。
import asyncio, random @asyncio.coroutine # 將一個generator定義爲coroutine def smart_fib(n): i, a, b = 0, 0, 1 while i < n: sleep_time = random.uniform(0, 0.2) yield from asyncio.sleep(sleep_time) # 一般yield from後都是接的耗時操做 print("smart take %s secs to get %s" % (sleep_time, b)) a, b = b, a+b i += 1 @asyncio.coroutine def stupid_fib(n): i, a, b = 0, 0, 1 while i < n: sleep_time = random.uniform(0, 0.5) yield from asyncio.sleep(sleep_time) print("stupid take %s secs to get %s" % (sleep_time, b)) a, b = b, a+b i += 1 if __name__ == '__main__': loop = asyncio.get_event_loop() # 獲取事件循環的引用 tasks = [ # 建立任務列表 smart_fib(10), stupid_fib(10), ] loop.run_until_complete(asyncio.wait(tasks)) # wait會分別把各個協程包裝進一個Task 對象。 print("All fib finished") loop.close()
yield from語法可讓咱們方便地調用另外一個generator。 本例中yield from後面接的asyncio.sleep()也是一個coroutine(裏面也用了yield from),因此線程不會等待asyncio.sleep(),而是直接中斷並執行下一個消息循環。當asyncio.sleep()返回時,線程就能夠從yield from拿到返回值(此處是None),而後接着執行下一行語句。
asyncio是一個基於事件循環的實現異步I/O的模塊。經過yield from,咱們能夠將協程asyncio.sleep的控制權交給事件循環,而後掛起當前協程;以後,由事件循環決定什麼時候喚醒asyncio.sleep,接着向後執行代碼。
協程之間的調度都是由事件循環決定。
yield from asyncio.sleep(sleep_secs) 這裏不能用time.sleep(1)由於time.sleep()返回的是None,它不是iterable,還記得前面說的yield from後面必須跟iterable對象(能夠是生成器,迭代器)。
另外一個示例
import asyncio @asyncio.coroutine def wget(host): print('wget %s...' % host) connect = asyncio.open_connection(host, 80) # 與要獲取數據的網頁創建鏈接 # 鏈接中包含一個 reader和writer reader, writer = yield from connect # 經過writer向服務器發送請求,經過reader讀取服務器repnse回來的請求 header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host # 組裝請求頭信息 writer.write(header.encode('utf-8')) # 須要對請求頭信息進行編碼 yield from writer.drain() # 因爲writer中有緩衝區,若是緩衝區沒滿不且drain的話數據不會發送出去 while True: line = yield from reader.readline() # 返回的數據放在了reader中,經過readline一行一行地讀取數據 if line == b'\r\n': # 由於readline實際上已經把\r\n轉換成換行了,而此時又出現\r\n說明之前有連續兩組\r\n break # 即\r\n\r\n,因此下面就是response body了 print('%s header > %s' % (host, line.decode('utf-8').rstrip())) # Ignore the body, close the socket writer.close() # reader.close() AttributeError: 'StreamReader' object has no attribute 'close' if __name__ == '__main__': loop = asyncio.get_event_loop() tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
弄清楚了asyncio.coroutine和yield from以後,在Python3.5中引入的async和await就不難理解了,咱們使用的時候只須要把@asyncio.coroutine換成async,把yield from換成await就能夠了。固然,從Python設計的角度來講,async/await讓協程表面上獨立於生成器而存在,將細節都隱藏於asyncio模塊之下,語法更清晰明瞭。
加入新的關鍵字 async ,能夠將任何一個普通函數變成協程
一個簡單的示例
import time, asyncio, random async def mygen(alist): while alist: c = alist.pop() print(c) lst = [1, 2, 3] g = mygen(lst) print(g)
執行結果
<coroutine object mygen at 0x00000267723FB3B8> # 協程對象 sys:1: RuntimeWarning: coroutine 'mygen' was never awaited
能夠看到,咱們在前面加上async,該函數就變成了一個協程,可是async對生成器是無效的
async def mygen(alist): while alist: c = alist.pop() yield c lst = [1, 2, 3] g = mygen(lst) print(g)
執行結果
<async_generator object mygen at 0x000001540EF505F8> # 並非協程對象
因此正常的協程是這樣的
import time, asyncio, random async def mygen(alist): while alist: c = alist.pop() print(c) await asyncio.sleep(1) lst1 = [1, 2, 3] lst2 = ["a", "b", "c"] g1 = mygen(lst1) g2 = mygen(lst2)
要運行協程,要用事件循環
在上面的代碼下面加上:
if __name__ == '__main__': loop = asyncio.get_event_loop() tasks = [ c1, c2 ] loop.run_until_complete(asyncio.wait(tasks)) print("all finished") loop.close()
參考:
1)https://blog.csdn.net/soonfly/article/details/78361819
2)https://blog.csdn.net/weixin_40247263/article/details/82728437