【Python 协程详解】

news2024/9/21 22:40:03

0.前言

 

前面讲了线程和进程,其实python还有一个特殊的线程就是协程。

协程不是计算机提供的,计算机只提供:进程、线程。协程是人工创造的一种用户态切换的微进程,使用一个线程去来回切换多个进程。

为什么需要协程?

导致Python不能充分利用多线程来实现高并发,在某些情况下使用多线程可能比单线程效率更低。

由于进程和线程的切换都要消耗时间,保存线程进程当前状态以便下次继续执行。在不怎么需要cpu的程序中,即相对于IO密集型的程序,协程相对于线程进程资源消耗更小,切换更快,更适用于IO密集型。所以Python中出现了协程

协程也是单线程的,没法利用cpu的多核,想利用cpu多核可以通过,进程+协程的方式,又或者进程+线程+协程。

因为协程中获取状态或将状态传递给协程。进程和线程都是通过CPU的调度实现不同任务的有序执行,而协程是由用户程序自己控制调度的,也没有线程切换的开销,所以执行效率极高。

1.实现方式

1.1yield关键字实现协程

通过生成器实现

import time
def producer():
    while True:
        time.sleep(1)
        print("生产了1个包子", time.strftime("%X"))
        yield
def consumer():
    while True:
        next(prd)
        print("消费了1个包子", time.strftime("%X"))
if __name__ == "__main__":
    prd = producer()#第一次执行会返回一个生成器对象,而不会真正的执行该函数
    consumer()

执行步骤解析:

1.prd = producer()#第一次执行会返回一个生成器对象,而不会真正的执行该函数

2.consumer(),调用并运行该函数,死循环,然后执行next(prd)执行第一步的生成器对象

3.执行producer,死循环,输出生产包子语句,遇到yield择执行挂起并返回到consumer中继续执行上次next()后的代码,然后次循环再次来到next()

4.程序又返回到上次yield挂起的地方继续执行,依次循环执行,达到并发效果。

所以在遇到需要cpu等待的操作主动让出cpu,记住函数执行的位置,下次切换回来继续执行才能算是并发的运行,提高程序的并发效果。

如果是仅仅用两哥循环来实现,这样并不能保留函数的执行的位置,只是简单的一个函数执行结束换到另一个函数而已

import time
def producer():
    time.sleep(1)
    print("生产了1个包子", time.strftime("%X"))
def consumer():
    print("消费了1个包子", time.strftime("%X"))
if __name__ == "__main__":
    while True:
        producer()
        consumer()
# 

1.2greenlet模块

如果有上百个任务,要想实现在多个任务之间切换,使用yield生成器的方式就过于麻烦,而greenlet模块可以很轻易的实现

greenlet模块需要安装,pip install greenlet。greenlet原理是对生成器的封装。greenlet类提供了一个方法,switch:在需要进行切换的时候切换到指定的协程。

旨在提供可⾃⾏调度的"微线程",也就是协程。在greenlet模块中,通过target.switch()可以切换到指定的协程,可以更简单的进行切换任务。

import time
from greenlet import greenlet
def producer():
  while True:
      time.sleep(1)
      print('生产了一个包子',time.strftime("%X"))
      grep2.switch()
def consumer():
  while True:
      print('消费了一个包子',time.strftime("%X"))
      grep1.switch()
if __name__ == '__main__':
    grep1 = greenlet(producer)
    grep2 = greenlet(consumer)
    grep1.switch()  # 先去执行producer

1.3gevent

虽然greenlet模块实现了协程并且可以方便的切换任务,但是仍需要人工切换,而不是自动进行任务的切换,当一个任务执行时如果遇到IO(⽐如⽹络、⽂件操作等),就会阻塞,没有解决遇到IO自动切换来提升效率的问题

gevent模块也需要安装,pip install gevent,gevent是对greenlet的再次封装,不用程序员自己编程切换。注意gevent遇到耗时操作才会切换协程运行,没有遇到耗时操作是不会主动切换的。

gevent.spawn(*args, **kwargs) 不定长参数中的第一个参数为协程执行的方法fn,其余的依次为 fn 的参数。开启了协程后要调用join方法。

gevent模块中识别耗时的操作有两种方式,① 使用gevent模块中重写的类。如,gevent.socket gevent.sleep ② 打补丁的方式,在所有的代码前。from gevent import monkey 导入这个模块,monkey.patch_all()调用这个方法。

推荐使用第二种方式,这样就不用更改已经写好的代码

正常情况下gevent并不会识别耗时操作

import gevent
import time
 
 
def 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()来实现耗时

① 使用gevent模块中重写的类。如,gevent.socket gevent.sleep

import gevent
import time
 
 
def 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能够交替执⾏

给程序打猴子补丁就可以不用改写源代码

 ② from gevent import monkey 导入这个模块,monkey.patch_all()调用这个方法。

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

  • 在运⾏时替换⽅法、属性等
  • 在不修改第三⽅代码的情况下增加原来不⽀持的功能
  • 在运⾏时为内存中的对象增加patch⽽不是在磁盘的源代码中增加
  • import gevent
     
    # 打补丁,让gevent识别⾃⼰提供或者⽹络请求的耗时操作
    from gevent import monkey
    monkey.patch_all()
     
    import time
     
     
    def 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也能识别到,可以自动切换任务

  • 当开启的协程很多的时候,一个个的调用join方法就有点麻烦,所以gevent提供了一个方法joinall(),可以一次join所有的协程。joinall() 方法传参一个列表,列表包含了所有的协程。

  • import time
    import gevent
    from gevent import monkey
    monkey.patch_all()
    def producer(name):
        for i in range(3):
            time.sleep(1)
            print("+++++ 1个包子", name, time.strftime("%X"))
    def consumer(name):
        for i in range(3):
            time.sleep(1)
            print("----- 1个包子",  name, time.strftime("%X"))
    if __name__ == "__main__":
        gevent.joinall([gevent.spawn(producer, "zhangsan"), gevent.spawn(consumer, "lisi")])
    # 

1.4asyncio异步协程

在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函数中继续执行原来的操作。
await就是等待对象的值得到结果后再继续向下执行

import asyncio
import datetime


async 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,证明程序是异步执行的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/459177.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中国制造再击败一家海外企业,彻底取得垄断地位

中国制造已在13个行业取得领先优势,凸显出中国制造的快速崛起,日前中国制造又在一个行业彻底击败海外同行,再次证明了中国制造的实力。 一、海外企业承认失败 提前LGD宣布它位于广州的8.5代液晶面板生产线停产,预计该项目将出售给…

crm day03 创建市场活动

页面切割 div切割,ifram显示 如何分割的呢,在主页面上打开iframe $(function(){ //页面加载时window.open("workbench/main/index.do","workareaFrame"); })注意所有在WEB-INF的页面都会收到保护,因此到达此目录下的页…

不得不的创建型模式-建造者模式

目录 建造者模式是什么 下面是一个简单的示例代码,演示了如何使用建造者模式来构建一个复杂对象: 面试中可能遇到的问题及回答: 建造者模式是什么 建造者模式是一种创建型模式,它的目的是将复杂对象的构造过程分离成多个简单的…

你知道项目进度控制和跟踪的目的是什么吗?

项目进度控制和跟踪的目的是: 增强项目进度的透明度,当项目进展与项目计划出现偏差时,可以及时采取适当的措施。 1、计划是项目监控的有效手段 项目控制的手段是根据计划对项目的各项活动进行监控,项目经理可以使用甘特图来制…

界面控件DevExtreme使用指南 - 折叠组件快速入门(二)

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合,使您可以利用现代Web开发堆栈(包括React,Angular,ASP.NET Core,jQuery,Knockout等)构建交互式的Web应用程序,该套件附带功能齐…

微信小程序nodejs+python+php+springboot+vue 微型整容医美挂号预约app系统

(a) 管理员;管理员使用本系统涉到的功能主要有首页、个人中心、用户管理、体检预约管理、项目预约、系统管理等功能 (b) 用户;用户进入app可以实现首页、美容产品、我的等,在我的页面可以对在线预约、体检预约、项目预约等功能进行操作 本基于…

Unity之OpenXR+XR Interaction Toolkit实现 UI交互

一.前言 在VR中我们经常会和一些3D的UI进行交互,今天我们就来说一下如何实现OpenXRXRInteraction Toolkit和UI的交互。 二.准备工作 有了前两篇的配置介绍,我们就不在详细说明这些了,大家自行复习 Unity之OpenXRXR Interaction Toolkit接入Pico VR一体…

钉钉用一条斜杠,金山系用一张表格,做了华为一直想做的事

阿里的“新钉钉”又一次站在风口上 一场疫情导致数万企业停工的同时,却让阿里的钉钉、腾讯会议,还有字节跳动的飞书等在线协同办公产品火得一塌糊涂。 今天,OpenAI公司的一个chatGPT,让阿里、百度等各大互联网巨头扎堆发布大模型产品。 回顾…

如何在Web上实现激光点云数据在线浏览和展示?

无人机激光雷达测量是一项综合性较强的应用系统,具有数据精度高、层次细节丰富、全天候作业等优势,能够精确测量三维现实世界,为各个行业提供了丰富有效的数据信息。但无人机激光雷达测量产生的点云数据需要占用大量的存储空间,甚…

Gantt图和PERT图的相关知识

1、Gantt 图 Gantt图以时间为基准描述项目任务,可以清晰的描述每个任务从何时开始,到何时结束,以及每个任务的并行关系,但是不能反映项目各任务之间的依赖关系,也无法确定整个任务的关键所在。 2、PERT图 计划评审…

Canvas实现动态绘制圆周效果(沿圆周运动的圆的绘制)

步骤实现: 首先,创建一个 HTML 画布和一个 JavaScript 动画函数。 在画布上绘制一个圆。 定义一个变量来表示圆心的坐标和半径。 进行动画循环以更新圆心坐标,使其沿外圆周运动。 使用三角函数(如 sin 和 cos)来计…

前端代码版本管理规范

Git 是目前最流行的源代码管理工具。为规范开发,保持代码提交记录以及 git分支结构清晰,方便后续维护,总结了如下规范。 分支约定 ├── master # 生产分支 ├── release # 测试分支├── develop # 开发分支…

学系统集成项目管理工程师(中项)系列11b_沟通管理(下)

1. 沟通过程的有效性 1.1. 效果 1.1.1. 在适当的时间、适当的方式、信息被准确的发送给适当的沟通参与方(信息的接收方),并且能够被正确的理解,最终参与方能够正确的采取行动 1.2. 效率 1.2.1. 强调的是及时提供所需的信息 2…

两数之和hash

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回…

基于opencv-python的深度学习模块案例

目录 图像分类 目标检测 人脸检测 姿态估计 车辆检测 一、图像分类 图像分类是基于深度学习的计算机视觉任务中最简单、也是最基础的一类,它其中用到的CNN特征提取技术也是目标检测、目标分割等视觉任务的基础。 具体到图像分类任务而言,其具体流…

Springcloud----Feign

在上一个案列中Springcloud-注册中心 使用的交互是利用RestTemplate发起远程调用的代码,Feign是Springcloud整合的声明式组件, Feign Feign和RestTemplate都是用于在Java中实现RESTful API调用的工具,但它们之间有一些区别和优缺点。 区别 Feign是一个声明式HTTP…

夜天之书 #82 Web API 简介

Application Programming Interface (API) 即应用程序接口。顾名思义,它是开发者访问应用程序的接口。 例如,你可以通过以下命令查询 GitHub 上特定代码仓库的元数据信息: curl https://api.github.com/repos/apache/pulsar GitHub 会返回以下…

Springboot——导入用户地址簿相关功能代码

目录 一、导入用户地址簿相关功能代码 1.1 需求分析 1.2 数据库对应的表 1.3 实体类 1.4 控制层 二、菜品展示 2.1 修改列表接口 2.2 设置对应接口查询套餐信息 三、购物车 3.1 购物车数据模型 3.2 代码开发 3.2.1 实体类 3.2.2 添加购物车 3.2.3 查看购物车 3.2.4 清空购…

clickhouse 为什么快?

文章目录 [TOC](文章目录) 前言一、什么是列式数据库?为什么要用列式数据库,优点是什么? 二、clickhouse入门1. 个人猜想2. 使用clickhouse引入依赖yml配置扫描mapper 2.生成相应代码,执行测试用例查询结果 总结 前言 例如:随着人工智能的不断发展&…

还在玩传统终端,不妨来试试全新 AI 终端 Warp

壹 ❀ 引 最近一段时间,AI领域如同雨后春笋般开始猛烈生长,processon,sentry,一些日常使用的工具都在积极接入AI,那么正好借着AI的风头,今天给大家推荐一款非常不错的智能终端 warp(目前仅限ma…