python中asyncio異步編程學習
1. 想學asyncio,得先了解協(xié)程
攜程的意義:
- 計算型的操作,利用協(xié)程來回切換執(zhí)行,沒有任何意義,來回切換并保存狀態(tài) 反倒會降低性能。
- IO型的操作,利用協(xié)程在IO等待時間就去切換執(zhí)行其他任務,當IO操作結束后再自動回調(diào),那么就會大大節(jié)省資源并提供性能,從而實現(xiàn)異步編程(不等待任務結束就可以去執(zhí)行其他代碼
2.協(xié)程和多線程之間的共同點和區(qū)別:
共同點:
都是并發(fā)操作,多線程同一時間點只能有一個線程在執(zhí)行,協(xié)程同一時間點只能有一個任務在執(zhí)行;
不同點:
多線程,是在I/O阻塞時通過切換線程來達到并發(fā)的效果,在什么情況下做線程切換是由操作系統(tǒng)來決定的,開發(fā)者不用操心,但會造成競爭條件 (race condition) ;
協(xié)程,只有一個線程,在I/O阻塞時通過在線程內(nèi)切換任務來達到并發(fā)的效果,在什么情況下做任務切換是開發(fā)者決定的,不會有競爭條件 (race condition) 的情況;多線程的線程切換比協(xié)程的任務切換開銷更大;
對于開發(fā)者而言,多線程并發(fā)的代碼比協(xié)程并發(fā)的更容易書寫。
一般情況下協(xié)程并發(fā)的處理效率比多線程并發(fā)更高。
3. greenlet實現(xiàn)協(xié)程
greenlet用于創(chuàng)建協(xié)程,switch用于進行協(xié)程之間的切換某個協(xié)程在執(zhí)行的過程中可以隨時的被其他協(xié)程通過switch函數(shù)來打斷,轉而去執(zhí)行其他協(xié)程,當前協(xié)程的中斷現(xiàn)場會被保留,一旦中斷的協(xié)程再次獲得cpu的執(zhí)行權首先會恢復現(xiàn)場然后從中斷處繼續(xù)執(zhí)行這種機制下的協(xié)程是同步,不能并發(fā)
pip install greenlet
import time
import greenlet
def func1():
print("func11")
gr2.switch()
time.sleep(1)
print("func22")
gr2.switch()
def func2():
print("func33")
gr1.switch()
time.sleep(1)
print("func44")
start = time.time()
gr1 = greenlet.greenlet(func1)
gr2 = greenlet.greenlet(func2)
gr1.switch()
end = time.time()
print(end - start)
4. yield關鍵字實現(xiàn)協(xié)程
def func1(): yield 1 yield from func2() yield 3 def func2(): yield 2 yield 4 ff = func1() for item in ff: print(item)
5.gevent協(xié)程
(1)gevent實現(xiàn)協(xié)程
pip install gevent
from greenlet import greenlet
from time import sleep
def func1():
print("協(xié)程1")
sleep(2)
g2.switch()
print("協(xié)程1恢復運行")
def func2():
print("協(xié)程2")
sleep(1)
g3.switch()
def func3():
print("協(xié)程3")
sleep(1)
g1.switch()
if __name__ == '__main__':
# 使用greenlet來創(chuàng)建三個協(xié)程
g1 = greenlet(func1)
g2 = greenlet(func2)
g3 = greenlet(func3)
# print(g1)
g1.switch() # 讓協(xié)程g1取搶占cpu資源
(2) gevent實現(xiàn)異步協(xié)程
# 協(xié)程被創(chuàng)建出來以后默認是多個協(xié)程同步執(zhí)行
# 我們可以加入monkey補丁,把同步的協(xié)程轉成異步協(xié)程
from gevent import monkey # 注意:monkey的引入必須在其他模塊之前
monkey.patch_all() # 用monkey給整個協(xié)程隊列,添加一個非阻塞I/O的補丁,使得他們成為異步協(xié)程
import time
import requests
import gevent
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
def func(url, i):
print("協(xié)程%d開啟!" % i)
res = requests.get(url=url, headers=headers)
html = res.text
print("協(xié)程%d執(zhí)行結束,獲取到的響應體大小為:%d" % (i, len(html)))
if __name__ == '__main__':
start = time.time()
urls = [
"https://www.baidu.com/",
"https://www.qq.com/",
"https://www.sina.com.cn",
"https://www.ifeng.com/",
"https://www.163.com/"
]
# 創(chuàng)建5個協(xié)程分別對上面5個網(wǎng)站進行訪問
g_list = []
for i in range(len(urls)):
g = gevent.spawn(func, urls[i], i)
g_list.append(g)
# func(urls[i], i)
gevent.joinall(g_list)
end = time.time()
print(end - start)
6. asyncio模塊實現(xiàn)異步協(xié)程
在python3.4及之后的版本使用,asyncio厲害之處在于:遇到IO操作時會自動切換執(zhí)行其它任務
import time import asyncio @asyncio.coroutine def func1(): print(1) yield from asyncio.sleep(1) # 遇到IO耗時操作,自動切換到tasks中的其它任務 print(2) @asyncio.coroutine def func2(): print(3) yield from asyncio.sleep(1) # 遇到IO耗時操作,自動切換到tasks中的其它任務 print(4) tasks = [ asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) ] start = time.time() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) end = time.time() print(end - start)
7. asyc & await關鍵字實現(xiàn)異步編程(現(xiàn)在推薦使用的用法)
在python3.5及之后的版本中可以使用
import time import asyncio async def func1(): print(1) await asyncio.sleep(1) print(2) async def func2(): print(3) await asyncio.sleep(1) print(4) tasks = [ asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) ] start = time.time() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) end = time.time() print(end - start)
7.1 事件循環(huán)
事件循環(huán),可以把他當做是一個while循環(huán),這個while循環(huán)在周期性的運行并執(zhí)行一些任務,在特定條件下終止循環(huán)。
偽代碼:
# 偽代碼
任務列表 = [ 任務1, 任務2, 任務3,... ]
while True:
可執(zhí)行的任務列表,已完成的任務列表 = 去任務列表中檢查所有的任務,將'可執(zhí)行'和'已完成'的任務返回
for 就緒任務 in 已準備就緒的任務列表:
執(zhí)行已就緒的任務
for 已完成的任務 in 已完成的任務列表:
在任務列表中移除 已完成的任務
如果 任務列表 中的任務都已完成,則終止循環(huán)
7.2 協(xié)程和異步編程
協(xié)程函數(shù),定義形式為 async def 的函數(shù)。
協(xié)程對象,調(diào)用 協(xié)程函數(shù) 所返回的對象。
# 定義一個協(xié)程函數(shù) async def func(): pass # 調(diào)用協(xié)程函數(shù),返回一個協(xié)程對象 result = func()
注意:調(diào)用協(xié)程函數(shù)時,函數(shù)內(nèi)部代碼不會執(zhí)行,只是會返回一個協(xié)程對象。
7.3 基本應用
程序中,如果想要執(zhí)行協(xié)程函數(shù)的內(nèi)部代碼,需要 事件循環(huán) 和 協(xié)程對象 配合才能實現(xiàn),如:
import asyncio
async def func():
print("協(xié)程內(nèi)部代碼")
# 調(diào)用協(xié)程函數(shù),返回一個協(xié)程對象。
result = func()
# 方式一
# loop = asyncio.get_event_loop() # 創(chuàng)建一個事件循環(huán)
# loop.run_until_complete(result) # 將協(xié)程當做任務提交到事件循環(huán)的任務列表中,協(xié)程執(zhí)行完成之后終止。
# 方式二
# 本質(zhì)上方式一是一樣的,內(nèi)部先 創(chuàng)建事件循環(huán) 然后執(zhí)行 run_until_complete,一個簡便的寫法。
# asyncio.run 函數(shù)在 Python 3.7 中加入 asyncio 模塊,
asyncio.run(result)
這個過程可以簡單理解為:將協(xié)程當做任務添加到 事件循環(huán) 的任務列表,然后事件循環(huán)檢測列表中的協(xié)程是否 已準備就緒(默認可理解為就緒狀態(tài)),如果準備就緒則執(zhí)行其內(nèi)部代碼。
7.4 await關鍵字
await是一個只能在協(xié)程函數(shù)中使用的關鍵字,用于遇到IO操作時掛起 當前協(xié)程(任務),當前協(xié)程(任務)掛起過程中 事件循環(huán)可以去執(zhí)行其他的協(xié)程(任務),當前協(xié)程IO處理完成時,可以再次切換回來執(zhí)行await之后的代碼,
await + 可等待對象(協(xié)程對象、Future對象、Task對象)
示例1:await+協(xié)程對象
import asyncio
async def func1():
print("start")
await asyncio.sleep(1)
print("end")
return "func1執(zhí)行完畢"
async def func2():
print("func2開始執(zhí)行")
# await關鍵字后面可以跟可等待對象(協(xié)程對象、Future對象、Task對象)
response = await func1()
print(response)
print("func2執(zhí)行完畢")
asyncio.run(func2())
示例2: 協(xié)程函數(shù)中可以使用多次await關鍵字
import asyncio
async def func1():
print("start")
await asyncio.sleep(1)
print("end")
return "func1執(zhí)行完畢"
async def func2():
print("func2開始執(zhí)行")
# await關鍵字后面可以跟可等待對象(協(xié)程對象、Future對象、Task對象)
response = await func1()
print(response)
response2 = await func1()
print(response2)
print("func2執(zhí)行完畢")
asyncio.run(func2())
7.5 task對象
Tasks用于并發(fā)調(diào)度協(xié)程,通過asyncio.create_task(協(xié)程對象)的方式創(chuàng)建Task對象,這樣可以讓協(xié)程加入事件循環(huán)中等待被調(diào)度執(zhí)行。除了使用 asyncio.create_task() 函數(shù)以外,還可以用低層級的 loop.create_task() 或 ensure_future() 函數(shù)。不建議手動實例化 Task 對象。
本質(zhì)上是將協(xié)程對象封裝成task對象,并將協(xié)程立即加入事件循環(huán),同時追蹤協(xié)程的狀態(tài)。
注意:asyncio.create_task() 函數(shù)在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低層級的 asyncio.ensure_future() 函數(shù)。
示例1:
import asyncio async def func(): print(1) await asyncio.sleep(1) print(2) return "func的返回值" async def main(): print(3) # 創(chuàng)建協(xié)程,將協(xié)程封裝到一個task對象中并立即添加到事件循環(huán)列表中,等待事件循環(huán)去執(zhí)行,(默認是就緒狀態(tài)) task1 = asyncio.create_task(func()) # 創(chuàng)建協(xié)程,將協(xié)程封裝到一個task對象中并立即添加到事件循環(huán)列表中,等待事件循環(huán)去執(zhí)行,(默認是就緒狀態(tài)) task2 = asyncio.create_task(func()) # 當執(zhí)行某協(xié)程遇到IO操作時,會自動化切換執(zhí)行其他任務。 # 此處的await是等待相對應的協(xié)程全都執(zhí)行完畢并獲取結果 ret1 = await task1 ret2 = await task2 print(ret1, ret2) asyncio.run(main())
示例2:用的還是比較多的
import asyncio
async def func():
print(1)
await asyncio.sleep(1)
print(2)
return "func的返回值"
async def main():
print(3)
# 創(chuàng)建協(xié)程,將協(xié)程封裝到Task對象中并添加到事件循環(huán)的任務列表中,等待事件循環(huán)去執(zhí)行(默認是就緒狀態(tài))。
# 在調(diào)用
task_list = [
asyncio.create_task(func()),
asyncio.create_task(func())
]
# 當執(zhí)行某協(xié)程遇到IO操作時,會自動化切換執(zhí)行其他任務。
# 此處的await是等待所有協(xié)程執(zhí)行完畢,并將所有協(xié)程的返回值保存到done
# 如果設置了timeout值,則意味著此處最多等待的秒,完成的協(xié)程返回值寫入到done中,未完成則寫到pending中。
done, pending = await asyncio.wait(task_list, timeout=None)
print(done)
print(pending)
asyncio.run(main())
示例3:
import asyncio
async def func():
print("執(zhí)行協(xié)程函數(shù)內(nèi)部代碼")
# 遇到IO操作掛起當前協(xié)程(任務),等IO操作完成之后再繼續(xù)往下執(zhí)行。當前協(xié)程掛起時,事件循環(huán)可以去執(zhí)行其他協(xié)程(任務)。
response = await asyncio.sleep(2)
print("IO請求結束,結果為:", response)
coroutine_list = [func(), func()]
# 錯誤:coroutine_list = [ asyncio.create_task(func()), asyncio.create_task(func()) ]
# 此處不能直接 asyncio.create_task,因為將Task立即加入到事件循環(huán)的任務列表,
# 但此時事件循環(huán)還未創(chuàng)建,所以會報錯。
# 使用asyncio.wait將列表封裝為一個協(xié)程,并調(diào)用asyncio.run實現(xiàn)執(zhí)行兩個協(xié)程
# asyncio.wait內(nèi)部會對列表中的每個協(xié)程執(zhí)行ensure_future,封裝為Task對象。
done, pending = asyncio.run(asyncio.wait(coroutine_list))
總結:
在程序中只要看到async和await關鍵字,其內(nèi)部就是基于協(xié)程實現(xiàn)的異步編程,這種異步編程是通過一個線程在IO等待時間去執(zhí)行其他任務,從而實現(xiàn)并發(fā)。
如果是 I/O 密集型,且 I/O 請求比較耗時的話,使用協(xié)程。
如果是 I/O 密集型,且 I/O 請求比較快的話,使用多線程。
如果是 計算 密集型,考慮可以使用多核 CPU,使用多進程。
以上就是python中asyncio異步編程學習的詳細內(nèi)容,更多關于python中使用asyncio的資料請關注腳本之家其它相關文章!
相關文章
詳解Python?Flask?API?示例演示(附cookies和session)
這篇文章主要為大家介紹了Python?Flask?API?示例演示(附cookies和session)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03
Python+OpenCV實現(xiàn)圖像識別替換功能詳解
OpenCV-Python是一個Python庫,旨在解決計算機視覺問題。本文將利用Python+OpenCV實現(xiàn)圖像識別替換功能,感興趣的小伙伴可以動手嘗試一下2022-07-07
基于Python+Matplotlib繪制漸變色扇形圖與等高線圖
這篇文章主要為大家介紹了如何利用Python中的Matplotlib繪制漸變色扇形圖與等高線圖,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下方法2022-04-04
Python實現(xiàn)將多個文件的名稱或后綴名由大寫改為小寫
這篇文章主要介紹了如何基于Python語言實現(xiàn)將多個文件的名稱或后綴名由大寫字母修改為小寫,文中的示例代碼講解詳細,感興趣的可以了解下2023-09-09
Python使用BeautifulSoup庫解析網(wǎng)頁
在Python的網(wǎng)絡爬蟲中,網(wǎng)頁解析是一項重要的技術。而在眾多的網(wǎng)頁解析庫中,BeautifulSoup庫憑借其簡單易用而廣受歡迎,在本篇文章中,我們將學習BeautifulSoup庫的基本用法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2023-08-08
python中Tkinter實現(xiàn)分頁標簽的示例代碼
這篇文章主要介紹了python中Tkinter實現(xiàn)分頁標簽的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04

