python2异步编程_最新Python异步编程详解
我們都知道對于I/O相關的程序來說,異步編程可以大幅度的提高系統的吞吐量,因為在某個I/O操作的讀寫過程中,系統可以先去處理其它的操作(通常是其它的I/O操作),那么Python中是如何實現異步編程的呢?
簡單的回答是Python通過協程(coroutine)來實現異步編程。那究竟啥是協程呢?這將是一個很長的故事。
故事要從yield開始說起(已經熟悉yield的讀者可以跳過這一節)。
yield
yield是用來生成一個生成器的(Generator), 生成器又是什么呢?這又是一個長長的story,所以這次我建議您移步到這里:
完全理解Python迭代對象、迭代器、生成器,而關于yield是怎么回事,建議看這里:[翻譯]PYTHON中YIELD的解釋
好了,現在假設你已經明白了yield和generator的概念了,請原諒我這種不負責任的說法但是這真的是一個很長的story啊!
總的來說,yield相當于return,它將相應的值返回給調用next()或者send()的調用者,從而交出了cpu使用權,而當調用者再調用next()或者send()時,又會返回到yield中斷的地方,如果send有參數,又會將參數返回給yield賦值的變量,如果沒有就跟next()一樣賦值為None。但是這里會遇到一個問題,就是嵌套使用generator時外層的generator需要寫大量代碼,看如下示例:
注意以下代碼均在Python3.6上運行調試
#!/usr/bin/env python
# encoding:utf-8
def inner_generator():
i = 0
while True:
i = yield i
if i > 10:
raise StopIteration
def outer_generator():
print("do something before yield")
from_inner = 0
from_outer = 1
g = inner_generator()
g.send(None)
while 1:
try:
from_inner = g.send(from_outer)
from_outer = yield from_inner
except StopIteration:
break
def main():
g = outer_generator()
g.send(None)
i = 0
while 1:
try:
i = g.send(i + 1)
print(i)
except StopIteration:
break
if __name__ == '__main__':
main()
為了簡化,在Python3.3中引入了yield from
yield from
使用yield from有兩個好處,
可以將main中send的參數一直返回給最里層的generator,
同時我們也不需要再使用while循環和send (), next()來進行迭代。
我們可以將上邊的代碼修改如下:
def inner_generator():
i = 0
while True:
i = yield i
if i > 10:
raise StopIteration
def outer_generator():
print("do something before coroutine start")
yield from inner_generator()
def main():
g = outer_generator()
g.send(None)
i = 0
while 1:
try:
i = g.send(i + 1)
print(i)
except StopIteration:
break
if __name__ == '__main__':
main()
執行結果如下:
do something before coroutine start
1
2
3
4
5
6
7
8
9
10
這里inner_generator()中執行的代碼片段我們實際就可以認為是協程,所以總的來說邏輯圖如下:
coroutine and wrapper
接下來我們就看下究竟協程是啥樣子
協程coroutine
協程的概念應該是從進程和線程演變而來的,他們都是獨立的執行一段代碼,但是不同是線程比進程要輕量級,協程比線程還要輕量級。多線程在同一個進程中執行,而協程通常也是在一個線程當中執行。它們的關系圖如下:
process, thread and coroutine
我們都知道Python由于GIL(Global Interpreter Lock)原因,其線程效率并不高,并且在*nix系統中,創建線程的開銷并不比進程小,因此在并發操作時,多線程的效率還是受到了很大制約的。所以后來人們發現通過yield來中斷代碼片段的執行,同時交出了cpu的使用權,于是協程的概念產生了。在Python3.4正式引入了協程的概念,代碼示例如下:
import asyncio
# Borrowed from http://curio.readthedocs.org/en/latest/tutorial.html.
@asyncio.coroutine
def countdown(number, n):
while n > 0:
print('T-minus', n, '({})'.format(number))
yield from asyncio.sleep(1)
n -= 1
loop = asyncio.get_event_loop()
tasks = [
asyncio.ensure_future(countdown("A", 2)),
asyncio.ensure_future(countdown("B", 3))]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
示例顯示了在Python3.4引入兩個重要概念協程和事件循環,
通過修飾符@asyncio.coroutine定義了一個協程,而通過event loop來執行tasks中所有的協程任務。之后在Python3.5引入了新的async & await語法,從而有了原生協程的概念。
async & await
在Python3.5中,引入了aync&await 語法結構,通過"aync def"可以定義一個協程代碼片段,作用類似于Python3.4中的@asyncio.coroutine修飾符,而await則相當于"yield from"。
先來看一段代碼,這個是我剛開始使用async&await語法時,寫的一段小程序。
#!/usr/bin/env python
# encoding:utf-8
import asyncio
import requests
import time
async def wait_download(url):
response = await requests.get(url)
print("get {} response complete.".format(url))
async def main():
start = time.time()
await asyncio.wait([
wait_download("http://www.163.com"),
wait_download("http://www.mi.com"),
wait_download("http://www.google.com")])
end = time.time()
print("Complete in {} seconds".format(end - start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
這里會收到這樣的報錯:
Task exception was never retrieved
future: exception=TypeError("object Response can't be used in 'await' expression",)>
Traceback (most recent call last):
File "asynctest.py", line 10, in wait_download
data = await requests.get(url)
TypeError: object Response can't be used in 'await' expression
這是由于requests.get()函數返回的Response對象不能用于await表達式,可是如果不能用于await,還怎么樣來實現異步呢?
原來Python的await表達式是類似于"yield from"的東西,但是await會去做參數檢查,它要求await表達式中的對象必須是awaitable的,那啥是awaitable呢? awaitable對象必須滿足如下條件中其中之一:
A native coroutine object returned from a native coroutine function .
原生協程對象
A generator-based coroutine object returned from a function decorated with types.coroutine() .
types.coroutine()修飾的基于生成器的協程對象,注意不是Python3.4中asyncio.coroutine
An object with an await method returning an iterator.
實現了await method,并在其中返回了iterator的對象
根據這些條件定義,我們可以修改代碼如下:
#!/usr/bin/env python
# encoding:utf-8
import asyncio
import requests
import time
async def download(url): # 通過async def定義的函數是原生的協程對象
print("get %s" % url)
response = requests.get(url)
print(response.status_code)
async def wait_download(url):
await download(url) # 這里download(url)就是一個原生的協程對象
print("get {} data complete.".format(url))
async def main():
start = time.time()
await asyncio.wait([
wait_download("http://www.163.com"),
wait_download("http://www.mi.com"),
wait_download("http://www.baidu.com")])
end = time.time()
print("Complete in {} seconds".format(end - start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
至此,程序可以運行,不過仍然有一個問題就是它并沒有真正地異步執行 (這里要感謝網友荊棘花王朝,是Ta指出的這個問題)
看一下運行結果:
get http://www.163.com
200
get http://www.163.com data complete.
get http://www.baidu.com
200
get http://www.baidu.com data complete.
get http://www.mi.com
200
get http://www.mi.com data complete.
Complete in 0.49027466773986816 seconds
會發現程序始終是同步執行的,這就說明僅僅是把涉及I/O操作的代碼封裝到async當中是不能實現異步執行的。必須使用支持異步操作的非阻塞代碼才能實現真正的異步。目前支持非阻塞異步I/O的庫是aiohttp
#!/usr/bin/env python
# encoding:utf-8
import asyncio
import aiohttp
import time
async def download(url): # 通過async def定義的函數是原生的協程對象
print("get: %s" % url)
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(resp.status)
# response = await resp.read()
# 此處的封裝不再需要
# async def wait_download(url):
# await download(url) # 這里download(url)就是一個原生的協程對象
# print("get {} data complete.".format(url))
async def main():
start = time.time()
await asyncio.wait([
download("http://www.163.com"),
download("http://www.mi.com"),
download("http://www.baidu.com")])
end = time.time()
print("Complete in {} seconds".format(end - start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
再看一下測試結果:
get: http://www.mi.com
get: http://www.163.com
get: http://www.baidu.com
200
200
200
Complete in 0.27292490005493164 seconds
可以看出這次是真正的異步了。
好了現在一個真正的實現了異步編程的小程序終于誕生了。
而目前更牛逼的異步是使用uvloop或者pyuv,這兩個最新的Python庫都是libuv實現的,可以提供更加高效的event loop。
uvloop和pyuv
關于uvloop可以參考uvloop
pyuv可以參考這里pyuv
pyuv實現了Python2.x和3.x,但是該項目在github上已經許久沒有更新了,不知道是否還有人在維護。
uvloop只實現了3.x, 但是該項目在github上始終活躍。
它們的使用也非常簡單,以uvloop為例,只需要添加以下代碼就可以了
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
關于Python異步編程到這里就告一段落了,而引出這篇文章的引子實際是關于網上有關Sanic和uvloop的組合創造的驚人的性能,感興趣的同學可以找下相關文章,也許后續我會再專門就此話題寫一篇文章,歡迎交流!
總結
以上是生活随笔為你收集整理的python2异步编程_最新Python异步编程详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对称加密与非对称加密的区别_https原
- 下一篇: 蓝宝石 470 原版 bios_想怎么玩