溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

python中asyncio異步編程的示例分析

發(fā)布時(shí)間:2021-04-07 09:35:31 來(lái)源:億速云 閱讀:218 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)python中asyncio異步編程的示例分析,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

1.   想學(xué)asyncio,得先了解協(xié)程

攜程的意義:

  1. 計(jì)算型的操作,利用協(xié)程來(lái)回切換執(zhí)行,沒(méi)有任何意義,來(lái)回切換并保存狀態(tài) 反倒會(huì)降低性能。

  2. IO型的操作,利用協(xié)程在IO等待時(shí)間就去切換執(zhí)行其他任務(wù),當(dāng)IO操作結(jié)束后再自動(dòng)回調(diào),那么就會(huì)大大節(jié)省資源并提供性能,從而實(shí)現(xiàn)異步編程(不等待任務(wù)結(jié)束就可以去執(zhí)行其他代碼

2.協(xié)程和多線程之間的共同點(diǎn)和區(qū)別:

共同點(diǎn):

都是并發(fā)操作,多線程同一時(shí)間點(diǎn)只能有一個(gè)線程在執(zhí)行,協(xié)程同一時(shí)間點(diǎn)只能有一個(gè)任務(wù)在執(zhí)行;

不同點(diǎn):

多線程,是在I/O阻塞時(shí)通過(guò)切換線程來(lái)達(dá)到并發(fā)的效果,在什么情況下做線程切換是由操作系統(tǒng)來(lái)決定的,開(kāi)發(fā)者不用操心,但會(huì)造成競(jìng)爭(zhēng)條件 (race condition) ;

協(xié)程,只有一個(gè)線程,在I/O阻塞時(shí)通過(guò)在線程內(nèi)切換任務(wù)來(lái)達(dá)到并發(fā)的效果,在什么情況下做任務(wù)切換是開(kāi)發(fā)者決定的,不會(huì)有競(jìng)爭(zhēng)條件 (race condition) 的情況;多線程的線程切換比協(xié)程的任務(wù)切換開(kāi)銷(xiāo)更大;
對(duì)于開(kāi)發(fā)者而言,多線程并發(fā)的代碼比協(xié)程并發(fā)的更容易書(shū)寫(xiě)。

一般情況下協(xié)程并發(fā)的處理效率比多線程并發(fā)更高。

3. greenlet實(shí)現(xiàn)協(xié)程

greenlet用于創(chuàng)建協(xié)程,switch用于進(jìn)行協(xié)程之間的切換某個(gè)協(xié)程在執(zhí)行的過(guò)程中可以隨時(shí)的被其他協(xié)程通過(guò)switch函數(shù)來(lái)打斷,轉(zhuǎn)而去執(zhí)行其他協(xié)程,當(dāng)前協(xié)程的中斷現(xiàn)場(chǎng)會(huì)被保留,一旦中斷的協(xié)程再次獲得cpu的執(zhí)行權(quán)首先會(huì)恢復(fù)現(xiàn)場(chǎng)然后從中斷處繼續(xù)執(zhí)行這種機(jī)制下的協(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關(guān)鍵字實(shí)現(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實(shí)現(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恢復(fù)運(yùn)行")
 
def func2():
  print("協(xié)程2")
  sleep(1)
  g3.switch()
def func3():
  print("協(xié)程3")
  sleep(1)
  g1.switch()
 
if __name__ == '__main__':
  # 使用greenlet來(lái)創(chuàng)建三個(gè)協(xié)程
  g1 = greenlet(func1)
  g2 = greenlet(func2)
  g3 = greenlet(func3)
  # print(g1)
  g1.switch() # 讓協(xié)程g1取搶占cpu資源

(2) gevent實(shí)現(xiàn)異步協(xié)程

# 協(xié)程被創(chuàng)建出來(lái)以后默認(rèn)是多個(gè)協(xié)程同步執(zhí)行
# 我們可以加入monkey補(bǔ)丁,把同步的協(xié)程轉(zhuǎn)成異步協(xié)程
from gevent import monkey # 注意:monkey的引入必須在其他模塊之前
 
monkey.patch_all() # 用monkey給整個(gè)協(xié)程隊(duì)列,添加一個(gè)非阻塞I/O的補(bǔ)丁,使得他們成為異步協(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開(kāi)啟!" % i)
  res = requests.get(url=url, headers=headers)
  html = res.text
  print("協(xié)程%d執(zhí)行結(jié)束,獲取到的響應(yīng)體大小為:%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個(gè)協(xié)程分別對(duì)上面5個(gè)網(wǎng)站進(jìn)行訪問(wèn)
  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模塊實(shí)現(xiàn)異步協(xié)程

在python3.4及之后的版本使用,asyncio厲害之處在于:遇到IO操作時(shí)會(huì)自動(dòng)切換執(zhí)行其它任務(wù)

import time
import asyncio
 
 
@asyncio.coroutine
def func1():
  print(1)
  yield from asyncio.sleep(1) # 遇到IO耗時(shí)操作,自動(dòng)切換到tasks中的其它任務(wù)
  print(2)
 
 
@asyncio.coroutine
def func2():
  print(3)
  yield from asyncio.sleep(1) # 遇到IO耗時(shí)操作,自動(dòng)切換到tasks中的其它任務(wù)
  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關(guān)鍵字實(shí)現(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),可以把他當(dāng)做是一個(gè)while循環(huán),這個(gè)while循環(huán)在周期性的運(yùn)行并執(zhí)行一些任務(wù),在特定條件下終止循環(huán)。

偽代碼:

# 偽代碼
任務(wù)列表 = [ 任務(wù)1, 任務(wù)2, 任務(wù)3,... ]
while True:
  可執(zhí)行的任務(wù)列表,已完成的任務(wù)列表 = 去任務(wù)列表中檢查所有的任務(wù),將'可執(zhí)行'和'已完成'的任務(wù)返回
  for 就緒任務(wù) in 已準(zhǔn)備就緒的任務(wù)列表:
    執(zhí)行已就緒的任務(wù)
  for 已完成的任務(wù) in 已完成的任務(wù)列表:
    在任務(wù)列表中移除 已完成的任務(wù)
  如果 任務(wù)列表 中的任務(wù)都已完成,則終止循環(huán)

7.2 協(xié)程和異步編程

協(xié)程函數(shù),定義形式為 async def 的函數(shù)。

協(xié)程對(duì)象,調(diào)用 協(xié)程函數(shù) 所返回的對(duì)象。

# 定義一個(gè)協(xié)程函數(shù)
async def func():
  pass
# 調(diào)用協(xié)程函數(shù),返回一個(gè)協(xié)程對(duì)象
result = func()

注意:調(diào)用協(xié)程函數(shù)時(shí),函數(shù)內(nèi)部代碼不會(huì)執(zhí)行,只是會(huì)返回一個(gè)協(xié)程對(duì)象。 

7.3 基本應(yīng)用

程序中,如果想要執(zhí)行協(xié)程函數(shù)的內(nèi)部代碼,需要 事件循環(huán) 和 協(xié)程對(duì)象 配合才能實(shí)現(xiàn),如:

import asyncio
async def func():
  print("協(xié)程內(nèi)部代碼")
# 調(diào)用協(xié)程函數(shù),返回一個(gè)協(xié)程對(duì)象。
result = func()
# 方式一
# loop = asyncio.get_event_loop() # 創(chuàng)建一個(gè)事件循環(huán)
# loop.run_until_complete(result) # 將協(xié)程當(dāng)做任務(wù)提交到事件循環(huán)的任務(wù)列表中,協(xié)程執(zhí)行完成之后終止。
# 方式二
# 本質(zhì)上方式一是一樣的,內(nèi)部先 創(chuàng)建事件循環(huán) 然后執(zhí)行 run_until_complete,一個(gè)簡(jiǎn)便的寫(xiě)法。
# asyncio.run 函數(shù)在 Python 3.7 中加入 asyncio 模塊,
asyncio.run(result)

這個(gè)過(guò)程可以簡(jiǎn)單理解為:將協(xié)程當(dāng)做任務(wù)添加到 事件循環(huán) 的任務(wù)列表,然后事件循環(huán)檢測(cè)列表中的協(xié)程是否 已準(zhǔn)備就緒(默認(rèn)可理解為就緒狀態(tài)),如果準(zhǔn)備就緒則執(zhí)行其內(nèi)部代碼。

7.4 await關(guān)鍵字

await是一個(gè)只能在協(xié)程函數(shù)中使用的關(guān)鍵字,用于遇到IO操作時(shí)掛起 當(dāng)前協(xié)程(任務(wù)),當(dāng)前協(xié)程(任務(wù))掛起過(guò)程中 事件循環(huán)可以去執(zhí)行其他的協(xié)程(任務(wù)),當(dāng)前協(xié)程IO處理完成時(shí),可以再次切換回來(lái)執(zhí)行await之后的代碼,

await + 可等待對(duì)象(協(xié)程對(duì)象、Future對(duì)象、Task對(duì)象)

示例1:await+協(xié)程對(duì)象

import asyncio
 
 
async def func1():
  print("start")
  await asyncio.sleep(1)
  print("end")
  return "func1執(zhí)行完畢"
 
 
async def func2():
  print("func2開(kāi)始執(zhí)行")
  # await關(guān)鍵字后面可以跟可等待對(duì)象(協(xié)程對(duì)象、Future對(duì)象、Task對(duì)象)
  response = await func1()
  print(response)
  print("func2執(zhí)行完畢")
 
 
asyncio.run(func2())

示例2: 協(xié)程函數(shù)中可以使用多次await關(guān)鍵字

import asyncio
 
 
async def func1():
  print("start")
  await asyncio.sleep(1)
  print("end")
  return "func1執(zhí)行完畢"
 
 
async def func2():
  print("func2開(kāi)始執(zhí)行")
  # await關(guān)鍵字后面可以跟可等待對(duì)象(協(xié)程對(duì)象、Future對(duì)象、Task對(duì)象)
  response = await func1()
  print(response)
  response2 = await func1()
  print(response2)
  print("func2執(zhí)行完畢")
 
 
asyncio.run(func2())

7.5 task對(duì)象

Tasks用于并發(fā)調(diào)度協(xié)程,通過(guò)asyncio.create_task(協(xié)程對(duì)象)的方式創(chuàng)建Task對(duì)象,這樣可以讓協(xié)程加入事件循環(huán)中等待被調(diào)度執(zhí)行。除了使用 asyncio.create_task() 函數(shù)以外,還可以用低層級(jí)的 loop.create_task() 或 ensure_future() 函數(shù)。不建議手動(dòng)實(shí)例化 Task 對(duì)象。

本質(zhì)上是將協(xié)程對(duì)象封裝成task對(duì)象,并將協(xié)程立即加入事件循環(huán),同時(shí)追蹤協(xié)程的狀態(tài)。

注意:asyncio.create_task() 函數(shù)在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低層級(jí)的 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é)程封裝到一個(gè)task對(duì)象中并立即添加到事件循環(huán)列表中,等待事件循環(huán)去執(zhí)行,(默認(rèn)是就緒狀態(tài))
  task1 = asyncio.create_task(func())
  # 創(chuàng)建協(xié)程,將協(xié)程封裝到一個(gè)task對(duì)象中并立即添加到事件循環(huán)列表中,等待事件循環(huán)去執(zhí)行,(默認(rèn)是就緒狀態(tài))
  task2 = asyncio.create_task(func())
  # 當(dāng)執(zhí)行某協(xié)程遇到IO操作時(shí),會(huì)自動(dòng)化切換執(zhí)行其他任務(wù)。
  # 此處的await是等待相對(duì)應(yīng)的協(xié)程全都執(zhí)行完畢并獲取結(jié)果
  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對(duì)象中并添加到事件循環(huán)的任務(wù)列表中,等待事件循環(huán)去執(zhí)行(默認(rèn)是就緒狀態(tài))。
  # 在調(diào)用
  task_list = [
    asyncio.create_task(func()),
    asyncio.create_task(func())
  ]
  # 當(dāng)執(zhí)行某協(xié)程遇到IO操作時(shí),會(huì)自動(dòng)化切換執(zhí)行其他任務(wù)。
  # 此處的await是等待所有協(xié)程執(zhí)行完畢,并將所有協(xié)程的返回值保存到done
  # 如果設(shè)置了timeout值,則意味著此處最多等待的秒,完成的協(xié)程返回值寫(xiě)入到done中,未完成則寫(xiě)到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操作掛起當(dāng)前協(xié)程(任務(wù)),等IO操作完成之后再繼續(xù)往下執(zhí)行。當(dāng)前協(xié)程掛起時(shí),事件循環(huán)可以去執(zhí)行其他協(xié)程(任務(wù))。
  response = await asyncio.sleep(2)
  print("IO請(qǐng)求結(jié)束,結(jié)果為:", response)
 
 
coroutine_list = [func(), func()]
# 錯(cuò)誤:coroutine_list = [ asyncio.create_task(func()), asyncio.create_task(func()) ]
# 此處不能直接 asyncio.create_task,因?yàn)閷ask立即加入到事件循環(huán)的任務(wù)列表,
# 但此時(shí)事件循環(huán)還未創(chuàng)建,所以會(huì)報(bào)錯(cuò)。
# 使用asyncio.wait將列表封裝為一個(gè)協(xié)程,并調(diào)用asyncio.run實(shí)現(xiàn)執(zhí)行兩個(gè)協(xié)程
# asyncio.wait內(nèi)部會(huì)對(duì)列表中的每個(gè)協(xié)程執(zhí)行ensure_future,封裝為T(mén)ask對(duì)象。
done, pending = asyncio.run(asyncio.wait(coroutine_list))

總結(jié):

在程序中只要看到asyncawait關(guān)鍵字,其內(nèi)部就是基于協(xié)程實(shí)現(xiàn)的異步編程,這種異步編程是通過(guò)一個(gè)線程在IO等待時(shí)間去執(zhí)行其他任務(wù),從而實(shí)現(xiàn)并發(fā)。

如果是 I/O 密集型,且 I/O 請(qǐng)求比較耗時(shí)的話,使用協(xié)程。
如果是 I/O 密集型,且 I/O 請(qǐng)求比較快的話,使用多線程。
如果是 計(jì)算 密集型,考慮可以使用多核 CPU,使用多進(jìn)程。

關(guān)于“python中asyncio異步編程的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI