【Python學習之九】asyncio—異步IO

asyncio

  這是python3.4引入的標準庫,直接內置對異步IO的支持。asyncio的編程模型就是一個消息循環。從asyncio模塊中直接獲取一個EventLoop的引用,而後把須要執行的協程扔到EventLoop中執行,就實現了異步IO。python

協程

  子程序,或者稱爲函數。在全部語言中都是層級調用,好比A調用B,B在執行過程當中又調用了C,C執行完畢返回,B執行完畢返回,最後是A執行完畢。子程序調用是經過棧實現的,一個線程就是執行一個子程序。子程序調用老是一個入口,一次返回,調用順序是明確的。而協程的調用和子程序不一樣。編程

協程的定義

  協程看上去是子程序,可是有很大的不一樣。協程,執行過程當中,在內部可中斷,而後轉而執行別的程序,在適當的時候再返回來接着執行。在一個子程序中中斷,去執行其餘子程序,不是函數調用,有點相似CPU的中斷。下面舉個例子:網絡

def A():
    print('1')
    print('2')
    print('3')

def B():
    print('x')
    print('y')
    print('z')

假設由協程執行,在執行A的過程當中,能夠隨時中斷,去執行B,B也可能在執行過程當中中斷再去執行A,結果多是:多線程

1
2
x
y
3
z

可是在A中是沒有調用B的,因此不是函數的調用中斷。所以,協程的調用比函數調用理解起來要難一些。併發

協程的優點

  協程最大的優點就是極高的執行效率。由於子程序切換不是線程切換,而是由程序自身控制,所以,沒有線程切換的開銷。第二大優點就是不須要多線程的鎖機制,由於只有一個線程,也不存在同時寫變量衝突,在協程中控制共享資源不加鎖,只須要判斷狀態就行了,因此執行效率比多線程高不少。異步

  協程利用多個CPU的方法是,多進程+協程,既充分利用多核,又充分發揮協程的高效率,可得到極高的性能。socket

協程的實現

  Python對協程的支持是經過generator(帶有yield的函數)實現的。在generator中,咱們不但能夠經過for循環來迭代,還能夠不斷調用next()函數獲取由yield語句返回的下一個值。Python的yield不但能夠返回一個值,它還能夠接收調用者發出的參數。看個實例:async

def consumer():
    r = 'k'  # 定義一個空字符串
    while True:  # 設定一個循環
        # 若是不調用consumer的send方法傳入其參數給n,n將爲None
        n = yield r
        if not n:  # 若是知足條件,表示方法外並未調用send
            return  # 執行return,退出方法,返回空值
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'


def produce(c):
    x = c.send(None)  # 啓動生成器,至關於調用了next(c)
    print("This is qidong:", x)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        # 生產了東西,經過c.send(n)切換到consumer執行
        r = c.send(n)
        # consumer經過yield拿到消息,處理,又經過yield把結果傳回
        print('[PRODUCER] Consumer return: %s' % r)
        # produce拿到consumer處理的結果,繼續生產下一條消息
    # produce決定不生產了,經過c.close()關閉consumer,整個過程結束
    c.close()


c = consumer()
produce(c)

從實例能夠看出來,整個生產者消費者的流程是無鎖的,只由一個線程執行,produceconsumer協做完成任務,因此稱爲「協程」,而非線程的搶佔式多任務。函數

asyncio實例

  下面是用asyncio實現Hello world:oop

import asyncio


# @asyncio.coroutine把一個generator標記爲coroutine(協程)類型
@asyncio.coroutine
def hello():
    print("Hello world!")
    # 異步調用asyncio.sleep(1):
    # yield from語法可讓咱們方便地調用另外一個generator
    # asyncio.sleep()也是一個coroutine
    # 線程不會等待asyncio.sleep(),而是直接中斷並執行下一個消息循環
    # 把asyncio.sleep(1)當作是一個耗時1秒的IO操做,在此期間,
    # 主線程並未等待,而是去執行EventLoop中其餘能夠執行的coroutine了,所以能夠實現併發執行
    yield from asyncio.sleep(1)
    print("Hello again!")


# 從asyncio模塊中直接獲取EventLoop
loop = asyncio.get_event_loop()
# 執行coroutine
loop.run_until_complete(hello())
loop.close()

用Task封裝兩個coroutine試試:

import asyncio
import threading


@asyncio.coroutine
def hello():
    print('Hello world! (%s)' % threading.currentThread())
    yield from asyncio.sleep(1)
    print('Hello again! (%s)' % threading.currentThread())


# 從asyncio模塊中直接獲取EventLoop
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

運行結果是:

Hello world! (<_MainThread(MainThread, started 7116)>)
Hello world! (<_MainThread(MainThread, started 7116)>)
Hello again! (<_MainThread(MainThread, started 7116)>)
Hello again! (<_MainThread(MainThread, started 7116)>)

能夠看出來,兩個協程是由同一個線程併發執行的。把asyncio.sleep()換成真正的IO操做,則多個coroutine就能夠由一個線程併發執行。

asyncio網絡實例

  用asyncio的異步網絡鏈接來獲取sina、sohu和163的網站首頁:

import asyncio


@asyncio.coroutine
def wget(host):
    print('wget %s...' % host)
    connect = asyncio.open_connection(host, 80)
    reader, writer = yield from connect
    header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
    writer.write(header.encode('utf-8'))
    # drain()在事件循環中刷新緩衝區,特別是在數據量很大的狀況下,保證數據完整性
    yield from writer.drain()
    while True:
        line = yield from reader.readline()
        if line == b'\r\n':
            break
        print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
    # Ignore the body, close the socket
    writer.close()


loop = asyncio.get_event_loop()
tasks = [wget(host)
         for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

可見3個鏈接由一個線程經過coroutine併發完成。

總結 

  asyncio模塊提供了完善的異步IO支持。異步操做須要在coroutine(協程)中經過yield from完成。多個coroutine能夠封裝成一組Task而後併發執行。

  注意在python3.5以後,能夠把@asyncio.coroutine替換爲async,yield from替換爲await。使代碼更簡潔易懂。

相關文章
相關標籤/搜索