1、理解概念
asyncio 是用來編寫並發代碼的庫,使用 async/await 語法。
(1)何為並發:
並發就是在一個時間段內,同時做多個事情。
比如在單CPU的機器中(只有一個CPU的機器),我們可以一邊聽歌,一邊斗地主,一邊聊QQ。
在我們看來,我們是同時在做這三件事,可是在單CPU中,這並不是同時進行的。
單CPU把自己分成了一段一段的時間片,每個時間片用來做一個事情。
如果該時間片用光了,但是事件還沒有做完,那就先不做了。CPU先去做其他的,做完其他的事情,再利用新的時間片,來接着做這個事情。
因為時間片之間的切換速度是很快的,用戶根本體驗不出來,所以就感覺是同時在做這些事情。
以上就可以理解為並發。
(2)何為並行
基本上提到了並發,總有人要問什么是並行,以及並行與並發的區別。
並行就是同一時間點,同時做多個事情。聽起來和並發差不多,但是還是有區別的,看完下面的解釋就容易理解了。
並行在單CPU中是不可能存在的,因為並行是真真實實存在的。而並發是宏觀上存在的,
比如在多CPU的機器中(有多個CPU的機器,與單CPU對立),我們可以一邊聽歌,一邊斗地主,一邊聊QQ。
在我們看來,我們是同時在做這三件事,在多CPU中,其實也是同時在做這三個事情
一個CPU負責聽歌的進程,一個CPU負責斗地主的進程,一個CPU負責QQ的進程。
在物理時間上,CPU真的就是在同時做這三個事情,只是每個事件用的CPU不同而已。
以上可以理解為並行。
備注:我這里所說的多CPU,一個CPU負責一個進程,其實也可以理解為一個CPU有多個核,一個核負責一個進程,這樣也是說的通的。
因為我對多CPU以及多核CPU還沒有理解清楚,所以上面的舉例就用了多CPU。本文主要是記錄自己的理解,所以難免有不懂的地方。請包涵。
(3)並發與並行的共同點與區別
共同點:
我們可以理解並發與並行都是同一個時間,做多個事情,執行多個程序。
不同點:
並發是在宏觀上存在的,是在邏輯上存在的,理解了(1),應該就能理解這句話了。這也是為什么用單CPU舉例的意義所在。
並行是在物理上存在的,是真實存在的。理解了(2),應該就能理解這句話了。
單核CPU是不可能存在並行的。
單CPU中進程只能是並發,多CPU計算機中進程可以並行。
單CPU單核中線程只能並發,單CPU多核中線程可以並行。
(4)並發與並行學習鏈接
https://cloud.tencent.com/developer/article/1424249 圖形並茂的,助於理解!
https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/ 義正言辭的!
https://sunweiguo.github.io/2019/11/17/miscellany/20%E5%85%B3%E4%BA%8E%E5%A4%9ACPU%E5%92%8C%E5%A4%9A%E6%A0%B8CPU%E7%9A%84%E5%8C%BA%E5%88%AB/ CPU知識點!
(5)協程理解
一般實現並發的方式就是使用多進程,多線程。這里加入一個協程概念,協程也可以實現並發。
asyncio就是通過協程,實現的並發。與本文第一句相互呼應,嘿嘿!
我們編寫程序,正常執行是串行的,即從上而下依次執行。
比如定義一個函數,執行函數就是從函數的第一行,依次執行第二行,再執行第三行......最后執行最后一行,然后結束函數的執行。
協程就是執行一個函數,到某一行,然后轉去執行其他函數,執行其他函數到某一行,又去執行了其他函數,最后在合適的時間點,又轉回來執行最初的函數
在合適的時間點,指的是其他函數運行結束,或者其他函數主動釋放CPU。
比如某個函數中有sleep操作,這個時間,等待睡眠是浪費CPU的,這個時候可以跳出該函數,轉去執行其他函數,當其他函數執行完,再接着執行這個sleep的后一句
以上就是個人理解協程,比較空洞,結合下面的代碼,就能容易理解一點了。
2、使用async關鍵字定義協程
def function(name): print('這是正常的函數,name:{}'.format(name)) async def coroutines_function(name): print('這是協程函數,name:{}'.format(name)) if __name__ == '__main__': function('hello') # 正常執行 coroutines_function('hello') # 報錯 coroutine 'coroutines_function' was never awaited
3、通過asyncio運行協程(開始了asycnio的使用)
協程對象不能直接運行,需要添加到事件循環中,在合適的時間點,事件循環自動運行
合適的時間點指的是CPU分配時間片給他,其他程序不占用CPU
import asyncio # 導入異步io庫 async def coroutines_function(name): print('這是協程函數,name:{}'.format(name)) if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回協程對象 print(type(obj)) # <class 'coroutine'> loop = asyncio.get_event_loop() # 獲取事件循環 loop.run_until_complete(obj) # 注冊到事件循環中 loop.close() # 關閉事件循環
運行
4、創建task對象
上面3中的示例代碼 loop.run_until_complete(obj)
傳入的參數是一個協程對象,傳入后協程對象會被包裝成task對象,這樣運行的就是task對象,而不是協程對象
可以這么寫,創建一個task對象,然后傳給run_until_complete函數
import asyncio # 導入異步io庫 async def coroutines_function(name): print('這是協程函數,name:{}'.format(name)) if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回協程對象 print(type(obj)) # <class 'coroutine'> loop = asyncio.get_event_loop() # 獲取事件循環 task = loop.create_task(obj) # 創建task print(type(task)) # <class '_asyncio.Task'> loop.run_until_complete(task) # 注冊到事件循環中 loop.close() # 關閉事件循環
運行
5、task對象的狀態
task對象被創建出來時,不會被執行,這時候狀態是pending
之后被添加到事件循環中,等待執行
執行結束后狀態為finished
import asyncio # 導入異步io庫 async def coroutines_function(name): print('這是協程函數,name:{}'.format(name)) if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回協程對象 print(type(obj)) # <class 'coroutine'> loop = asyncio.get_event_loop() # 獲取事件循環 task = loop.create_task(obj) # 創建task print(type(task)) # <class '_asyncio.Task'> print(task) # <Task pending coro=<coroutines_function() loop.run_until_complete(task) # 注冊到事件循環中 print(task) # <Task finished coro=<coroutines_function() done loop.close() # 關閉事件循環
運行
6、回調函數
可以為task對象添加一個回調函數,執行完task后,自動執行綁定的回調函數
import asyncio # 導入異步io庫 async def coroutines_function(name): print('這是協程函數,name:{}'.format(name)) def callback(function): print('協程函數本身:{}'.format(function)) print('回調函數完成 ---> coroutines_function') if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回協程對象 loop = asyncio.get_event_loop() # 獲取事件循環 task = loop.create_task(obj) # 創建task task.add_done_callback(callback) loop.run_until_complete(task) # 注冊到事件循環中 loop.close() # 關閉事件循環
運行
7、獲取協程函數的返回值
只有當協程函數執行完畢,狀態為finished時,才可以獲取返回值,提前獲取報錯
import asyncio # 導入異步io庫 async def coroutines_function(name): print('這是協程函數,name:{}'.format(name)) return 'coroutines_function-->{}'.format(name) def callback(function): print('協程函數本身:{}'.format(function)) print('回調函數完成 ---> coroutines_function') if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回協程對象 loop = asyncio.get_event_loop() # 獲取事件循環 task = loop.create_task(obj) # 創建task # print(task.result()) # 報錯 Result is not set. task.add_done_callback(callback) loop.run_until_complete(task) # 注冊到事件循環中 print('task result:{}'.format(task.result())) # coroutines_function-->墨玉麒麟 loop.close() # 關閉事件循環
運行
8、await讓出CPU控制器
以上的例子都不能演示並發的情景,因為以上的例子沒有用到await關鍵字
我們可以通過使用await關鍵字,來讓出CPU的控制器,去執行其他函數的語句,從而實現並發機制
當事件循環看到await關鍵字時,事件循環會掛起該協程,從而去執行其他協程。其他協程掛起時,或者其他協程執行完畢,事件循環再繼續執行最初的協程
(1)舉個睡眠的例子
import asyncio # 導入異步io庫 import time async def sleep_second(second): print('將要睡眠{}秒'.format(second)) await asyncio.sleep(second) print('睡眠{}秒完成,醒過來了'.format(second)) return '休息了{}秒'.format(second) if __name__ == '__main__': start = time.time() obj1 = sleep_second(5) # 返回協程對象 obj3 = sleep_second(3) # 返回協程對象 obj5 = sleep_second(1) # 返回協程對象 loop = asyncio.get_event_loop() # 獲取事件循環 task1 = loop.create_task(obj1) # 創建task task3 = loop.create_task(obj3) # 創建task task5 = loop.create_task(obj5) # 創建task loop.run_until_complete(task1) # 注冊到事件循環中 loop.run_until_complete(task3) # 注冊到事件循環中 loop.run_until_complete(task5) # 注冊到事件循環中 loop.close() # 關閉事件循環 end = time.time() print('耗時:{}'.format(end - start))
運行
可以看出,當運行到睡眠5秒的時候,程序並沒有等待,而是去運行睡眠3秒,再去運行睡眠1秒,最后1秒先執行完畢,先返回,再是睡眠3秒返回,再是睡眠5秒返回
用時是5秒,而不是5 + 3 + 1 = 9 秒
備注 await后面需要跟協程函數,而不能是普通函數,這里不能用time.sleep()代替asyncio.sleep(),用了會報錯,可以自己換了嘗試一下看看
(2)演示協程嵌套
import asyncio # 導入異步io庫 import time async def fun1(name): print('fun1 name begin:{}'.format(name)) print('fun1 name end:{}'.format(name)) async def fun2(name): print('fun2 name begin:{}'.format(name)) await fun1('墨玉麒麟') print('fun2 name end:{}'.format(name)) async def fun3(name): print('fun3 name begin:{}'.format(name)) await fun2('張龍趙虎') print('fun3 name end:{}'.format(name)) if __name__ == '__main__': start = time.time() obj3 = fun3('神龍吸水') # 返回協程對象 loop = asyncio.get_event_loop() # 獲取事件循環 task3 = loop.create_task(obj3) # 創建task loop.run_until_complete(task3) # 注冊到事件循環中 loop.close() # 關閉事件循環 end = time.time() print('耗時:{}'.format(end - start))
運行
備注:實際項目中,一般asyncio.sleep()都是用其他協程的庫來代替,比如aiohttp、 aiomysq等
aiohttp 的簡單使用可以參考我的另一篇隨筆 https://www.cnblogs.com/rainbow-tan/p/15130054.html
學習鏈接: https://www.jianshu.com/p/b5e347b3a17c