协程嵌套

使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。

  1. import asyncio
  2. import time
  3. async def task(x):
  4. print('Waiting: ', x)
  5. await asyncio.sleep(x)
  6. return 'Done after {}s'.format(x)
  7. async def main():
  8. tasks = [
  9. asyncio.ensure_future(task(1)),
  10. asyncio.ensure_future(task(2)),
  11. asyncio.ensure_future(task(4))
  12. ]
  13. dones, pendings = await asyncio.wait(tasks)
  14. for i in dones:
  15. print('Task ret: ', i.result())
  16. start = time.time()
  17. loop = asyncio.get_event_loop()
  18. loop.run_until_complete(main())
  19. print('Time: ', time.time() - start)

如果使用的是 asyncio.gather创建协程对象,那么await的返回值就是协程运行的结果。

  1. results = await asyncio.gather(*tasks)
  2. for result in results:
  3. print('Task ret: ', result)

不在main协程函数里处理结果,直接返回await的内容,那么最外层的run_until_complete将会返回main协程的结果。

  1. async def task(x):
  2. print('Waiting: ', x)
  3. await asyncio.sleep(x)
  4. return 'Done after {}s'.format(x)
  5. async def main():
  6. coroutine1 = task(1)
  7. coroutine2 = task(2)
  8. coroutine3 = task(2)
  9. tasks = [
  10. asyncio.ensure_future(coroutine1),
  11. asyncio.ensure_future(coroutine2),
  12. asyncio.ensure_future(coroutine3)
  13. ]
  14. return await asyncio.gather(*tasks)
  15. loop = asyncio.get_event_loop()
  16. results = loop.run_until_complete(main())
  17. for result in results:
  18. print('Task ret: ', result)

或者返回使用asyncio.wait方式挂起协程。

  1. async def task(x):
  2. print('Waiting: ', x)
  3. await asyncio.sleep(x)
  4. return 'Done after {}s'.format(x)
  5. async def main():
  6. coroutine1 = task(1)
  7. coroutine2 = task(2)
  8. coroutine3 = task(4)
  9. tasks = [
  10. asyncio.ensure_future(coroutine1),
  11. asyncio.ensure_future(coroutine2),
  12. asyncio.ensure_future(coroutine3)
  13. ]
  14. return await asyncio.wait(tasks)
  15. loop = asyncio.get_event_loop()
  16. done, pending = loop.run_until_complete(main())
  17. for task in done:
  18. print('Task ret: ', task.result())

也可以使用asyncio的as_completed方法

  1. async def task(x):
  2. print('Waiting: ', x)
  3. await asyncio.sleep(x)
  4. return 'Done after {}s'.format(x)
  5. async def main():
  6. coroutine1 = task(1)
  7. coroutine2 = task(2)
  8. coroutine3 = task(4)
  9. tasks = [
  10. asyncio.ensure_future(coroutine1),
  11. asyncio.ensure_future(coroutine2),
  12. asyncio.ensure_future(coroutine3)
  13. ]
  14. for task in asyncio.as_completed(tasks):
  15. result = await task
  16. print('Task ret: {}'.format(result))
  17. loop = asyncio.get_event_loop()
  18. done = loop.run_until_complete(main())

协程停止

future对象有几个状态:

  • Pending
  • Running
  • Done
  • Cancelled

创建future的时候,task为pending,事件循环调用执行的时候当然就是running,调用完毕自然就是done,如果需要停止事件循环,就需要先把task取消。可以使用asyncio.Task获取事件循环的task

  1. import asyncio
  2. async def task(x):
  3. print('Waiting: ', x)
  4. await asyncio.sleep(x)
  5. return 'Done after {}s'.format(x)
  6. tasks = [
  7. asyncio.ensure_future(task(1)),
  8. asyncio.ensure_future(task(2)),
  9. asyncio.ensure_future(task(3))
  10. ]
  11. loop = asyncio.get_event_loop()
  12. try:
  13. loop.run_until_complete(asyncio.wait(tasks))
  14. except KeyboardInterrupt as e:
  15. print(asyncio.Task.all_tasks())
  16. for task in asyncio.Task.all_tasks():
  17. print(task.cancel())
  18. loop.stop()
  19. loop.run_forever()
  20. finally:
  21. loop.close()

启动事件循环之后,马上ctrl+c,会触发run_until_complete的执行异常 KeyBorardInterrupt。然后通过循环asyncio.Task取消future。可以看到输出如下:

  1. Waiting: 1
  2. Waiting: 2
  3. Waiting: 2
  4. {<Task pending coro=<task() running at /Users/ghost/Rsj217/python3.6/async/async-main.py:18> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x101230648>()]> cb=[_wait.<locals>._on_completion() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:374]>, <Task pending coro=<do_some_work() running at /Users/ghost/Rsj217/python3.6/async/async-main.py:18> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x1032b10a8>()]> cb=[_wait.<locals>._on_completion() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:374]>, <Task pending coro=<wait() running at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:307> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x103317d38>()]> cb=[_run_until_complete_cb() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py:176]>, <Task pending coro=<do_some_work() running at /Users/ghost/Rsj217/python3.6/async/async-main.py:18> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x103317be8>()]> cb=[_wait.<locals>._on_completion() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:374]>}
  5. True
  6. True
  7. True
  8. True

True表示cannel成功,loop stop之后还需要再次开启事件循环,最后在close,不然还会抛出异常:


  1. Task was destroyed but it is pending!
  2. task: <Task pending coro=<task() done,

循环task,逐个cancel是一种方案,可是正如上面我们把task的列表封装在main函数中,main函数外进行事件循环的调用。这个时候,main相当于最外出的一个task,那么处理包装的main函数即可。

  1. import asyncio
  2. import time
  3. now = lambda: time.time()
  4. async def do_some_work(x):
  5. print('Waiting: ', x)
  6. await asyncio.sleep(x)
  7. return 'Done after {}s'.format(x)
  8. async def main():
  9. coroutine1 = do_some_work(1)
  10. coroutine2 = do_some_work(2)
  11. coroutine3 = do_some_work(2)
  12. tasks = [
  13. asyncio.ensure_future(coroutine1),
  14. asyncio.ensure_future(coroutine2),
  15. asyncio.ensure_future(coroutine3)
  16. ]
  17. done, pending = await asyncio.wait(tasks)
  18. for task in done:
  19. print('Task ret: ', task.result())
  20. start = now()
  21. loop = asyncio.get_event_loop()
  22. task = asyncio.ensure_future(main())
  23. try:
  24. loop.run_until_complete(task)
  25. except KeyboardInterrupt as e:
  26. print(asyncio.Task.all_tasks())
  27. print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())
  28. loop.stop()
  29. loop.run_forever()
  30. finally:
  31. loop.close()

不同线程的事件循环

很多时候,我们的事件循环用于注册协程,而有的协程需要动态的添加到事件循环中。一个简单的方式就是使用多线程。当前线程创建一个事件循环,然后在新建一个线程,在新线程中启动事件循环。当前线程不会被block。

  1. from threading import Thread
  2. def start_loop(loop):
  3. asyncio.set_event_loop(loop)
  4. loop.run_forever()
  5. def more_work(x):
  6. print('More work {}'.format(x))
  7. time.sleep(x)
  8. print('Finished more work {}'.format(x))
  9. start = now()
  10. new_loop = asyncio.new_event_loop()
  11. t = Thread(target=start_loop, args=(new_loop,))
  12. t.start()
  13. print('TIME: {}'.format(time.time() - start))
  14. new_loop.call_soon_threadsafe(more_work, 6)
  15. new_loop.call_soon_threadsafe(more_work, 3)

启动上述代码之后,当前线程不会被block,新线程中会按照顺序执行call_soon_threadsafe方法注册的more_work方法,后者因为time.sleep操作是同步阻塞的,因此运行完毕more_work需要大致6 + 3

新线程协程

  1. def start_loop(loop):
  2. asyncio.set_event_loop(loop)
  3. loop.run_forever()
  4. async def do_some_work(x):
  5. print('Waiting {}'.format(x))
  6. await asyncio.sleep(x)
  7. print('Done after {}s'.format(x))
  8. def more_work(x):
  9. print('More work {}'.format(x))
  10. time.sleep(x)
  11. print('Finished more work {}'.format(x))
  12. start = now()
  13. new_loop = asyncio.new_event_loop()
  14. t = Thread(target=start_loop, args=(new_loop,))
  15. t.start()
  16. print('TIME: {}'.format(time.time() - start))
  17. asyncio.run_coroutine_threadsafe(do_some_work(6), new_loop)
  18. asyncio.run_coroutine_threadsafe(do_some_work(4), new_loop)

上述的例子,主线程中创建一个new_loop,然后在另外的子线程中开启一个无限事件循环。主线程通过run_coroutine_threadsafe新注册协程对象。这样就能在子线程中进行事件循环的并发操作,同时主线程又不会被block。一共执行的时间大概在6s左右。

master-worker主从模式

对于并发任务,通常是用生成消费模型,对队列的处理可以使用类似master-worker的方式,master主要用户获取队列的msg,worker用户处理消息。

为了简单起见,并且协程更适合单线程的方式,我们的主线程用来监听队列,子线程用于处理队列。这里使用redis的队列。主线程中有一个是无限循环,用户消费队列。

  1. while True:
  2. task = rcon.rpop("queue")
  3. if not task:
  4. time.sleep(1)
  5. continue
  6. asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)

给队列添加一些数据:

  1. 127.0.0.1:6379[3]> lpush queue 2
  2. (integer) 1
  3. 127.0.0.1:6379[3]> lpush queue 5
  4. (integer) 1
  5. 127.0.0.1:6379[3]> lpush queue 1
  6. (integer) 1
  7. 127.0.0.1:6379[3]> lpush queue 1

可以看见输出:

  1. Waiting 2
  2. Done 2
  3. Waiting 5
  4. Waiting 1
  5. Done 1
  6. Waiting 1
  7. Done 1
  8. Done 5

我们发起了一个耗时5s的操作,然后又发起了连个1s的操作,可以看见子线程并发的执行了这几个任务,其中5s awati的时候,相继执行了1s的两个任务。

Python协程(中)的更多相关文章

  1. 关于python协程中aiorwlock 使用问题

    最近工作中多个项目都开始用asyncio aiohttp aiomysql aioredis ,其实也是更好的用python的协程,但是使用的过程中也是遇到了很多问题,最近遇到的就是 关于aiorwl ...

  2. Python协程中使用上下文

    在Python 3.7中,asyncio 协程加入了对上下文的支持.使用上下文就可以在一些场景下隐式地传递变量,比如数据库连接session等,而不需要在所有方法调用显示地传递这些变量.使用得当的话, ...

  3. python 协程(单线程中的异步调用)(转廖雪峰老师python教程)

    协程,又称微线程,纤程.英文名Coroutine. 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在 ...

  4. python 并发专题(十三):asyncio (二) 协程中的多任务

    . 本文目录# 协程中的并发 协程中的嵌套 协程中的状态 gather与wait . 协程中的并发# 协程的并发,和线程一样.举个例子来说,就好像 一个人同时吃三个馒头,咬了第一个馒头一口,就得等这口 ...

  5. Python 协程总结

    Python 协程总结 理解 协程,又称为微线程,看上去像是子程序,但是它和子程序又不太一样,它在执行的过程中,可以在中断当前的子程序后去执行别的子程序,再返回来执行之前的子程序,但是它的相关信息还是 ...

  6. 带你简单了解python协程和异步

    带你简单了解python的协程和异步 前言 对于学习异步的出发点,是写爬虫.从简单爬虫到学会了使用多线程爬虫之后,在翻看别人的博客文章时偶尔会看到异步这一说法.而对于异步的了解实在困扰了我好久好久,看 ...

  7. day-5 python协程与I/O编程深入浅出

    基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1.  什么是协程(以下内容来自维基百 ...

  8. 终结python协程----从yield到actor模型的实现

    把应用程序的代码分为多个代码块,正常情况代码自上而下顺序执行.如果代码块A运行过程中,能够切换执行代码块B,又能够从代码块B再切换回去继续执行代码块A,这就实现了协程 我们知道线程的调度(线程上下文切 ...

  9. Python 协程 61

    什么是协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程的特点 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到 ...

  10. 从yield 到yield from再到python协程

    yield 关键字 def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b yield 是在:PEP 255 -- Simple Generator ...

随机推荐

  1. 20155335俞昆《Java程序设计》第五周总结

    #  20155335    <Java程序设计>第五周学习总结 ##  教材学习内容总结 ##  教材学习中的问题和解决过程 对于异常处理,程序中总有意想不到的状况所引发的的错误,Jav ...

  2. phpcms直接取子栏目的内容、调用点击量的方法

    子栏目里面的内容可以直接取,而不需要通过循环. {$CATEGORYS[$catid][catname]}//取子栏目的栏目名称 {$CATEGORYS[$catid][image]}//取子栏目的栏 ...

  3. 面试整理(3)js事件委托

    事件委托主要用于一个父容器下面有很多功能相仿的子容器,这时候就需要将子容器的事件监听交给父容器来做.父容器之所以能够帮子容器监听其原理是事件冒泡,对于子容器的点击在冒泡时会被父容器捕获到,然后用e.t ...

  4. redis基础之redis-cluster(集群)(七)

    前言 redis的主流高可用集群模式为redis-cluster.从redis3.0+版本后开始支持,自带集群管理工具redis-trib.rb. 安装redis 参考:https://www.cnb ...

  5. makefile里PHONY的相关介绍

      Phony Targets PHONY 目标并非实际的文件名:只是在显式请求时执行命令的名字.有两种理由需要使用PHONY 目标:避免和同名文件冲突,改善性能. 如果编写一个规则,并不产生目标文件 ...

  6. js弱数据类型的坑

    1.从表单获取的value是字符串,如果需要为数字相加,则需要转换为number类型 <input type="number" id="val1"> ...

  7. 大型网站的 HTTPS 实践(二)——HTTPS 对性能的影响(转)

    原文链接:http://op.baidu.com/2015/04/https-s01a02/ 1 前言 HTTPS 在保护用户隐私,防止流量劫持方面发挥着非常关键的作用,但与此同时,HTTPS 也会降 ...

  8. Java文件上传与下载

    文件上传与下载可谓上网中的常见现象.apache为我们准备了用于文件上传与下载的两个jar包(commons-fileupload-1.2.1.jar,commons-io-1.4.jar).我们在w ...

  9. TreeSet基本用法

    TreeSet的基础方法: public class TreeSetTest { public static void main(String[] args) { TreeSet nums = new ...

  10. 用OpenSSL命令行生成证书文件

    用OpenSSL命令行生成证书文件 1.首先要生成服务器端的私钥(key文件): openssl genrsa -des3 -out server.key 1024 运行时会提示输入密码,此密码用于加 ...