python中asyncio異步編程學習

1.   想學asyncio,得先瞭解協程

攜程的意義:

  1. 計算型的操作,利用協程來回切換執行,沒有任何意義,來回切換並保存狀態 反倒會降低性能。
  2. IO型的操作,利用協程在IO等待時間就去切換執行其他任務,當IO操作結束後再自動回調,那麼就會大大節省資源並提供性能,從而實現異步編程(不等待任務結束就可以去執行其他代碼

2.協程和多線程之間的共同點和區別:

共同點:

都是並發操作,多線程同一時間點隻能有一個線程在執行,協程同一時間點隻能有一個任務在執行;

不同點:

多線程,是在I/O阻塞時通過切換線程來達到並發的效果,在什麼情況下做線程切換是由操作系統來決定的,開發者不用操心,但會造成競爭條件 (race condition) ;

協程,隻有一個線程,在I/O阻塞時通過在線程內切換任務來達到並發的效果,在什麼情況下做任務切換是開發者決定的,不會有競爭條件 (race condition) 的情況;多線程的線程切換比協程的任務切換開銷更大;
對於開發者而言,多線程並發的代碼比協程並發的更容易書寫。

一般情況下協程並發的處理效率比多線程並發更高。

3. greenlet實現協程

greenlet用於創建協程,switch用於進行協程之間的切換某個協程在執行的過程中可以隨時的被其他協程通過switch函數來打斷,轉而去執行其他協程,當前協程的中斷現場會被保留,一旦中斷的協程再次獲得cpu的執行權首先會恢復現場然後從中斷處繼續執行這種機制下的協程是同步,不能並發

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關鍵字實現協程

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協程

(1)gevent實現協程

pip install gevent

from greenlet import greenlet
from time import sleep
def func1():
  print("協程1")
  sleep(2)
  g2.switch()
  print("協程1恢復運行")
 
def func2():
  print("協程2")
  sleep(1)
  g3.switch()
def func3():
  print("協程3")
  sleep(1)
  g1.switch()
 
if __name__ == '__main__':
  # 使用greenlet來創建三個協程
  g1 = greenlet(func1)
  g2 = greenlet(func2)
  g3 = greenlet(func3)
  # print(g1)
  g1.switch() # 讓協程g1取搶占cpu資源

(2) gevent實現異步協程

# 協程被創建出來以後默認是多個協程同步執行
# 我們可以加入monkey補丁,把同步的協程轉成異步協程
from gevent import monkey # 註意:monkey的引入必須在其他模塊之前
 
monkey.patch_all() # 用monkey給整個協程隊列,添加一個非阻塞I/O的補丁,使得他們成為異步協程
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("協程%d開啟!" % i)
  res = requests.get(url=url, headers=headers)
  html = res.text
  print("協程%d執行結束,獲取到的響應體大小為:%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/"
  ]
  # 創建5個協程分別對上面5個網站進行訪問
  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模塊實現異步協程

在python3.4及之後的版本使用,asyncio厲害之處在於:遇到IO操作時會自動切換執行其它任務

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關鍵字實現異步編程(現在推薦使用的用法)

在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 事件循環

    事件循環,可以把他當做是一個while循環,這個while循環在周期性的運行並執行一些任務,在特定條件下終止循環。

偽代碼:

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

7.2 協程和異步編程

協程函數,定義形式為 async def 的函數。

協程對象,調用 協程函數 所返回的對象。

# 定義一個協程函數
async def func():
  pass
# 調用協程函數,返回一個協程對象
result = func()

註意:調用協程函數時,函數內部代碼不會執行,隻是會返回一個協程對象。 

7.3 基本應用

程序中,如果想要執行協程函數的內部代碼,需要 事件循環 和 協程對象 配合才能實現,如:

import asyncio
async def func():
  print("協程內部代碼")
# 調用協程函數,返回一個協程對象。
result = func()
# 方式一
# loop = asyncio.get_event_loop() # 創建一個事件循環
# loop.run_until_complete(result) # 將協程當做任務提交到事件循環的任務列表中,協程執行完成之後終止。
# 方式二
# 本質上方式一是一樣的,內部先 創建事件循環 然後執行 run_until_complete,一個簡便的寫法。
# asyncio.run 函數在 Python 3.7 中加入 asyncio 模塊,
asyncio.run(result)

這個過程可以簡單理解為:將協程當做任務添加到 事件循環 的任務列表,然後事件循環檢測列表中的協程是否 已準備就緒(默認可理解為就緒狀態),如果準備就緒則執行其內部代碼。

7.4 await關鍵字

await是一個隻能在協程函數中使用的關鍵字,用於遇到IO操作時掛起 當前協程(任務),當前協程(任務)掛起過程中 事件循環可以去執行其他的協程(任務),當前協程IO處理完成時,可以再次切換回來執行await之後的代碼,

await + 可等待對象(協程對象、Future對象、Task對象)

示例1:await+協程對象

import asyncio
 
 
async def func1():
  print("start")
  await asyncio.sleep(1)
  print("end")
  return "func1執行完畢"
 
 
async def func2():
  print("func2開始執行")
  # await關鍵字後面可以跟可等待對象(協程對象、Future對象、Task對象)
  response = await func1()
  print(response)
  print("func2執行完畢")
 
 
asyncio.run(func2())

示例2: 協程函數中可以使用多次await關鍵字

import asyncio
 
 
async def func1():
  print("start")
  await asyncio.sleep(1)
  print("end")
  return "func1執行完畢"
 
 
async def func2():
  print("func2開始執行")
  # await關鍵字後面可以跟可等待對象(協程對象、Future對象、Task對象)
  response = await func1()
  print(response)
  response2 = await func1()
  print(response2)
  print("func2執行完畢")
 
 
asyncio.run(func2())

7.5 task對象

Tasks用於並發調度協程,通過asyncio.create_task(協程對象)的方式創建Task對象,這樣可以讓協程加入事件循環中等待被調度執行。除瞭使用 asyncio.create_task() 函數以外,還可以用低層級的 loop.create_task() 或 ensure_future() 函數。不建議手動實例化 Task 對象。

本質上是將協程對象封裝成task對象,並將協程立即加入事件循環,同時追蹤協程的狀態。

註意:asyncio.create_task() 函數在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低層級的 asyncio.ensure_future() 函數。

示例1:

import asyncio
 
 
async def func():
  print(1)
  await asyncio.sleep(1)
  print(2)
  return "func的返回值"
 
 
async def main():
  print(3)
  # 創建協程,將協程封裝到一個task對象中並立即添加到事件循環列表中,等待事件循環去執行,(默認是就緒狀態)
  task1 = asyncio.create_task(func())
  # 創建協程,將協程封裝到一個task對象中並立即添加到事件循環列表中,等待事件循環去執行,(默認是就緒狀態)
  task2 = asyncio.create_task(func())
  # 當執行某協程遇到IO操作時,會自動化切換執行其他任務。
  # 此處的await是等待相對應的協程全都執行完畢並獲取結果
  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)
  # 創建協程,將協程封裝到Task對象中並添加到事件循環的任務列表中,等待事件循環去執行(默認是就緒狀態)。
  # 在調用
  task_list = [
    asyncio.create_task(func()),
    asyncio.create_task(func())
  ]
  # 當執行某協程遇到IO操作時,會自動化切換執行其他任務。
  # 此處的await是等待所有協程執行完畢,並將所有協程的返回值保存到done
  # 如果設置瞭timeout值,則意味著此處最多等待的秒,完成的協程返回值寫入到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("執行協程函數內部代碼")
  # 遇到IO操作掛起當前協程(任務),等IO操作完成之後再繼續往下執行。當前協程掛起時,事件循環可以去執行其他協程(任務)。
  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立即加入到事件循環的任務列表,
# 但此時事件循環還未創建,所以會報錯。
# 使用asyncio.wait將列表封裝為一個協程,並調用asyncio.run實現執行兩個協程
# asyncio.wait內部會對列表中的每個協程執行ensure_future,封裝為Task對象。
done, pending = asyncio.run(asyncio.wait(coroutine_list))

總結:

在程序中隻要看到asyncawait關鍵字,其內部就是基於協程實現的異步編程,這種異步編程是通過一個線程在IO等待時間去執行其他任務,從而實現並發。

如果是 I/O 密集型,且 I/O 請求比較耗時的話,使用協程。
如果是 I/O 密集型,且 I/O 請求比較快的話,使用多線程。
如果是 計算 密集型,考慮可以使用多核 CPU,使用多進程。

以上就是python中asyncio異步編程學習的詳細內容,更多關於python中使用asyncio的資料請關註WalkonNet其它相關文章!