首页 > 其他分享 >31. 协程的使用

31. 协程的使用

时间:2024-11-13 18:08:21浏览次数:1  
标签:协程 31 使用 任务 gevent sleep time print

一、什么是协程

  从 Python 3.4 开始,Python 加入了协程的概念,使用 asyncio 模块实现协程。但这个版本的协程还是以生成器对象为基础。 Python 3.5 中增加了 async、await 关键字,使协程的实现更加方便。

  协程(Coroutine),又称 微线程,是一种运行运行在用户态的轻量级线程。协程可以在单线程的情况下实现并发。

  协程拥有自己的寄存器上下文和栈。协程在调度切换时,将寄存器下上文和栈保存到其它地方,等切换回来的时候,再恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态,即所有局部状态的一个特定组合,每次过程重入,就相当于进入上一次调用的状态。

  协程本质上是个单进程,相对于多进程来说,它没有线程上下文切换的开销,没有原子操作锁定及同步的开销。

  关于协程,我们还需要了解以下几个概念:

  • event_loop:事件循环,相当于无限循环,我们可以把一些函数注册到这个事件循环上,当满足发生条件的时候,就调用对应的处理方法;
  • coroutine:在 Python 中常指代协程对象类型,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用不会立即被执行,而是会返回一个协程对象;
  • task:任务,这是协程对象的进一步封装,包含协程对象的各个状态;
  • future:代表将来执行或者没有执行的任务的结果,实际上和 task 没有本质区别;

二、协程的使用

import asyncio
import time

# 协程函数,定义函数的时候,使用async关键字修饰的函数
async def work(x):
    print(f"work({x}) start!")
    print(f"当前任务的参数为:{x}")

    # 遇到IO操作挂起当前协程(任务),等待IO操作完成之后再继续往下执行
    # 当前协程挂起时,事件循环可以去执行其它任务
    await asyncio.sleep(x)

    print(f"work({x}) end!")

    return f"当前任务的返回值:{x}"

async def main():
    print("开始执行main()函数内部代码!")

    tasks = [
        # asyncio.create_task()创建一个task对象
        asyncio.create_task(work(2)),
        # asyncio.ensure_future()创建一个task对象
        asyncio.ensure_future(work(3)),
        asyncio.create_task(work(5)),
    ]

    # asyncio.wait()接收的参数为task对象,返回一个二值元组
    # done接收任务的返回值,pending接收任务的状态
    done, pending = await asyncio.wait(tasks)
    for item in done:
        print(item)

    print("main()函数内部代码执行完毕!")

if __name__ == "__main__":
    start  = time.time()

    # 协程对象,执行协程函数()得到的协程对象
    # 执行协程函数创建协程对象,函数内部的代码不会执行
    result = main()

    # 去生成或获取一个事件循环
    # loop = asyncio.get_event_loop()
    # 将任务放到任务列表中
    # loop.run_until_complete(result)

    # Python 3.7之后的版本可以简化为如下代码
    asyncio.run(result)

    print(time.time() - start)

  协程函数 就是使用 async 关键字修饰的函数。协程对象 就是执行协程函数得到的对象。执行协程函数时,协程函数内部的代码不会执行。如果想要运行协程函数内部代码,必须要将协程对象交给事件循环来处理。当我们把协程对象传递给 run_until_complete() 方法时,实际它将 coroutine 封装成 task 对象。

  事件循环 可以理解为一个死循环,它循环检测并执行某些代码,它的实现原理如下:

任务列表 = [任务1, 任务2, 任务3, ...]

while True:
    可执行的任务列表, 已完成的列表列表 = 去任务列表中检查所有的任务,将“可执行”和“已完成”的任务返回
    for 就绪任务 in 可执行的任务列表:
        执行已就绪的任务

    for 已完成的任务 in 已完成的任务列表:
        在任务列表中移除“已完成”的任务

    if 任务列表中的任务都已完成:
        break

  要实现异步处理,先要有挂起操作,当一个任务需要等待 IO 结果的时候,可以挂起当前任务,转而执行其它任务,这样才能充分利用好资源。

  await 关键字可以将耗时等待的操作挂起,让出控制权。如果协程在执行的时候遇到 await,事件循环就会将本协程挂起,转而执行别的协程,直到其它协程挂起或执行完毕。

  await 后面的对象必须是如下格式之一:

  • 一个原生的协程对象;
  • 一个由 types.coroutine 修饰的生成器,这个生成器可以返回协程对象;
  • 有一个包含 __await__ 方法的对象返回的一个迭代器;

三、手动实现协程

  进程、线程创建完之后,到底是哪个进程、线程执行去执行,这是不确定的,这需要有操作系统来进行计算(调度算法,例如优先级调度)。而协程是可以人为来控制的。程序员在代码层面上检测所有的 IO 操作,一旦遇到 IO 操作,程序员在代码级别完成切换,这样给 CPU 的感觉就是这个程序一直运行,没有 IO 操作,从而提升程序的运行效率。

3.1、使用greenlet模块实现协程

  我们可以使用 greenlet 模块实现协程,但遇到 IO(指的是 input output,输入输出,比如,网络、文件操作等) 操作时,还需要人工切换。

  我们可以在终端中使用 pip 命令安装 greelet 模块:

pip install greenlet
import time

from greenlet import greenlet

def task1():
    while True:
        print("task1 work!")
        g2.switch()
        time.sleep(0.5)

def task2():
    while True:
        print("task2 work!")
        g1.switch()
        time.sleep(0.5)

if __name__ == "__main__":
    g1 = greenlet(task1)
    g2 = greenlet(task2)

    g1.switch()

3.2、使用gevent模块实现协程

  greenlet 模块已经实现了协程,但还需要人工切换。Python 还提供了一个能自动切换任务的 gevent 模块。其原理是当一个 greenlet 遇到 IO 就会自动切换到其它的 greenlet 再执行,而不是等待 IO。

  我们可以在终端中使用 pip 命令安装 gevent 模块:

pip install gevent
import time

from gevent import spawn

# gevent模块本身无法检测常见的一些IO操作
# 在使用的时候需要额外导入monkey模块
from gevent import monkey
monkey.patch_all()

def say_hello():
    print("hello")
    time.sleep(2)
    print("hello")

def say_hi():
    print("hi")
    time.sleep(3)
    print("hi")

def say_good():
    print("good")
    time.sleep(5)
    print("good")

start_time = time.time()

g1 = spawn(say_hello)
g2 = spawn(say_hi)
g3 = spawn(say_good)

# 等待被检测的任务执行完毕,再往后执行
g1.join()
g2.join()
g3.join()

print(time.time() - start_time)
import gevent
import time

# gevent模块本身无法检测常见的一些IO操作
# 在使用的时候需要额外导入monkey模块
from gevent import monkey
monkey.patch_all()

def say_hello():
    print("hello")
    time.sleep(2)
    print("hello")

def say_hi():
    print("hi")
    time.sleep(3)
    print("hi")

def say_good():
    print("good")
    time.sleep(5)
    print("good")

start_time = time.time()

gevent.joinall([
    gevent.spawn(say_hello),
    gevent.spawn(say_hi),
    gevent.spawn(say_good),
])

print(time.time() - start_time)

time 模块的 sleep() 方法不具备自动切换任务的功能,而 gevent 模块的 sleep() 方法具有该能功能,所以我们使用猴子补丁将 time 模块的 sleep() 方法编程 gevent 模块的 sleep() 方法;

标签:协程,31,使用,任务,gevent,sleep,time,print
From: https://www.cnblogs.com/FlurryHeart/p/18544494

相关文章

  • CSC3150 memory-mapped files
    CSC3150-Instruction-A3:IntroductionThisassignmentusesxv6,asimpleandUnix-liketeachingoperatingsystem,astheplatformtoguideyouinimplementingthemmapandmunmpsystemcalls.Thesetwoareusedtosharememoryamongprocesesandtomapfil......
  • 【最新原创毕设】面向课堂教学的智能课堂点名系统+09531(免费领源码)可做计算机毕业设计
    摘要本文旨在设计和实现一个基于智能课堂点名系统的智能助手。随着高校招生规模的不断扩大和信息化技术的发展,为教师提供一款便捷、全面的点名系统具有重要意义。本系统通过整合校园各项服务资源和功能,旨在帮助教师和学生更好地适应智能课堂,提供全方位的指导和支持。本文......
  • IEMS5731 Software Design and Development
    IEMS5731SoftwareDesignandDevelopment(Fall2024)IndividualCourseProjectSpecification-MasterMindExpectedtime:10hoursLearningoutcomes:TopractiseGUIbuttons,labelsandpanelsinJava.ToexperiencetheMVCpatternviaaGUIMasterMind.......
  • libcurl.net入门使用
    libcurl.net入门使用关于libcurl.net一个引用libcurl.dll并封装为.NET使用的Curl库,方便在.NET应用程序里面执行Curl命令,没有其他库依赖,只是对libcurl.dll的封装和引用。在大多数情况下,我们可以或者比较容易获取Web请求的Curl格式的请求内容,因此该库可以比较方便地对已知Curl命......
  • ❤React-JSX语法认识和使用
    1、JSX基本使用​JSX是React的核心JSX是ES的扩展jsx语法->普通的JavaScript代码->babelReact可以使用JSX的前提和原因:React生态系统支持: 脚手架通常用于构建React应用程序,而JSX是React框架的核心语法之一。因此,脚手架默认支持JSX语法,以便更轻松地编写和管理React组件......
  • Delphi使用常见问题
    1.Delphi报错:[dcc64FatalError]BasicDemo.dpr(8):F2048Badunitformat:'..\src\CameraParamsUnit.dcu'-Expectedversion:35.0,WindowsUnicode(x64)Foundversion:35.0,WindowsUnicode(x86),是什么原因?这个错误通常是由于编译器期望的单元格式与实际提供的单......
  • 如何使用PHP开发API接口?
    在当今数字化时代,API(ApplicationProgrammingInterface,应用程序编程接口)成为前后端交互的核心桥梁。PHP作为一种流行的服务器端脚本语言,被广泛用于开发API接口。本文将详细介绍如何使用PHP开发API接口,从基础概念到实战步骤,帮助读者掌握这一重要技能。一、API接口基础介绍AP......
  • 家电安装工的操作标准和规范是确保家电安全、高效运行以及延长使用寿命的基础。安装过
    家电安装工的操作标准和规范是确保家电安全、高效运行以及延长使用寿命的基础。安装过程中必须遵循一定的技术规范和安全标准。以下是一些常见家电安装工的操作标准和规范:一、基本要求工作准备:在安装前,了解并检查家电的型号、品牌及产品说明书。确认安装环境符合要求,确保安......
  • 在webstorm里面使用prettier
    1.安装Prettierbash复制代码npminstall--save-devprettier2.创建Prettier配置文件在项目根目录下创建一个.prettierrc文件来定义Prettier的代码风格规则。以下是一个简单的Prettier配置文件示例:json复制代码{"semi":false,//不加分号......
  • C# 抛出异常代码应该避免使用 System.Exception或ApplicationException
    在.NET开发中,异常处理是确保程序健壮性和可靠性的关键部分。然而,许多开发者在编写代码时,可能会默认使用System.Exception或ApplicationException来抛出异常。这种做法虽然简单,但并不推荐。本文将探讨为什么应该避免使用这些通用异常,并提供更好的替代方案,以及如何结合这些最佳......