22. 协程与Python中的多任务异步协程
目錄
前言
協程概念
示例代碼
Python編寫協程程序
要用到的庫函數
嘗試編寫異步
嘗試改進函數
嘗試優化代碼
在爬蟲領域的應用?
總結
前言
本節我們介紹一個新概念:協程。協程顧名思義,是協助執行程序的過程。我們將介紹協程的概念和其在Python中的應用。
協程概念
百度百科(協程):
協程不是進程或線程,其執行過程更類似于子例程,或者說不帶返回值的函數調用。
一個程序可以包含多個協程,可以對比與一個進程包含多個線程,因而下面我們來比較協程和線程。我們知道多個線程相對獨立,有自己的上下文,切換受系統控制;而協程也相對獨立,有自己的上下文,但是其切換由自己控制,由當前協程切換到其他協程由當前協程來控制。
注意,協程是應用程序內部切換上下文,不會進入內核進行線程上下文切換。
協程主要用于某一進程CPU調用睡眠時或者進行I/O操作時,選擇性地切換到其他任務,從而提高效率。在微觀上是一個任務一個任務的進行切換,切換條件一般就是IO操作。在宏觀上,我們能看到的其實是多個任務一起在執行(多任務異步操作)
示例代碼
import timedef func():print("我愛黎明")time.sleep(3) # 讓當前的線程處于阻塞狀態. CPU是不為我工作的print("我真的愛黎明")if __name__ == '__main__':func()""" # input() 程序也是處于阻塞狀態 # requests.get(bilibili) 在網絡請求返回數據之前, 程序也是處于阻塞狀態的 # 一般情況下, 當程序處于 IO操作的時候. 線程都會處于阻塞狀態# 協程: 當程序遇見了IO操作的時候. 可以選擇性的切換到其他任務上. # 在微觀上是一個任務一個任務的進行切換. 切換條件一般就是IO操作 # 在宏觀上,我們能看到的其實是多個任務一起在執行 # 多任務異步操作# 上方所講的一切. 都是在單線程的條件下 """Python編寫協程程序
要用到的庫函數
# python編寫協程的程序 import asyncioasync def func():print("你好啊, 我叫賽利亞")if __name__ == '__main__':g = func() # 此時的函數是異步協程函數. 此時函數執行得到的是一個協程對象# print(g)asyncio.run(g) # 協程程序運行需要asyncio模塊的支持要編寫異步程序,要用到asyncio這個庫,它是Python自帶的,直接導入就可以。我們寫一個異步函數,它不能在主函數中直接運行,會拋出Error,因為它直接運行返回的是一個協程對象。我們必須用asyncio的run函數才能運行。
嘗試編寫異步
import asyncio import timeasync def func1():print("你好啊, 我叫李誕")time.sleep(3) # 當程序出現了同步操作的時候. 異步就中斷了print("你好啊, 我叫李誕")async def func2():print("你好啊, 我叫王建國")time.sleep(2)print("你好啊, 我叫王建國")async def func3():print("你好啊, 我叫李雪琴")time.sleep(4)print("你好啊, 我叫李雪琴")if __name__ == '__main__':f1 = func1()f2 = func2()f3 = func3()tasks = [f1, f2, f3]t1 = time.time()# 一次性啟動多個任務(協程)asyncio.run(asyncio.wait(tasks)) # 固定搭配t2 = time.time()print(t2 - t1)我們想一次性執行多個異步任務時,需要把它們放在列表中,并且用asyncio的run函數中嵌套wait函數才能實現,它是固定搭配,可以套公式。
打印程序執行時間,發現此時執行時間和串行執行的速度差不多——也是9秒多
(打印數據時間+3+2+4)秒。
問題出在time.sleep()。當異步函數中出現同步操作時,異步就中斷了,所以還是在等待睡眠時間中CPU什么都沒有執行。
嘗試改進函數
import asyncio import timeasync def func1():print("你好啊, 我叫李誕")# time.sleep(3) # 當程序出現了同步操作的時候. 異步就中斷了await asyncio.sleep(3) # 異步操作的代碼print("你好啊, 我叫李誕")async def func2():print("你好啊, 我叫王建國")# time.sleep(2)await asyncio.sleep(2)print("你好啊, 我叫王建國")async def func3():print("你好啊, 我叫李雪琴")# time.sleep(4)await asyncio.sleep(4)print("你好啊, 我叫李雪琴")if __name__ == '__main__':f1 = func1()f2 = func2()f3 = func3()tasks = [f1, f2, f3]t1 = time.time()# 一次性啟動多個任務(協程)asyncio.run(asyncio.wait(tasks))t2 = time.time()print(t2 - t1)我們將睡眠操作改為異步,嘗試執行,打印執行時間為4秒多(最長的睡眠時間+調度時間)
但我們這樣并不是最理想化的代碼,我們將其進行優化:
嘗試優化代碼
import time import asyncioasync def func1():print("你好啊, 我叫李誕")await asyncio.sleep(3)print("你好啊, 我叫李誕")async def func2():print("你好啊, 我叫王建國")await asyncio.sleep(2)print("你好啊, 我叫王建國")async def func3():print("你好啊, 我叫李雪琴")await asyncio.sleep(4)print("你好啊, 我叫李雪琴")async def main():# 第一種寫法# f1 = func1()# await f1 # 一般await掛起操作放在協程對象前面# 第二種寫法(推薦)tasks = [asyncio.create_task(func1()), # py3.8以后加上asyncio.create_task()asyncio.create_task(func2()),asyncio.create_task(func3())]await asyncio.wait(tasks)if __name__ == '__main__':t1 = time.time()# 一次性啟動多個任務(協程)asyncio.run(main())t2 = time.time()print(t2 - t1)這里還是推薦把函數放在tasks列表中,然后在main異步函數中異步執行異步函數列表,然后在主程序中調用main異步函數。我們依舊輸出運行時間,查看是否成功異步運行:
可以看到是沒問題的。
在爬蟲領域的應用?
import asyncio# 在爬蟲領域的應用 async def download(url):print("準備開始下載")await asyncio.sleep(2) # 網絡請求 requests.get()print("下載完成")async def main():urls = ["http://www.baidu.com","http://www.bilibili.com","http://www.163.com"]# 準備異步協程對象列表tasks = []for url in urls:d = asyncio.create_task(download(url))tasks.append(d)# tasks = [asyncio.create_task(download(url)) for url in urls] # 這么干也行哦~# 一次性把所有任務都執行await asyncio.wait(tasks)if __name__ == '__main__':asyncio.run(main())這里用睡眠代替了網絡請求操作,相當于一個模板,以后要批量請求網頁的時候可以套用。
運行結果:
完整代碼
調試請自行修改注釋部分
# import time # # # def func(): # print("我愛黎明") # time.sleep(3) # 讓當前的線程處于阻塞狀態. CPU是不為我工作的 # print("我真的愛黎明") # # # if __name__ == '__main__': # func() # # """ # # input() 程序也是處于阻塞狀態 # # requests.get(bilibili) 在網絡請求返回數據之前, 程序也是處于阻塞狀態的 # # 一般情況下, 當程序處于 IO操作的時候. 線程都會處于阻塞狀態 # # # 協程: 當程序遇見了IO操作的時候. 可以選擇性的切換到其他任務上. # # 在微觀上是一個任務一個任務的進行切換. 切換條件一般就是IO操作 # # 在宏觀上,我們能看到的其實是多個任務一起在執行 # # 多任務異步操作 # # # 上方所講的一切. 都是在單線程的條件下 # """# python編寫協程的程序 import asyncio import time# async def func(): # print("你好啊, 我叫賽利亞") # # # if __name__ == '__main__': # g = func() # 此時的函數是異步協程函數. 此時函數執行得到的是一個協程對象 # # print(g) # asyncio.run(g) # 協程程序運行需要asyncio模塊的支持# async def func1(): # print("你好啊, 我叫李誕") # # time.sleep(3) # 當程序出現了同步操作的時候. 異步就中斷了 # await asyncio.sleep(3) # 異步操作的代碼 # print("你好啊, 我叫李誕") # # # async def func2(): # print("你好啊, 我叫王建國") # # time.sleep(2) # await asyncio.sleep(2) # print("你好啊, 我叫王建國") # # # async def func3(): # print("你好啊, 我叫李雪琴") # await asyncio.sleep(4) # print("你好啊, 我叫李雪琴") # # # if __name__ == '__main__': # f1 = func1() # f2 = func2() # f3 = func3() # tasks = [ # f1, f2, f3 # ] # t1 = time.time() # # 一次性啟動多個任務(協程) # asyncio.run(asyncio.wait(tasks)) # t2 = time.time() # print(t2 - t1)# async def func1(): # print("你好啊, 我叫李誕") # await asyncio.sleep(3) # print("你好啊, 我叫李誕") # # # async def func2(): # print("你好啊, 我叫王建國") # await asyncio.sleep(2) # print("你好啊, 我叫王建國") # # # async def func3(): # print("你好啊, 我叫李雪琴") # await asyncio.sleep(4) # print("你好啊, 我叫李雪琴") # # # async def main(): # # 第一種寫法 # # f1 = func1() # # await f1 # 一般await掛起操作放在協程對象前面 # # 第二種寫法(推薦) # tasks = [ # asyncio.create_task(func1()), # py3.8以后加上asyncio.create_task() # asyncio.create_task(func2()), # asyncio.create_task(func3()) # ] # await asyncio.wait(tasks) # # # if __name__ == '__main__': # t1 = time.time() # # 一次性啟動多個任務(協程) # asyncio.run(main()) # t2 = time.time() # print(t2 - t1)# 在爬蟲領域的應用 async def download(url):print("準備開始下載")await asyncio.sleep(2) # 網絡請求 requests.get()print("下載完成")async def main():urls = ["http://www.baidu.com","http://www.bilibili.com","http://www.163.com"]# 準備異步協程對象列表tasks = []for url in urls:d = asyncio.create_task(download(url))tasks.append(d)# tasks = [asyncio.create_task(download(url)) for url in urls] # 這么干也行哦~# 一次性把所有任務都執行await asyncio.wait(tasks)if __name__ == '__main__':asyncio.run(main())總結
我們今天認識了協程和異步爬蟲,一步步逐步認識了異步的優點,進一步提高了我們的程序效率。
總結
以上是生活随笔為你收集整理的22. 协程与Python中的多任务异步协程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php模拟用户自动在qq空间发表文章的方
- 下一篇: wx.scanCode(Object o