python2异步编程_最新Python异步编程详解
我們都知道對(duì)于I/O相關(guān)的程序來(lái)說(shuō),異步編程可以大幅度的提高系統(tǒng)的吞吐量,因?yàn)樵谀硞€(gè)I/O操作的讀寫(xiě)過(guò)程中,系統(tǒng)可以先去處理其它的操作(通常是其它的I/O操作),那么Python中是如何實(shí)現(xiàn)異步編程的呢?
簡(jiǎn)單的回答是Python通過(guò)協(xié)程(coroutine)來(lái)實(shí)現(xiàn)異步編程。那究竟啥是協(xié)程呢?這將是一個(gè)很長(zhǎng)的故事。
故事要從yield開(kāi)始說(shuō)起(已經(jīng)熟悉yield的讀者可以跳過(guò)這一節(jié))。
yield
yield是用來(lái)生成一個(gè)生成器的(Generator), 生成器又是什么呢?這又是一個(gè)長(zhǎng)長(zhǎng)的story,所以這次我建議您移步到這里:
完全理解Python迭代對(duì)象、迭代器、生成器,而關(guān)于yield是怎么回事,建議看這里:[翻譯]PYTHON中YIELD的解釋
好了,現(xiàn)在假設(shè)你已經(jīng)明白了yield和generator的概念了,請(qǐng)?jiān)徫疫@種不負(fù)責(zé)任的說(shuō)法但是這真的是一個(gè)很長(zhǎng)的story啊!
總的來(lái)說(shuō),yield相當(dāng)于return,它將相應(yīng)的值返回給調(diào)用next()或者send()的調(diào)用者,從而交出了cpu使用權(quán),而當(dāng)調(diào)用者再調(diào)用next()或者send()時(shí),又會(huì)返回到y(tǒng)ield中斷的地方,如果send有參數(shù),又會(huì)將參數(shù)返回給yield賦值的變量,如果沒(méi)有就跟next()一樣賦值為None。但是這里會(huì)遇到一個(gè)問(wèn)題,就是嵌套使用generator時(shí)外層的generator需要寫(xiě)大量代碼,看如下示例:
注意以下代碼均在Python3.6上運(yùn)行調(diào)試
#!/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()
為了簡(jiǎn)化,在Python3.3中引入了yield from
yield from
使用yield from有兩個(gè)好處,
可以將main中send的參數(shù)一直返回給最里層的generator,
同時(shí)我們也不需要再使用while循環(huán)和send (), next()來(lái)進(jìn)行迭代。
我們可以將上邊的代碼修改如下:
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()
執(zhí)行結(jié)果如下:
do something before coroutine start
1
2
3
4
5
6
7
8
9
10
這里inner_generator()中執(zhí)行的代碼片段我們實(shí)際就可以認(rèn)為是協(xié)程,所以總的來(lái)說(shuō)邏輯圖如下:
coroutine and wrapper
接下來(lái)我們就看下究竟協(xié)程是啥樣子
協(xié)程coroutine
協(xié)程的概念應(yīng)該是從進(jìn)程和線程演變而來(lái)的,他們都是獨(dú)立的執(zhí)行一段代碼,但是不同是線程比進(jìn)程要輕量級(jí),協(xié)程比線程還要輕量級(jí)。多線程在同一個(gè)進(jìn)程中執(zhí)行,而協(xié)程通常也是在一個(gè)線程當(dāng)中執(zhí)行。它們的關(guān)系圖如下:
process, thread and coroutine
我們都知道Python由于GIL(Global Interpreter Lock)原因,其線程效率并不高,并且在*nix系統(tǒng)中,創(chuàng)建線程的開(kāi)銷(xiāo)并不比進(jìn)程小,因此在并發(fā)操作時(shí),多線程的效率還是受到了很大制約的。所以后來(lái)人們發(fā)現(xiàn)通過(guò)yield來(lái)中斷代碼片段的執(zhí)行,同時(shí)交出了cpu的使用權(quán),于是協(xié)程的概念產(chǎn)生了。在Python3.4正式引入了協(xié)程的概念,代碼示例如下:
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引入兩個(gè)重要概念協(xié)程和事件循環(huán),
通過(guò)修飾符@asyncio.coroutine定義了一個(gè)協(xié)程,而通過(guò)event loop來(lái)執(zhí)行tasks中所有的協(xié)程任務(wù)。之后在Python3.5引入了新的async & await語(yǔ)法,從而有了原生協(xié)程的概念。
async & await
在Python3.5中,引入了aync&await 語(yǔ)法結(jié)構(gòu),通過(guò)"aync def"可以定義一個(gè)協(xié)程代碼片段,作用類(lèi)似于Python3.4中的@asyncio.coroutine修飾符,而await則相當(dāng)于"yield from"。
先來(lái)看一段代碼,這個(gè)是我剛開(kāi)始使用async&await語(yǔ)法時(shí),寫(xiě)的一段小程序。
#!/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())
這里會(huì)收到這樣的報(bào)錯(cuò):
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()函數(shù)返回的Response對(duì)象不能用于await表達(dá)式,可是如果不能用于await,還怎么樣來(lái)實(shí)現(xiàn)異步呢?
原來(lái)Python的await表達(dá)式是類(lèi)似于"yield from"的東西,但是await會(huì)去做參數(shù)檢查,它要求await表達(dá)式中的對(duì)象必須是awaitable的,那啥是awaitable呢? awaitable對(duì)象必須滿(mǎn)足如下條件中其中之一:
A native coroutine object returned from a native coroutine function .
原生協(xié)程對(duì)象
A generator-based coroutine object returned from a function decorated with types.coroutine() .
types.coroutine()修飾的基于生成器的協(xié)程對(duì)象,注意不是Python3.4中asyncio.coroutine
An object with an await method returning an iterator.
實(shí)現(xiàn)了await method,并在其中返回了iterator的對(duì)象
根據(jù)這些條件定義,我們可以修改代碼如下:
#!/usr/bin/env python
# encoding:utf-8
import asyncio
import requests
import time
async def download(url): # 通過(guò)async def定義的函數(shù)是原生的協(xié)程對(duì)象
print("get %s" % url)
response = requests.get(url)
print(response.status_code)
async def wait_download(url):
await download(url) # 這里download(url)就是一個(gè)原生的協(xié)程對(duì)象
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())
至此,程序可以運(yùn)行,不過(guò)仍然有一個(gè)問(wèn)題就是它并沒(méi)有真正地異步執(zhí)行 (這里要感謝網(wǎng)友荊棘花王朝,是Ta指出的這個(gè)問(wèn)題)
看一下運(yùn)行結(jié)果:
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
會(huì)發(fā)現(xiàn)程序始終是同步執(zhí)行的,這就說(shuō)明僅僅是把涉及I/O操作的代碼封裝到async當(dāng)中是不能實(shí)現(xiàn)異步執(zhí)行的。必須使用支持異步操作的非阻塞代碼才能實(shí)現(xiàn)真正的異步。目前支持非阻塞異步I/O的庫(kù)是aiohttp
#!/usr/bin/env python
# encoding:utf-8
import asyncio
import aiohttp
import time
async def download(url): # 通過(guò)async def定義的函數(shù)是原生的協(xié)程對(duì)象
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)就是一個(gè)原生的協(xié)程對(duì)象
# 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())
再看一下測(cè)試結(jié)果:
get: http://www.mi.com
get: http://www.163.com
get: http://www.baidu.com
200
200
200
Complete in 0.27292490005493164 seconds
可以看出這次是真正的異步了。
好了現(xiàn)在一個(gè)真正的實(shí)現(xiàn)了異步編程的小程序終于誕生了。
而目前更牛逼的異步是使用uvloop或者pyuv,這兩個(gè)最新的Python庫(kù)都是libuv實(shí)現(xiàn)的,可以提供更加高效的event loop。
uvloop和pyuv
關(guān)于uvloop可以參考uvloop
pyuv可以參考這里pyuv
pyuv實(shí)現(xiàn)了Python2.x和3.x,但是該項(xiàng)目在github上已經(jīng)許久沒(méi)有更新了,不知道是否還有人在維護(hù)。
uvloop只實(shí)現(xiàn)了3.x, 但是該項(xiàng)目在github上始終活躍。
它們的使用也非常簡(jiǎn)單,以u(píng)vloop為例,只需要添加以下代碼就可以了
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
關(guān)于Python異步編程到這里就告一段落了,而引出這篇文章的引子實(shí)際是關(guān)于網(wǎng)上有關(guān)Sanic和uvloop的組合創(chuàng)造的驚人的性能,感興趣的同學(xué)可以找下相關(guān)文章,也許后續(xù)我會(huì)再專(zhuān)門(mén)就此話題寫(xiě)一篇文章,歡迎交流!
總結(jié)
以上是生活随笔為你收集整理的python2异步编程_最新Python异步编程详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 对称加密与非对称加密的区别_https原
- 下一篇: 蓝宝石 470 原版 bios_想怎么玩