一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Python - Python使用asyncio包處理并發詳解

Python使用asyncio包處理并發詳解

2020-12-07 00:14zhexiao27 Python

這篇文章主要為大家詳細介紹了Python使用asyncio包處理并發的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下

阻塞型I/O和GIL

CPython 解釋器本身就不是線程安全的,因此有全局解釋器鎖(GIL),一次只允許使用一個線程執行 Python 字節碼。因此,一個 Python 進程通常不能同時使用多個 CPU 核心。

然而,標準庫中所有執行阻塞型 I/O 操作的函數,在等待操作系統返回結果時都會釋放GIL。這意味著在 Python 語言這個層次上可以使用多線程,而 I/O 密集型 Python 程序能從中受益:一個 Python 線程等待網絡響應時,阻塞型 I/O 函數會釋放 GIL,再運行一個線程。

asyncio

這個包使用事件循環驅動的協程實現并發。 asyncio 大量使用 yield from 表達式,因此與Python 舊版不兼容。

asyncio 包使用的“協程”是較嚴格的定義。適合asyncio API 的協程在定義體中必須使用 yield from,而不能使用 yield。此外,適合 asyncio 的協程要由調用方驅動,并由調用方通過 yield from 調用;

示例1

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import threading
import asyncio
 
@asyncio.coroutine
def hello():
  print('Start Hello', threading.currentThread())
  yield from asyncio.sleep(5)
  print('End Hello', threading.currentThread())
 
@asyncio.coroutine
def world():
  print('Start World', threading.currentThread())
  yield from asyncio.sleep(3)
  print('End World', threading.currentThread())
 
# 獲取EventLoop:
loop = asyncio.get_event_loop()
tasks = [hello(), world()]
# 執行coroutine
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

@asyncio.coroutine把生成器函數標記為協程類型。
asyncio.sleep(3) 創建一個3秒后完成的協程。
loop.run_until_complete(future),運行直到future完成;如果參數是 coroutine object,則需要使用 ensure_future()函數包裝。
loop.close() 關閉事件循環

示例2

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import asyncio
 
@asyncio.coroutine
def worker(text):
  """
  協程運行的函數
  :param text:
  :return:
  """
  i = 0
  while True:
    print(text, i)
 
    try:
      yield from asyncio.sleep(.1)
    except asyncio.CancelledError:
      break
 
    i += 1
 
 
@asyncio.coroutine
def client(text, io_used):
  worker_fu = asyncio.ensure_future(worker(text))
 
  # 假裝等待I/O一段時間
  yield from asyncio.sleep(io_used)
 
  # 結束運行協程
  worker_fu.cancel()
  return 'done'
 
 
loop = asyncio.get_event_loop()
tasks = [client('xiaozhe', 3), client('zzzz', 5)]
result = loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print('Answer:', result)

解釋:

1. asyncio.ensure_future(coro_or_future, *, loop=None):計劃安排一個 coroutine object的執行,返回一個 asyncio.Task object。
2. worker_fu.cancel(): 取消一個協程的執行,拋出CancelledError異常。
3. asyncio.wait():協程的參數是一個由期物或協程構成的可迭代對象; wait 會分別把各個協程包裝進一個 Task 對象。

asyncio.Task 對象與threading.Thread對象的比較

asyncio.Task 對象差不多與 threading.Thread 對象等效。
Task 對象用于驅動協程, Thread 對象用于調用可調用的對象。
Task 對象不由自己動手實例化,而是通過把協程傳給 asyncio.ensure_future(…) 函數或loop.create_task(…) 方法獲取。
獲取的 Task 對象已經排定了運行時間;Thread 實例則必須調用 start 方法,明確告知讓它運行。
如果想終止任務,可以使用 Task.cancel() 實例方法,在協程內部拋出CancelledError 異常。

線程與協程的安全比較

如果使用線程做過重要的編程,因為調度程序任何時候都能中斷線程。必須記住保留鎖,去保護程序中的重要部分,防止多步操作在執行的過程中中斷,防止數據處于無效狀態。

協程默認會做好全方位保護,以防止中斷。我們必須顯式產出才能讓程序的余下部分運行。對協程來說,無需保留鎖,在多個線程之間同步操作,協程自身就會同步,因為在任意時刻只有一個協程運行。想交出控制權時,可以使用 yield 或 yield from 把控制權交還調度程序。這就是能夠安全地取消協程的原因:按照定義,協程只能在暫停的 yield處取消,因此可以處理 CancelledError 異常,執行清理操作。

Future(期物)

通常情況下自己不應該創建期物,而只能由并發框架(concurrent.futures 或 asyncio)實例化。原因很簡單:期物表示終將發生的事情,而確定某件事會發生的唯一方式是執行的時間已經排定。

asyncio.Future

在 asyncio 包中, BaseEventLoop.create_task(…) 方法接收一個協程,排定它的運行時間,然后返回一個 asyncio.Task 實例——也是 asyncio.Future 類的實例,因為 Task 是Future 的子類,用于包裝協程。

asyncio.ensure_future(coro_or_future, *, loop=None)

這個函數統一了協程和期物:第一個參數可以是二者中的任何一個。如果是 Future 或 Task 對象,那就原封不動地返回。如果是協程,那么 async 函數會調用loop.create_task(…) 方法創建 Task 對象。 loop= 關鍵字參數是可選的,用于傳入事件循環;如果沒有傳入,那么 async 函數會通過調用 asyncio.get_event_loop() 函數獲取循環對象。

BaseEventLoop.create_task(coro)

這個方法排定協程的執行時間,返回一個 asyncio.Task 對象。

asyncio 包中有多個函數會自動把參數指定的協程包裝在 asyncio.Task 對象中,例如 BaseEventLoop.run_until_complete(…) 方法。

asyncio.as_completed

為了集成進度條,我們可以使用的是 as_completed 生成器函數;幸好, asyncio 包提供了這個生成器函數的相應版本。

使用asyncio和aiohttp包

從 Python 3.4 起, asyncio 包只直接支持 TCP 和 UDP。如果想使用 HTTP 或其他協議,那么要借助第三方包 aiohttp 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cc_list = ['China', 'USA']
 
@asyncio.coroutine
def get_flag(cc):
  url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
  resp = yield from aiohttp.request('GET', url)
  image = yield from resp.read()
  return image
 
@asyncio.coroutine
def download_one(name):
  image = yield from get_flag(name)
  save_flag(image, name.lower() + '.gif')
  return name
 
loop = asyncio.get_event_loop()
wait_coro = asyncio.wait([download_one(cc) for cc in sorted(cc_list)])
res, _ = loop.run_until_complete(wait_coro)
loop.close()

使用 asyncio 包時,我們編寫的異步代碼中包含由 asyncio 本身驅動的協程(即委派生成器),而生成器最終把職責委托給 asyncio 包或第三方庫(如aiohttp)中的協程。這種處理方式相當于架起了管道,讓 asyncio 事件循環(通過我們編寫的協程)驅動執行低層異步 I/O 操作的庫函數。

避免阻塞型調用

有兩種方法能避免阻塞型調用中止整個應用程序的進程:
1. 在單獨的線程中運行各個阻塞型操作
2. 把每個阻塞型操作轉換成非阻塞的異步調用使用

多個線程是可以的,但是各個操作系統線程(Python 使用的是這種線程)消耗的內存達兆字節(具體的量取決于操作系統種類)。如果要處理幾千個連接,而每個連接都使用一個線程的話,我們負擔不起。

把生成器當作協程使用是異步編程的另一種方式。對事件循環來說,調用回調與在暫停的協程上調用 .send() 方法效果差不多。各個暫停的協程是要消耗內存,但是比線程消耗的內存數量級小。

上面的腳本為什么會很快

在上面的腳本中,調用 loop.run_until_complete 方法時,事件循環驅動各個download_one 協程,運行到第一個 yield from 表達式處時,那個表達式驅動各個get_flag 協程,然后在get_flag協程里面運行到第一個 yield from 表達式處時,調用 aiohttp.request(…)函數。這些調用都不會阻塞,因此在零點幾秒內所有請求全部開始。

asyncio 的基礎設施獲得第一個響應后,事件循環把響應發給等待結果的 get_flag 協程。得到響應后, get_flag 向前執行到下一個 yield from 表達式處,調用resp.read() 方法,然后把控制權還給主循環。其他響應會陸續返回。所有 get_ flag 協程都獲得結果后,委派生成器 download_one 恢復,保存圖像文件。

async和await

為了簡化并更好地標識異步IO,從Python 3.5開始引入了新的語法async和await,可以讓coroutine的代碼更簡潔易讀。

async和await是針對coroutine的新語法,要使用新的語法,只需要做兩步簡單的替換。
1. 把@asyncio.coroutine替換為async
2. 把yield from替換為await

例如:

?
1
2
3
4
5
@asyncio.coroutine
def hello():
  print("Hello world!")
  r = yield from asyncio.sleep(1)
  print("Hello again!")

等同于

?
1
2
3
4
async def hello():
  print("Hello world!")
  r = await asyncio.sleep(1)
  print("Hello again!")

網站請求實例

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import asyncio
import aiohttp
 
urls = [
  'http://www.163.com/',
  'http://www.sina.com.cn/',
  'https://www.hupu.com/',
  'http://www.csdn.net/'
]
 
 
async def get_url_data(u):
  """
  讀取url的數據
  :param u:
  :return:
  """
  print('running ', u)
  async with aiohttp.ClientSession() as session:
    async with session.get(u) as resp:
      print(u, resp.status, type(resp.text()))
      # print(await resp.text())
 
  return resp.headers
 
 
async def request_url(u):
  """
  主調度函數
  :param u:
  :return:
  """
  res = await get_url_data(u)
  return res
 
 
loop = asyncio.get_event_loop()
task_lists = asyncio.wait([request_url(u) for u in urls])
all_res, _ = loop.run_until_complete(task_lists)
loop.close()
 
print(all_res)

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:http://blog.csdn.net/andybegin/article/details/77891053

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国内精品91久久久久 | 国产999在线观看 | 王的视频视ivk | 猥琐对着美女飞机喷到脸上 | 国产午夜视频在线观看网站 | 亚洲精品综合一二三区在线 | 99国产在线视频 | 91久久偷偷做嫩草影院免费看 | 18捆绑调教在线高清 | 精品国产在天天线在线麻豆 | 免费看又黄又爽又猛的视频软件- | 91香蕉视频网址 | 欧洲美女啪啪 | 高清国产在线观看 | 国产日韩一区二区 | 亚洲美洲国产日产 | 国产精品久久久久毛片 | 亚洲美色综合天天久久综合精品 | 出水小说 | 午夜日本大胆裸艺术 | 沉沦艳妇杨幂肉体小说 | 末发育xxxxx仙踪林 | 午夜AV亚洲一码二中文字幕青青 | 精品国产免费一区二区三区 | 天天操天天射天天爽 | 国产免费色视频 | 精品国产一区二区三区国产馆 | 国产精品亚洲专区在线播放 | 国产偷窥女洗浴在线观看亚洲 | 日日碰碰 | 国产精品日韩欧美一区二区 | 星空无限传媒xk8046 | 免费观看欧美一级高清 | 教练你好大轻点漫 | chinesespanking网站 | 香蕉eeww99国产在线观看 | 青青视频国产依人在线 | 国产成人v爽在线免播放观看 | 大胸纲手被羞羞漫画网站 | 美女做又爽又黄又猛 | 楚乔传第二部免费观看全集完整版 |