很多朋友對異步編程都處於“聽說很強大”的認知狀態。鮮有在生產項目中使用它。而使用它的同學,則大多數都停留在知道如何使用 Tornado、Twisted、Gevent 這類異步框架上,出現各種古怪的問題難以解決。而且使用了異步框架的部分同學,由於用法不對,感覺它並沒牛逼到哪裏去,所以很多同學做 Web 後端服務時還是採用 Flask、Django等傳統的非異步框架。
從上兩屆 PyCon 技術大會看來,異步編程已經成了 Python 生態下一階段的主旋律。如新興的 Go、Rust、Elixir 等編程語言都將其支持異步和高併發作爲主要“賣點”,技術變化趨勢如此。Python 生態爲不落人後,從2013年起由 Python 之父 Guido 親自操刀主持了Tulip(asyncio)項目的開發。
異步io的好處在於避免的線程的開銷和切換,而且我們都知道python其實是沒有多線程的,只是通過底層線層鎖實現的多線程。另一個好處在於避免io操作(包含網絡傳輸)的堵塞時間。
asyncio可以實現單線程併發IO操作。如果僅用在客戶端,發揮的威力不大。如果把asyncio用在服務器端,例如Web服務器,由於HTTP連接就是IO操作,因此可以用單線程+coroutine實現多用戶的高併發支持。
asyncio實現了TCP、UDP、SSL等協議,aiohttp則是基於asyncio實現的HTTP框架。
對於異步io你需要知道的重點,要注意的是,await語法只能出現在通過async修飾的函數中,否則會報SyntaxError錯誤。而且await後面的對象需要是一個Awaitable,或者實現了相關的協議。
注意:
所有需要異步執行的函數,都需要asyncio中的輪訓器去輪訓執行,如果函數阻塞,輪訓器就會去執行下一個函數。所以所有需要異步執行的函數都需要加入到這個輪訓器中。
例如:
import requests
import time
import asyncio
# 創建一個異步函數
async def task_func():
await asyncio.sleep(1)
resp = requests.get('http://192.168.2.177:5002/')
print('2222222',time.time(),resp.text)
async def main(loop):
loop=asyncio.get_event_loop() # 獲取全局輪訓器
task = loop.create_task(task_func()) # 在全局輪訓器加入協成,只有加入全局輪訓器才能被監督執行
await asyncio.sleep(2) # 等待兩秒爲了不要立即執行event_loop.close(),項目中event_loop應該是永不停歇的
print('11111111111',time.time())
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(event_loop))
finally:
event_loop.close() # 當輪訓器關閉以後,所有沒有執行完成的協成將全部關閉
下面是aiohttp作爲服務器端的一個簡單的demo。
#!/usr/bin/env python3
import argparse
from aiohttp import web
import asyncio
import base64
import logging
import uvloop
import time,datetime
import json
import requests
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
routes = web.RouteTableDef()
@routes.get('/')
async def hello(request):
return web.Response(text="Hello, world")
# 定義一個路由映射,接收網址參數,post方式
@routes.post('/demo1/{name}')
async def demo1(request): # 異步監聽,只要一有握手就開始觸發,此時網址參數中的name就已經知道了,但是前端可能還沒有完全post完數據。
name = request.match_info.get('name', "Anonymous") # 獲取name
print(datetime.datetime.now()) # 觸發視圖函數的時間
data = await request.post() # 等待post數據完成接收,只有接收完成才能進行後續操作.data['key']獲取參數
print(datetime.datetime.now()) # 接收post數據完成的時間
logging.info('safety dect request start %s' % datetime.datetime.now())
result = {'name':name,'key':data['key']}
logging.info('safety dect request finish %s, %s' % (datetime.datetime.now(),json.dumps(result)))
return web.json_response(result)
# 定義一個路由映射,設計到io操作
@routes.post('/demo2')
async def demo2(request): # 異步監聽,只要一有握手就開始觸發,此時網址參數中的name就已經知道了,但是前端可能還沒有完全post完數據。
data = await request.post() # 等待post數據完成接收,只有接收完成才能進行後續操作.data['key']獲取參數
logging.info('safety dect request start %s' % datetime.datetime.now())
res = requests.post('http://www.baidu.com') # 網路id,會自動切換到其他協成上
logging.info('safety dect request finish %s' % res.test)
return web.Response(text="welcome")
if __name__ == '__main__':
logging.info('server start')
app = web.Application()
app.add_routes(routes)
web.run_app(app,host='0.0.0.0',port=8080)
logging.info('server close')
aiohttp的另一個主要作用是作爲異步客戶端,用來解決高併發請求的情況。比如現在我要模擬一個高併發請求來測試我的服務器負載情況。所以需要在python裏模擬高併發。高併發可以有多種方式,比如多線程,但是由於python本質上是沒有多線程的,通過底層線程鎖實現的多線程。在模型高併發時,具有線程切換和線程開銷的損耗。所以我們就可以使用多協成來實現高併發。
我們就可以使用aiohttp來模擬高併發客戶端。demo如下,用來模擬多個客戶端向指定服務器post圖片。
這裏寫代碼片