python 并发专题(十三):asyncio (二) 协程中的多任务
. 本文目录#
- 协程中的并发
- 协程中的嵌套
- 协程中的状态
- gather与wait
. 协程中的并发#
协程的并发,和线程一样。举个例子来说,就好像 一个人同时吃三个馒头,咬了第一个馒头一口,就得等这口咽下去,才能去啃第其他两个馒头。就这样交替换着吃。
asyncio
实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。
第一步,当然是创建多个协程的列表。
# 协程函数
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x) # 协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4) # 将协程转成task,并组成list
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
第二步,如何将这些协程注册到事件循环中呢。
有两种方法,至于这两种方法什么区别,稍后会介绍。
- 使用
asyncio.wait()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
- 使用
asyncio.gather()
# 千万注意,这里的 「*」 不能省略
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))
最后,return的结果,可以用task.result()
查看。
for task in tasks:
print('Task ret: ', task.result())
完整代码如下
import asyncio # 协程函数
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x) # 协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4) # 将协程转成task,并组成list
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
] loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks)) for task in tasks:
print('Task ret: ', task.result())
输出结果
Waiting: 1
Waiting: 2
Waiting: 4
Task ret: Done after 1s
Task ret: Done after 2s
Task ret: Done after 4s
协程中的嵌套#
使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。
来看个例子。
import asyncio # 用于内部的协程函数
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x) # 外部的协程函数
async def main():
# 创建三个协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4) # 将协程转为task,并组成list
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
] # 【重点】:await 一个task列表(协程)
# dones:表示已经完成的任务
# pendings:表示未完成的任务
dones, pendings = await asyncio.wait(tasks) for task in dones:
print('Task ret: ', task.result()) loop = asyncio.get_event_loop()
loop.run_until_complete(main())
如果这边,使用的是asyncio.gather()
,是这么用的
# 注意这边返回结果,与await不一样 results = await asyncio.gather(*tasks)
for result in results:
print('Task ret: ', result)
输出还是一样的。
Waiting: 1
Waiting: 2
Waiting: 4
Task ret: Done after 1s
Task ret: Done after 2s
Task ret: Done after 4s
仔细查看,可以发现这个例子完全是由 上面「协程中的并发
」例子改编而来。结果完全一样。只是把创建协程对象,转换task任务,封装成在一个协程函数里而已。外部的协程,嵌套了一个内部的协程。
其实你如果去看下asyncio.await()
的源码的话,你会发现下面这种写法
loop.run_until_complete(asyncio.wait(tasks))
看似没有嵌套,实际上内部也是嵌套的。
这里也把源码,贴出来,有兴趣可以看下,没兴趣,可以直接跳过。
# 内部协程函数
async def _wait(fs, timeout, return_when, loop):
assert fs, 'Set of Futures is empty.'
waiter = loop.create_future()
timeout_handle = None
if timeout is not None:
timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
counter = len(fs) def _on_completion(f):
nonlocal counter
counter -= 1
if (counter <= 0 or
return_when == FIRST_COMPLETED or
return_when == FIRST_EXCEPTION and (not f.cancelled() and
f.exception() is not None)):
if timeout_handle is not None:
timeout_handle.cancel()
if not waiter.done():
waiter.set_result(None) for f in fs:
f.add_done_callback(_on_completion) try:
await waiter
finally:
if timeout_handle is not None:
timeout_handle.cancel() done, pending = set(), set()
for f in fs:
f.remove_done_callback(_on_completion)
if f.done():
done.add(f)
else:
pending.add(f)
return done, pending # 外部协程函数
async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
if futures.isfuture(fs) or coroutines.iscoroutine(fs):
raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
if not fs:
raise ValueError('Set of coroutines/Futures is empty.')
if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
raise ValueError(f'Invalid return_when value: {return_when}') if loop is None:
loop = events.get_event_loop() fs = {ensure_future(f, loop=loop) for f in set(fs)}
# 【重点】:await一个内部协程
return await _wait(fs, timeout, return_when, loop)
. 协程中的状态#
还记得我们在讲生成器的时候,有提及过生成器的状态。同样,在协程这里,我们也了解一下协程(准确的说,应该是Future对象,或者Task任务)有哪些状态。
Pending
:创建future,还未执行Running
:事件循环正在调用执行任务Done
:任务执行完毕Cancelled
:Task被取消后的状态
可手工 python3 xx.py
执行这段代码,
import asyncio
import threading
import time async def hello():
print("Running in the loop...")
flag = 0
while flag < 1000:
with open("F:\\test.txt", "a") as f:
f.write("------")
flag += 1
print("Stop the loop") if __name__ == '__main__':
coroutine = hello()
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine) # Pending:未执行状态
print(task)
try:
t1 = threading.Thread(target=loop.run_until_complete, args=(task,))
# t1.daemon = True
t1.start() # Running:运行中状态
time.sleep(1)
print(task)
t1.join()
except KeyboardInterrupt as e:
# 取消任务
task.cancel()
# Cacelled:取消任务
print(task)
finally:
print(task)
顺利执行的话,将会打印 Pending
-> Pending:Runing
-> Finished
的状态变化
假如,执行后 立马按下 Ctrl+C,则会触发task取消,就会打印 Pending
-> Cancelling
-> Cancelling
的状态变化。
. gather与wait#
还记得上面我说,把多个协程注册进一个事件循环中有两种方法吗?
- 使用
asyncio.wait()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
- 使用
asyncio.gather()
# 千万注意,这里的 「*」 不能省略
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))
asyncio.gather
和 asyncio.wait
在asyncio中用得的比较广泛,这里有必要好好研究下这两货。
还是照例用例子来说明,先定义一个协程函数
import asyncio async def factorial(name, number):
f = 1
for i in range(2, number+1):
print("Task %s: Compute factorial(%s)..." % (name, i))
await asyncio.sleep(1)
f *= i
print("Task %s: factorial(%s) = %s" % (name, number, f))
接收参数方式
asyncio.wait
接收的tasks,必须是一个list对象,这个list对象里,存放多个的task。
它可以这样,用asyncio.ensure_future
转为task对象
tasks=[
asyncio.ensure_future(factorial("A", 2)),
asyncio.ensure_future(factorial("B", 3)),
asyncio.ensure_future(factorial("C", 4))
] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
也可以这样,不转为task对象。
loop = asyncio.get_event_loop() tasks=[
factorial("A", 2),
factorial("B", 3),
factorial("C", 4)
] loop.run_until_complete(asyncio.wait(tasks))
asyncio.gather
接收的就比较广泛了,他可以接收list对象,但是 *
不能省略
tasks=[
asyncio.ensure_future(factorial("A", 2)),
asyncio.ensure_future(factorial("B", 3)),
asyncio.ensure_future(factorial("C", 4))
] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather(*tasks))
还可以这样,和上面的 *
作用一致,这是因为asyncio.gather()
的第一个参数是 *coros_or_futures
,它叫 非命名键值可变长参数列表
,可以集合所有没有命名的变量。
loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
))
甚至还可以这样
loop = asyncio.get_event_loop() group1 = asyncio.gather(*[factorial("A" ,i) for i in range(1, 3)])
group2 = asyncio.gather(*[factorial("B", i) for i in range(1, 5)])
group3 = asyncio.gather(*[factorial("B", i) for i in range(1, 7)]) loop.run_until_complete(asyncio.gather(group1, group2, group3))
返回结果不同
asyncio.wait
asyncio.wait
返回dones
和pendings
dones
:表示已经完成的任务pendings
:表示未完成的任务
如果我们需要获取,运行结果,需要手工去收集获取。
dones, pendings = await asyncio.wait(tasks) for task in dones:
print('Task ret: ', task.result())
asyncio.gather
asyncio.gather
它会把值直接返回给我们,不需要手工去收集。
results = await asyncio.gather(*tasks) for result in results:
print('Task ret: ', result)
wait有控制功能
import asyncio
import random async def coro(tag):
await asyncio.sleep(random.uniform(0.5, 5)) loop = asyncio.get_event_loop() tasks = [coro(i) for i in range(1, 11)] # 【控制运行任务数】:运行第一个任务就返回
# FIRST_COMPLETED :第一个任务完全返回
# FIRST_EXCEPTION:产生第一个异常返回
# ALL_COMPLETED:所有任务完成返回 (默认选项)
dones, pendings = loop.run_until_complete(
asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED))
print("第一次完成的任务数:", len(dones)) # 【控制时间】:运行一秒后,就返回
dones2, pendings2 = loop.run_until_complete(
asyncio.wait(pendings, timeout=1))
print("第二次完成的任务数:", len(dones2)) # 【默认】:所有任务完成后返回
dones3, pendings3 = loop.run_until_complete(asyncio.wait(pendings2)) print("第三次完成的任务数:", len(dones3)) loop.close()
输出结果
第一次完成的任务数: 1
第二次完成的任务数: 4
第三次完成的任务数: 5
python 并发专题(十三):asyncio (二) 协程中的多任务的更多相关文章
- python 异步IO( asyncio) 协程
python asyncio 网络模型有很多中,为了实现高并发也有很多方案,多线程,多进程.无论多线程和多进程,IO的调度更多取决于系统,而协程的方式,调度来自用户,用户可以在函数中yield一个状态 ...
- python 并发专题(十二):基础部分补充(四)协程
相关概念: 协程:一个线程并发的处理任务 串行:一个线程执行一个任务,执行完毕之后,执行下一个任务 并行:多个CPU执行多个任务,4个CPU执行4个任务 并发:一个CPU执行多个任务,看起来像是同时执 ...
- python第五十三天--进程,协程.select.异步I/O...
进程: #!usr/bin/env python #-*-coding:utf-8-*- # Author calmyan import multiprocessing,threading,time ...
- python 并发编程 基于gevent模块 协程池 实现并发的套接字通信
基于协程池 实现并发的套接字通信 客户端: from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('12 ...
- python并发编程之asyncio协程(三)
协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...
- python 并发专题(六):协程相关函数以及实现(gevent)
文档资源 http://sdiehl.github.io/gevent-tutorial/ 一.协程实现 线程和协程 既然我们上面也说了,协程也被称为微线程,下面对比一下协程和线程: 线程之间需要上下 ...
- python 并发专题(十三):asyncio (一) 初识
https://www.cnblogs.com/wongbingming/p/9095243.html . 本文目录# 如何定义/创建协程 asyncio的几个概念 学习协程是如何工作的 await与 ...
- Python黑魔法 --- 异步IO( asyncio) 协程
python asyncio 网络模型有很多中,为了实现高并发也有很多方案,多线程,多进程.无论多线程和多进程,IO的调度更多取决于系统,而协程的方式,调度来自用户,用户可以在函数中yield一个状态 ...
- {python之协程}一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二
python之协程 阅读目录 一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二 一 引子 本 ...
随机推荐
- Source insight 提示: it is not currently available for write access
点击阅读原文 使用sourceinsight编辑linux内核文件后不能保存,并且弹出窗口揭示:Error: "Z:\linux\kernel\kernel-2.6.13\scripts\k ...
- 有a1,a2,a3,a4,四个数组,四个数组重新组合成一个数组(A),间隔是10个元素
好久没折腾Py了,这是去年年初2019.3月发在Q中的一个记录,因不从事这个,并且被在工厂耽误10几年,所以很少写Blog在这里,感觉这里比较正式,而在Q中只是随意性的记载, 但发布图片总是需要另外再 ...
- 网页元素居中的n种方法
导语:元素居中对齐在很多场景看上去很和谐很漂亮.除此之外,对于前端开发面试者的基础也是很好的一个考察点.下面跟着作者的思路,一起来看下吧. 场景分析 一个元素,它有可能有背景,那我要它的背景居中对齐 ...
- Razor 视图
Razor 视图 关于视图引擎 视图引擎简单理解就是能够支持对视图的解析,在 ASP.NET MVC 中,视图引擎的作用就是把视图处理成浏览器能够执行的 HTML 代码,不同的视图引擎,语法规则不一样 ...
- 如何获取Apollo上项目下的所有namespace?
背景 项目配置迁移到Apollo之后,通过统一的配置管理及配置监听使得项目配置修改的成本大大降低. 但是,在使用Apollo的过程中,强哥也遇到一个问题:如果我们要获取Apollo下的namespac ...
- JPS/JPS+ 寻路算法
目录 概念 强迫邻居(Forced Neighbour) 跳点(Jump Point) JPS 寻路算法(Jump Point Search) 实现原理 示例过程 JPS+(Jump Point Se ...
- 4.kubernetes的服务发现插件-CoreDNS
1.1.部署K8S内网资源清单http服务 1.2.部署coredns 部署K8S内网资源清单http服务 在运维主机HDSS7-200.host.com上,配置一个nginx虚拟主机,用以提高k8s ...
- C#数据结构与算法系列(十):中缀表达式转后缀表达式
1.具体步骤 1)初始化两个栈:运算符栈s1和储存中间结果的栈s2:2)从左至右扫描中缀表达式:3)遇到操作数时,将其压s2:4)遇到运算符时,比较其与s1栈顶运算符的优先级: (1)如果s1 ...
- 谈谈我对 Flutter 未来发展 和 “嵌套地狱” 的浅显看法
Flutter 未来发展 提到 Flutter 就不得不提到 Fuchsia 系统,这是一个尚未正式发布的操作的系统,引用 Android 和 Chrome 的高级副总裁 Hiroshi Lockhe ...
- 手写spring事务框架-蚂蚁课堂
1.视频参加C:\Users\Administrator\Desktop\蚂蚁3期\[www.zxit8.com] 0017-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Sp ...