Python并发之协程asyncio(7)

在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块。

asyncio在python3.4后被内置在python中,使得python的协程创建变得更加方便。

Python 3.5添加了async和await这两个关键字,分别用来替换asyncio.coroutineyield from。自此,协程成为新的语法,而不再是一种生成器类型了。事件循环与协程的引入,可以极大提高高负载下程序的I/O性能。除此之外还增加了async with(异步上下文管理)、async for(异步迭代器)语法。特别说的是,在新发布的Python 3.6里面终于可以用异步生成器了

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

1,asyncio基本用法

同步/异步的概念:

同步:是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行

异步:是和同步相对的,异步是指在处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、通知、回调来通知调用者处理结果。

代码示例:(同步)

import time

def hello():
    time.sleep(1)

def run():
    for i in range(5):
        hello()
        print('Hello World:%s' % time.time())  # 任何伟大的代码都是从Hello World 开始的!
if __name__ == '__main__':
    run()

输出:(间隔约是1s)

Hello World:1548574672.5149598
Hello World:1548574673.5190861
Hello World:1548574674.524205
Hello World:1548574675.528661
Hello World:1548574676.532924

改为异步执行,主要包含以下步骤:

  • 定义异步函数返回异步任务coroutine对象,异步操作需要在coroutine中通过yield from完成
  • 将coroutine对象添加进入事件循环,即把异步的任务丢给这个循环的run_until_complete()。多个coroutine可以封装成一组Task然后并发执行
  • 主线程创建事件循环,调用asyncio.get_event_loop()
  • 关闭事件循环
import time
import asyncio

# 定义异步函数
@asyncio.coroutine
def hello():
    '''
    yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。
    当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。
    把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行

    '''
    r = yield from asyncio.sleep(1)
    print('Hello World:%s' % time.time())

def run():
    for i in range(5):
        loop.run_until_complete(hello()) #将协程对象添加到事件循环,运行直到结束

loop = asyncio.get_event_loop() #主线程调用asyncio.get_event_loop()时会创建事件循环
if __name__ =='__main__':
    run()

运行输出:

Hello World:1548575242.072916
Hello World:1548575243.0778098
Hello World:1548575244.080589
Hello World:1548575245.0845451
Hello World:1548575246.0891478

改为多任务执行:

import time
import asyncio

# 定义异步函数
@asyncio.coroutine
def hello():
    '''
    yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。
    当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。
    把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行

    '''
    r = yield from asyncio.sleep(1)
    print('Hello World:%s' % time.time())

def run():
    for i in range(3):
        tasks = [hello(), hello()]
        loop.run_until_complete(asyncio.wait(tasks)) #将协程对象添加到事件循环,运行直到结束

if __name__ =='__main__':
    loop = asyncio.get_event_loop()  # 主线程调用asyncio.get_event_loop()时会创建事件循环
    run()
    loop.close()

输出:(两个任务并行执行)

Hello World:1548576104.507673
Hello World:1548576104.507762
Hello World:1548576105.511091
Hello World:1548576105.5111492
Hello World:1548576106.5158
Hello World:1548576106.5158558

2,async和await

asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。

为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法asyncawait,可以让coroutine的代码更简洁易读。

async:

async关键字可以定义一个协程对象,被async修饰的函数变成了一个协程对象而不是一个普通的函数。被async修饰的函数调用后会生成协程函数,可以通过send唤醒执行。

import os
import asyncio
async def target_func1():
    print('the func start')
    print(os.getpid())
    print('the func end')
coroutine = target_func1()

try:
    coroutine.send(None) # 唤醒协程
except StopIteration:
    print('xx')
    coroutine.close() # 关闭

await:

await用于控制事件的执行顺序,它只能在异步函数中使用,即被async关键字定义的协程函数,否则报错。当执行到await时,当前协程挂起,转而去执行await后面的协程,完毕后再回到当前协程继续往下。

import time
import asyncio
# async 关键字定义一个协程
async def target_func1():
    print('the func start')
    x = await target_func2() #当前协程挂起
    print(x)
    print('the func end')
    return 1

async def target_func2():
    """
    目标函数2
    :return:
    """
    time.sleep(2)
    print('the func end2')
    return 0

注意:asyncawait是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:

  • @asyncio.coroutine替换为async
  • yield from替换为await

上面的代码示例修改为:

import time
import asyncio

# 定义异步函数
async def hello():
    asyncio.sleep(1)
    print('Hello World:%s' % time.time())

def run():
    for i in range(5):
        loop.run_until_complete(hello())

loop = asyncio.get_event_loop()
if __name__ =='__main__':
    run()

asyncio主要方法:

asyncio.get_event_loop():创建一个事件循环,所有的异步函数都需要在事件循环中运行;
asyncio.ensure_future():创建一个任务
asyncio.gather(*fs):添加并行任务
asyncio.wait(fs):添加并行任务,可以是列表
loop.run_until_complete(func):添加协程函数同时启动阻塞直到结束
loop.run_forever():运行事件无限循环,直到stop被调用
loop.create_task():创建一个任务并添加到循环
loop.close():关闭循环
loop.time():循环开始后到当下的时间
loop.stop():停止循环
loop.is_closed() # 判断循环是否关闭
loop.create_future():创建一个future对象,推荐使用这个函数而不要直接创建future实例
loop.call_soon() # 设置回调函数,不能接受返回的参数,需要用到future对象,立即回调
loop.call_soon_threadsafe() # 线程安全的对象
loop.call_later() # 异步返回后开始算起,延迟回调
loop.call_at() # 循环开始多少s回调
loop.call_exception_handler() # 错误处理

主要的类:

Future:主要用来保存任务的状态;
Task:Future的子类,扩展了Future的功能;

# Future
from asyncio import Future
# future = Future()
# future.result() # 获取任务的结果
# future.remove_done_callback(fn) # 删除所有的回调函数并返回个数
# future.set_result('result') # 设置任务的结果,必须在result()之前执行,否则报错
# future.exception() # 获取任务的错误信息
# future.set_exception('bad') # 设置任务的错误信息
# future.add_done_callback('fn') # 添加回调函数

# Task
current_task():返回循环当前的任务,类方法
all_tasks():返回事件循环所有的任务
get_stack():获取其他协程的堆栈列表
print_stack:输出其他协程的堆栈列表
cancel:取消任务

2.1 添加多个任务到事件循环

使用asyncio.gather(*fs):添加并行任务到事件循环

import asyncio
import os

# async 关键字定义一个协程
async def target_func1(name):
    print('the func start')
    print('PID: {} with name: {}'.format(os.getpid(), name))
    print('the func end')
    return name

def run():
    loop = asyncio.get_event_loop() # 创建一个事件循环
    taks_list = [target_func1("A"), target_func1("B"), target_func1("C")]
    rest = loop.run_until_complete(asyncio.gather(*taks_list)) #添加并行任务
    print(rest) # 等待返回结果,一个列表,按照事件添加的顺序,但是计算的顺序是不定的
    loop.close()

if __name__ == '__main__':
    run()

运行结果:

the func start
PID: 9435 with name: A
the func end
the func start
PID: 9435 with name: C
the func end
the func start
PID: 9435 with name: B
the func end
['A', 'B', 'C']

2.2  run_forever启动循环获取异步计算结果

run_forever()不能直接得到异步函数的返回结果,需要使用Future类来作为第三方保存结果,同时设置回调函数;

import time
import asyncio
from asyncio import Future
from functools import partial

async def target_func0(name, future):
    """
    目标函数2
    :return:
    """
    time.sleep(1)
    print(name)
    future.set_result(name) # 替换return,设置返回结果

def got_result(loop, future):
    print(future.result()) # 处理结果
    loop.stop() #循环停止

def run():
    loop = asyncio.get_event_loop()
    future = Future(loop=loop)
    res = asyncio.ensure_future(target_func0('A', future)) # 生成一个Task任务
    print(res)
    future.add_done_callback(partial(got_result, loop)) # 回调函数默认只能有一个参数future,必须使用偏函数
    # print(future.result()) # future上下文必须先调用future.set_result。
    loop.run_forever()
    loop.close()

if __name__ == '__main__':
    run()

运行结果:

>
A
A

2.3  链协程

协程里调用等待另外的协程完成后才能返回

import time, os
import asyncio

async def target_func1(): # async 关键字定义一个协程
    print('the func1 start')
    x = await target_func2() #当前协程挂起
    print(x)
    print('the func1 end')
    return 1

async def target_func2():
    """
    目标函数2
    :return:
    """
    time.sleep(2)
    print('the func end2')
    return 0

def run():
    # 创建一个协程对象
    coroutine = target_func1()
    # 创建一个事件循环
    loop = asyncio.get_event_loop()
    loop.run_until_complete(coroutine) # 将协程对象添加到事件循环,运行直到结束
    print(os.getpid())
    loop.close() # 关闭事件循环

if __name__ == '__main__':
    run()

运行结果:

the func1 start
the func end2
0
the func1 end
13584

2.4 回调函数

import asyncio
from functools import partial

# async 关键字定义一个协程
async def target_func1():
    print('the func end')
    return 1

def get_res(loop):
    print('xxxx')
    loop.stop()

def run():
    loop = asyncio.get_event_loop() #创建一个事件循环
    resp = loop.create_task(target_func1()) #创建一个任务并添加到循环
    loop.call_soon(partial(get_res, loop)) #设置回调函数,不能接受返回的参数,需要用到future对象
    # loop.call_soon_threadsafe() # 线程安全的对象
    # loop.call_later(delay=5, callback=partial(get_res, loop)) # 异步返回后开始算起,延迟5秒回调
    # loop.call_at(when=8000,callback=partial(get_res, loop)) # 循环开始第8秒回调
    # loop.call_exception_handler() # 错误处理
    loop.run_forever()
    print(resp.result())
    loop.close()

运行结果:

the func end
xxxx
1

3,异步网络IO库aiohttp

aiohttp库,用来实现异步网页请求等功能,就是异步版的requests库。

关于aiohttp库我们另起一个章节专门介绍:https://blog.csdn.net/biheyu828/article/details/87896507

基本用法如下:

import asyncio
import aiohttp
from aiohttp import ClientSession


async def hello(url):
    async with ClientSession() as session: ##获取session
        async with session.get(url) as resp:
            print(resp.status)
            resp = await resp.text()
            print(resp)

loop = asyncio.get_event_loop()
url = "http://httpbin.org/get"
loop.run_until_complete(hello(url))

4,协程queue

asyncio模块也有自己的queue实现生产消费模式,只要有三种队列:Queue(先进先出),PriorityQueue(优先级队列),LifoQueue(栈),但是Queue不是线程安全的类,也就是说在多进程或多线程的情况下不要使用这个队列。

import asyncio
from asyncio import Queue

async def producer(queue):
    for i in range(10):
        await queue.put(i)

async def consumer(queue):
    for i in range(10):
        data = await queue.get()
        print(data)

def run():
    loop = asyncio.get_event_loop()
    q = Queue(10)
    tasks = [producer(q), consumer(q)]
    loop.run_until_complete(asyncio.gather(*tasks))
    loop.close()

if __name__ == '__main__':
    run()

Queue的get(),join(),put()方法返回的都是协程,需要使用await关键字。

 

参考文献:

https://www.cnblogs.com/shenh/p/9090586.html

https://www.cnblogs.com/aademeng/articles/7241485.html

https://www.cnblogs.com/cwp-bg/p/9590700.html

你可能感兴趣的:(Python,并发,asyncio,异步编程)