上篇文章我们说过由于GIL锁的限制,导致Python不能充分利用多线程来实现高并发,在某些情况下使用多线程可能比单线程效率更低,所以Python中出现了协程
协程(coroutine) 又称微线程,是一中轻量级的线程,它可以在函数的特定位置暂停或恢复,同时调用者可以从协程中获取状态或将状态传递给协程。进程和线程都是通过CPU的调度实现不同任务的有序执行,而协程是由用户程序自己控制调度的,也没有线程切换的开销,所以执行效率极高。

生成器方式实现

早先的协程是使用生成器关键字yield来实现的,和生成器很相似。下面利用一生产者-消费者模型来介绍如何使用yield协程来进行任务的切换。

import timedef consumer():r = Nonewhile True:n = yield rif not n:returnprint("[消费者] 消费'{}'...".format(n))time.sleep(1)r = '消费完成'def producer(c):next(c)n = 0while n < 5:n = n + 1print("[生产者] 生产'{}'...".format(n))res = c.send(n)print("[生产者] 消费者返回'{}'".format(res))c.close()if __name__ == '__main__':c = consumer()producer(c)

执行结果如下:

上述代码中的consumer函数是一个生成器,

  • 把consumer传入producer函数,调用next(c)启动该生成器;
  • 生产者producer函数生产了n,通过c.send(n)切换到consumer执行;
  • consumer函数通过yield拿到生产者生产的消息n,处理后,又通过yield把结果r返回。
  • producer拿到consumer处理的结果,继续生产下一条消息,直至生产结束,通过c.close()关闭consumer,整个过程结束。

这段代码全程在一个线程中完成,producer和consumer协作完成任务,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高。

greenlet

如果有上百个任务,要想实现在多个任务之间切换,使用yield生成器的方式就过于麻烦,而greenlet模块可以很轻易的实现。
Greenlet是Python的⼀个C扩展,旨在提供可⾃⾏调度的"微线程",也就是协程。在greenlet模块中,通过target.switch()可以切换到指定的协程,可以更简单的进行切换任务。

可以使用命令pip install greenlet安装greenlet模块。

from greenlet import greenlet
import timedef work1():while True:print("work1开始执行...")g2.switch()  # 切换到g2中运行time.sleep(0.5)def work2():while True:print("work2开始执行...")g1.switch()  # 切换到g1中运行time.sleep(0.5)if __name__ == "__main__":# 定义greenlet对象g1 = greenlet(work1)g2 = greenlet(work2)g1.switch()  # 切换到g1中运行

执行结果就是work1和work2交替执行。

gevent

虽然greenlet模块实现了协程并且可以方便的切换任务,但是仍需要人工切换,而不是自动进行任务的切换,当一个任务执行时如果遇到IO(⽐如⽹络、⽂件操作等),就会阻塞,没有解决遇到IO自动切换来提升效率的问题。
其实Python还有⼀个⽐greenlet更强⼤的协程模块gevent,gevent也是基于greenlet的,可以实现任务的自动切换,当⼀个greenlet遇到IO操作时,就会⾃动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执⾏。这样就跳过了IO操作的时间,⽽不是等待IO完成,可以提升程序的运行效率。

可以使用命令pip install gevent安装gevent模块。

import gevent
import timedef work1():for i in range(5):print("work1开始执行...", gevent.getcurrent())time.sleep(0.5)def work2():for i in range(5):print("work2开始执行...", gevent.getcurrent())time.sleep(0.5)if __name__ == "__main__":g1 = gevent.spawn(work1)g2 = gevent.spawn(work2)# 等待协程执⾏完成再关闭主线程g1.join()g2.join()

执行结果如下:

我们希望的是gevent模块帮我们⾃动切换协程,以达到work1和work2交替执⾏的⽬的,但并没有达到效果,原因是因为我们使用time.sleep(0.5)来模拟IO耗时操作,但是这样并没有被gevent正确识别为IO操作,所以要使⽤下⾯的gvent.sleep()来实现耗时

import gevent
import timedef work1():for i in range(5):print("work1开始执行...", gevent.getcurrent())gevent.sleep(0.5)def work2():for i in range(5):print("work2开始执行...", gevent.getcurrent())gevent.sleep(0.5)if __name__ == "__main__":g1 = gevent.spawn(work1)g2 = gevent.spawn(work2)# 等待协程执⾏完成再关闭主线程g1.join()g2.join()

运行结果如下:

猴子补丁

上面把time.sleep()改写成gevent.sleep()后,work1和work2能够交替执⾏,那么有没有不用改写,就可以实现的方法吗?答案是有的,那就是给程序打猴子补丁

关于猴⼦补丁:这个叫法起源于Zope框架,⼤家在修正Zope的Bug的时候经常在程序后⾯追加更新部分,这些被称作是"杂牌军补丁"(guerilla patch),后来guerilla就渐渐的写成了gorllia(猩猩),再后来就写成了monkey(猴⼦),所以现在被称为猴⼦补丁。

猴⼦补丁主要有以下⼏个⽤处:

  • 在运⾏时替换⽅法、属性等
  • 在不修改第三⽅代码的情况下增加原来不⽀持的功能
  • 在运⾏时为内存中的对象增加patch⽽不是在磁盘的源代码中增加

可以使用以下代码给程序打猴子补丁:

import gevent# 打补丁,让gevent识别⾃⼰提供或者⽹络请求的耗时操作
from gevent import monkey
monkey.patch_all()import timedef work1():for i in range(5):print("work1开始执行...", gevent.getcurrent())time.sleep(0.5)def work2():for i in range(5):print("work2开始执行...", gevent.getcurrent())time.sleep(0.5)if __name__ == "__main__":g1 = gevent.spawn(work1)g2 = gevent.spawn(work2)# 等待协程执⾏完成再关闭主线程g1.join()g2.join()

执行结果如下:

可以看出给程序打上猴子补丁后,使用time.sleep(),gevent也能识别到,可以自动切换任务。

async/await异步协程

在python2以及python3.3之前,使用协程要基于greenlet或者gevent这种第三方库来实现,由于不是Python原生封装的,使用起来可能会有一些性能上的流失。但是在python3.4中,引入了标准库asyncio,直接内置了对异步IO的支持,可以很好的支持协程。可以使用asyncio库提供的@asyncio.coroutine把一个生成器函数标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。

而后为了简化并更好地标识异步IO,从Python3.5开始引入了新的语法async和await,把asyncio库的@asyncio.coroutine替换为async,把yield from替换为await,可以让coroutine的代码更简洁易读。

其中async关键字用来声明一个函数为异步函数,异步函数的特点是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件消失后再回来继续执行。

await关键字用来实现任务挂起操作,比如某一异步任务执行到某一步时需要较长时间的耗时操作,就将此挂起,去执行其他的异步程序。注意:await后面只能跟异步程序或有__await__属性的对象。

假设有两个异步函数async work1和async work2,work1中的某一步有await,当程序碰到关键字await work2()后,异步程序挂起后去执行另一个异步work2函数,当挂起条件消失后,不管work2是否执行完毕,都要马上从work2函数中回到原work1函数中继续执行原来的操作

import asyncio
import datetimeasync def work1(i):print("work1'{}'执行中......".format(i))res = await work2(i)print("work1'{}'执行完成......".format(i), datetime.datetime.now())print("接收来自work2'{}'的:", res)async def work2(i):print("work2'{}'执行中......".format(i))await asyncio.sleep(1.5)print("work2'{}'执行完成......".format(i), datetime.datetime.now())return "work2'{}'返回".format(i)loop = asyncio.get_event_loop()  # 创建事件循环
task = [asyncio.ensure_future(work1(i)) for i in range(5)]  # 创建一个task列表
time1 = datetime.datetime.now()
# 将任务注册到事件循环中
loop.run_until_complete(asyncio.wait(task))
time2 = datetime.datetime.now()
print("总耗时:", time2 - time1)
loop.close()

运行结果如下:

从結果可以看出,所有任务差不多是在同一时间执行结束的,所以总耗时为1.5s,证明程序是异步执行的。

总结

至此,Python中协程的用法已经总结完毕。在平时开发中,写协程用的最多的还是async/await或者第三方库gevent模块,yield的方式只用于写生成器。关于异步协程,Python的支持和封装也越来越完善,用起来语法也很简单,可是如果我们要透过语法去学习他们优秀的思想,却不是一件容易的事情。

看到最后喜欢的话别忘了点个赞哈!

Python协程讲解相关推荐

  1. 夜来风雨声,Python协程知多少

    最近有很多的同学问,大家都知道多线程,多进程,那么这个协程有什么什么东西?难不成还是携程旅游(此处没有广告费)?能不能说一下Python协程,而且最好要讲清楚! 那行,今天将来讲解一下Python协程 ...

  2. python协程系列(三)——yield from原理详解

    声明:本文将详细讲解python协程的实现机理,为了彻底的弄明白它到底是怎么一回事,鉴于篇幅较长,将彻底从最简单的yield说起从最简单的生成器开始说起,因为很多看到这样一句话的时候很懵,即" ...

  3. python协程详解_python协程详解

    原博文 2019-10-25 10:07 − # python协程详解 ![python协程详解](https://pic2.zhimg.com/50/v2-9f3e2152b616e89fbad86 ...

  4. 【Python核心】揭秘Python协程

    首先要明白什么是协程? 协程是实现并发编程的一种方式.一说并发肯定想到了多线程/多进程模型,多线程/多进程正是解决并发问题的经典模型之一 先从一个爬虫实例出发,用清晰的思路并且结合实战来搞懂这个不算特 ...

  5. python 协程_Python 协程与 Go 协程的区别(一)

    ? "Python猫" ,一个值得加星标的公众号 花下猫语:年关将近,不知各位过得怎样?我最近有些忙,收获也挺多,以后有机会分享下.吃饭时间,追了两部剧<了不起的麦瑟尔夫人& ...

  6. python协程实时输出_python协程

    不知道你有没有被问到过有没有使用过的python协程? 协程是什么? 协程是一种用户态轻量级,是实现并发编程的一种方式.说到并发,就能想到了多线程 / 多进程模型,是解决并发问题的经典模型之一. 但是 ...

  7. python中协程与函数的区别_深入浅析python 协程与go协程的区别

    进程.线程和协程 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定义: 操作系统能够进行运算调度的最小单位.它被包含在进 ...

  8. python 协程可以嵌套协程吗_Python线程、协程探究(2)——揭开协程的神秘面纱...

    一.上集回顾 在上一篇中我们主要研究了python的多线程困境,发现多核情况下由于GIL的存在,python的多线程程序无法发挥多线程该有的并行威力.在文章的结尾,我们提出如下需求: 既然python ...

  9. c++ 协程_理解Python协程(Coroutine)

    由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...

最新文章

  1. PHP 设计模式 笔记与总结(8)策略模式
  2. commons.apache
  3. 《深入理解Spark-核心思想与源码分析》(四)第四章存储体系
  4. Android各种dialog
  5. RabbitMQ教程_5 整合SpringBoot
  6. php开发我的收藏,我的收藏列表 · 老猫带你玩转ThinkPHP5 API开发 · 看云
  7. bootstrap3-iframe-modal子页面在父页面显示模态框
  8. 【车间调度】基于matlab差分进化算法求解作业车间调度问题【含Matlab源码 1743期】
  9. WIN8转WIN7的两三事
  10. 20 年前,亚马逊就推出了大数据杀熟算法
  11. xlsx表格怎么筛选重复数据_excel表格中如何筛选重复数据
  12. 解决Elasticsearch集群 master_not_discovered_exception 异常
  13. 全球及中国图书出版发行业营销策略与运行前景分析报告2022版
  14. 中国露营、户外和越野拖车市场运行动态与发展趋势分析报告2022-2028年
  15. 问道手游服务器配置文件,问道手游脚本视频教程
  16. 我的世界学园都市java_我的世界学园都市地图整合包
  17. 互联网热点:“双十一”节奏提前,猿辅导、掌门教育积极转型素质教育
  18. 树莓派官方摄像头detected=0问题
  19. js中isNaN和Number.isNaN的区别
  20. 计算机二级c语言程序题怎么评分,计算机二级编程题怎么给分

热门文章

  1. (2022.9.4)Windows 反复弹窗报毒 Behavior:Win32/Hive.ZY 疑似误报
  2. lol韩服游戏内设置_《英雄联盟手游》韩服怎么设置中文 韩服设置中文教程
  3. Photoshop CS2 视频教程-PS制作相框(转)
  4. 坚持学习100天:多态(函数重载、虚函数和多态)
  5. 虚析构函数的内存泄漏
  6. matlab2019支持python_全方位对比:Python、Julia、MATLAB、IDL 和 Java (2019 版)
  7. Shell 编程的老臣 - gawk
  8. C/C++ 华为笔试题目
  9. 数据治理到底能不能干
  10. 大数据分析的原理和潜力