Python 的协程

前言

最近在看部分Python源码时, 发现了async 这个关键字. 查了一下发现了Python中的协程.

协程这玩意, 在GO中我用过啊, 简单说, 就是一个轻量级的线程嘛, 由语言自己来实现不同协程的调度. 想着Python中可能也是差不多的东西吧. 但是我Google搜了一下, 前面的说明都给出了下面的例子:

def consumer():r = ''while True:n = yield rif not n:returnprint('[CONSUMER] Consuming %s...' % n)r = '200 OK'def produce(c):c.send(None)n = 0while n < 5:n = n + 1print('[PRODUCER] Producing %s...' % n)r = c.send(n)print('[PRODUCER] Consumer return: %s' % r)c.close()c = consumer()
produce(c)

这这这, 这是协程咩? 这不就是一个生成器咩?

不过你想一下生成器的特征:

  • 需要的时候, 返回一个值, 并将当前函数内部的状态保存
  • 下次执行的时候, 会恢复上次执行的环境并继续执行

能够保存运行状态并在下次执行时恢复, 说他是协程貌似也没什么问题哈. 而且, Python中的协程是跑在同一个线程中, 也就是串行执行的, 所以也不需要加锁.

协程

看了上面的生成器, 是不是有一种想法? 这玩意不就是个生成器么? 为什么要叫协程? 没错, 就是生成器.

上面也说了, 协程的特点, 就是可以停止当前函数的执行并保存当前状态, 并在下次执行时进行恢复. 对于单线程的运行来说, 什么时候需要这种操作呢? 等待的时候. 比如等待文件打开, 等待锁, 等待网络返回等等. 这时程序运行着也没什么事做, 就可以先去做其他事情, 等这边好了再继续回来执行. 下面以单纯的sleep举例.

自己实现

我们如何使用原生的yield生成器来实现一个任务队列呢? 我随便写了一下:

import timedef yield_sleep(delay):start_time = time.time()while True:if time.time() - start_time < delay:yieldelse:breakdef hello(name):print(f"{name}-1-{time.strftime('%X')}")yield from yield_sleep(1)print(f"{name}-2-{time.strftime('%X')}")# 创建任务
tasks = [hello('one'), hello('two')]
while True:if len(tasks) <= 0:break# 复制数组, 下方删除时能够正常遍历copy_tasks = tasks[:]for task in copy_tasks:try:next(task)except StopIteration:# 迭代完成, 删除元素tasks.remove(task)

image-20211008222517989

简单解释一下, 我们在每次任务调用yield临时返回时, 进行任务的轮换. 这样, 原本需要2s 执行的操作, 一共只需要1s 即可. (这里为了说明效果, 只是简单实现了一下. )

哎, 如此一来, 所有支持yield生成器的语言, 其实都是支持coroutine的呀, 比如我大 PHP, 嘿嘿.

asyncio

Python 3.4中, 引入了asyncio包. 将异步 IO 的操作进行了封装.

简单说, asyncio内部, 维护了一个任务队列, 在函数执行yield让出执行权时, 切换到下一个任务继续执行. 嗯, 大概就是这样.

import asyncio
import time@asyncio.coroutine
def hello(name):print(f"{name}-1-{time.strftime('%X')}")yield from asyncio.sleep(1)print(f"{name}-2-{time.strftime('%X')}")# 获取事件队列
loop = asyncio.get_event_loop()
# 并发执行任务
tasks = [hello('one'), hello('two')]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

image-20211005124103834

可以看到, 函数在首次执行到yield时, 进行了中断并将执行权让了出去.

  1. 通过asyncio.get_event_loop()方法, 生成一个事件队列
  2. 调用loop.run_until_complete方法运行指定的任务队列
  3. asyncio.sleep函数和我们上面实现的yield_sleep是一样的效果. 在yield中断时, 会从队列中找到另一个任务并执行

需要注意的一点, Python的协程是需要手动让出执行权的. 这点与Go不同. 也就是说, 发生协程切换的时机为:

  1. 任务主动让出执行权
  2. 任务执行完成

举个例子(将上面的例子简单修改):

import asyncio
import time@asyncio.coroutine
def hello(name, delay, not_yield):print(f"{name}-1-{time.strftime('%X')}")# 占用协程等待if not_yield:time.sleep(delay)else:yield from asyncio.sleep(1)print(f"{name}-2-{time.strftime('%X')}")# 获取事件队列
loop = asyncio.get_event_loop()
# 并发执行任务
tasks = [hello('one', 2, False), hello('two', 5, True)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

image-20211005124241888

可以看到, 尽管协程1只是想等待1s, 但因为协程2一直占用这执行权没有放出来, 故协程1等到协程2执行结束后才再次获得执行权, 既5s 后

async/await

Python 3.5中, 增加了async/await语法糖.

简单来说, 将上面的@asyncio.coroutine换成async, 将yield from换成await就行了. 其他不变. 替换后, 上面的代码就变成了这样, 意思是一样的.

import asyncio
import timeasync def hello(name):print(f"{name}-1-{time.strftime('%X')}")await asyncio.sleep(1)print(f"{name}-2-{time.strftime('%X')}")# 获取事件队列
loop = asyncio.get_event_loop()
# 并发执行任务
tasks = [hello('one'), hello('two')]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

通用方式

前面说的方式是简单介绍下实现, 协程在Python中较为平常的使用方式如下:

import asyncio
import timeasync def hello(name):print(f"{name}-1-{time.strftime('%X')}")await asyncio.sleep(1)print(f"{name}-2-{time.strftime('%X')}")async def main():"""创建任务并执行"""# 方案1: 添加并执行任务task1 = asyncio.create_task(hello('one'))task2 = asyncio.create_task(hello('tow'))# 调用`asyncio.create_task`方法调用后, 任务就已经存在调度队列中了# 即使没有手动`await`等待, 在协程切换时也会被执行. 我们添加`await`只是为了等待所有任务完成await task1await task2# 等待其中所有协程执行完成, 与分开 await 相同await asyncio.wait({task1, task2})# 等待协程执行. 若指定时间后还没有执行完毕, 则会抛出异常await asyncio.wait_for(task1, 1)# 方案2: 批量执行协程. 方案1的简化版# 返回值为所有协程的集合await asyncio.gather(hello('one'),hello('tow'))"""获取任务信息"""# 获取当前执行的任务current_task = asyncio.current_task()# 获取事件循环中所有未完成的任务all_task = asyncio.all_tasks()# 关闭一个协程, 不再执行task1.cancel()# 获取任务的结果. 若协程被关闭了, 会抛出异常ret = task1.result()asyncio.run(main())

创建并执行一个协程任务, 这样就不用操心事件队列的问题了, 我们在main函数中要做的就是创建任务并执行.

注意, 使用关键字async定义的方法, 是一个协程对象, 不能单纯调用hello('1234'), 其实现是一个装饰器, 返回的是一个coroutine对象.


对于Python中的协程, 差不多就是这么个东西了. 简单说, 就是在执行 IO 耗时操作时, 将执行权暂时让出, 以活动更好的执行效率.

但是问题来了, 对于这种耗时操作, 我们总不能每次都自己实现一遍吧. 勿慌, 其实很多异步操作, 都已经有了实现, 具体可见: https://github.com/aio-libs, 列出来当前已经实现异步操作的大部分库. 当然了, 系统asyncio库中也有部分简单的异步操作实现.

以后再写耗时操作的时候, 就可以用上协程了, 比如爬虫. 爬虫在发起请求的时候, 是需要等待返回的, 这时候同时发起 n 个请求, 就可以极大的提高爬虫的效率.

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

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

相关文章

虚拟内存分页机制的地址映射

概述 在之前的文章虚拟内存对分页机制做了简单的介绍. 还有一个疑问, 那就是如何将虚存中的逻辑地址映射为物理地址呢? 今天就来简单分析一下. 对于一个分页的地址来说, 一般包含两个元素: 页号: 第几页偏移量: 当前页的第几个字节 以下以 addr_virtual(p, o)表示一个逻辑…

虚拟内存分页机制的页面置换

前言 之前简单介绍过虚拟内存是如何与物理内存进行地址映射的: 虚拟内存分页机制的地址映射, 但是仅仅地址映射是不够的, 在地址映射说过会有缺页的情况, 此时就需要操作操作系统将缺少的页加载到内存中. 但是, 如果内存满了怎么办呢? 毕竟虚拟内存一般都要大于物理内存的, 不…

Kubernetes各个组件的概念

前言 Kubernetes中的概念太多了, 什么Pod Service Deployment 等等等等, 给刚接触的我都整蒙了. 通过几天观察下来, 说一下我对各个组件的理解. 此文章仅仅对这些概念做一个简单的介绍, 不至于后面看其他文章的时候一头雾水. Node Node很好理解. 就是服务实际运行的实例, 可…

Kubernetes中Pod生命周期

在 Kubernetes中Pod是容器管理的最小单位, 有着各种各样的Pod管理器. 那么一个Pod从启动到释放, 在这期间经历了哪些过程呢? Pod自开始创建, 到正常运行, 再到释放, 其时间跨度及经历的阶段大致如下: 说一下各个阶段的作用以及是为了解决什么问题. 容器调度和下载镜像的过程就…

wait函数的作用

前言 在编写C程序的时候, 通过fork函数来创建新的进程, wait函数来等待子进程结束. 那么就有一个问题了, 什么情况下父进程需要等待子进程结束后继续执行呢? 如果需要等待子进程结束, 那直接将操作放到父进程执行不就醒了么? 反正等着也是等着. 当然, 还有有一种情况, 任务…

OAuth1.0介绍

背景 为什么需要OAuth授权呢? 最典型的应用场景就是第三方登录了, 我们开发了一个网站希望用户可以QQ登录, 但是怎么能拿到用户的 QQ 信息呢? 用户将 账号密码告诉我们当然可以, 但是这样有如下隐患: 我们拿到了用户的密码, 这样很不安全. 而且任意一个应用被黑, 所有相关…

PHP 数组的内部实现

前言 这几天在翻github的时候, 碰巧看到了php的源码, 就 down 下来随便翻了翻. 地址: https://github.com/php/php-src 那么PHP中什么玩意最引人注目嘞? 一定是数组了, PHP中的数组太强大了, 于是就想着不如进去看看数组的实现部分. 这篇文章打算全程针对代码进行解读了. 以…

base64编码原理

引出 众所周知, ASICC编码共127个, 使用了7个bit进行编码. 而文件在存储的时候是以 字节为单位, 也就是8bit. 这就难免导致有一部分编码是没有定义在ASICC编码中的. 而在网络中传输二进制数据的时候(字符串本质上也是二进制数据嘛), 如果直接传输比特流, 倒也不是不可以, 只是…

页面加载速度-合并资源文件

前言 一直觉得自己的博客站点页面加载很慢, 就想着去优化一下. 呐, 下图是一次文章页面的加载, 需要2.5s. 其中 js 文件就有18个. 众所周知, 浏览器对资源文件的并行下载数量是有限制的(不同浏览器限制不同). 也就是说, 这18个 js 文件是无法同时下载的, 再说了, 页面中还有其…

hbase/thrift/go连接失败

问题 在通过Go连接hbase的过程中, 发现 get操作可以查到数据, 但是scanner命令访问数据失败, 也没有报错, 就是单纯的查不到数据. 而且Python PHP都一切正常. 这里简单复述一下我出现问题的情况, 安装过程和网上大部分内容一致, 这里简单列一下, 只是为了查询问题时参考安装过…

printf缓冲区踩坑

问题 碰到了这样一段代码(经过简化的): #include "stdio.h" #include "unistd.h" #include "sys/wait.h"int main(){fork();printf("1\n");fork();printf("1\n");wait(NULL);return 0; }这里我们简单算一下, 结果会打印几…

进程切换时是如何保存上下文的

前言 当前操作系统大部分采用分时的进程调度, 既每个进程运行一小段时间, 然后切换到下一个进程运行, 依次往复. 当进程运行的时候是独占CPU的, 此时操作系统是无法强行介入的, 为了将执行权让出来, 就需要硬件的配合了. 硬件每个一个时钟周期(比如10ms), 就会产生一个时钟中…

GO/testing包

前言 之前在写GO单元测试的时候, 使用了这个结构testing.T. 进来无事翻了翻, 发现testing包中还有一些其他的结构体, 想来是不同用处. 没想到GO的testing包竟然默默做了这么多支持, 之前竟然不知道. 在testing包中包含一下结构体: testing.T: 这就是我们平常使用的单元测试t…

CPU的分支预测

前言 最近在进行性能调优的时候, 碰到了这样的一段代码(为了展示问题而简化的代码): <?php // 第一次运行 $start microtime(true); for ($i 0; $i < 100; $i) {for ($j 0; $j <1000; $j) {for ($k 0;$k < 10000; $k) {}} } $end microtime(true); echo fi…

PHP获取Opcode及C源码

是什么 在开始之前, 必须要先介绍一下Opcode是什么. 众所周知, Java在执行的时候, 会将.java后缀的文件预先编译为.class字节码文件, JVM加载字节码文件进行解释执行. 而字节码文件存在的意义, 就是为了加速执行. 那么PHP的Opcode与之类似, 也是从.php文件到执行的过程中, 所…

PHP require/include 区别

前言 在PHP中, 载入文件可以选择使用require, 也可以使用include, 那么那他们有什么区别呢? 看了网上的一些文章, 说他们使用场景不同, require一般在文件开头引入文件, include一般在函数中动态引入文件. 但是我觉得并不是这么简单, require是作为语言结构(关键字)出现的, …

Golang 接口原理

问题 小提示, 若想直接查看原理, 可从接口原理开始查看. 有这样一段GO代码: func main() {var obj interface{}fmt.Printf("obj nil. %b\n", obj nil)type st struct{}var s *stobj sfmt.Printf("s nil. %b\n", s nil)fmt.Printf("obj nil. …

三星识别文字_比亚迪电子助力三星Galaxy Note 10系列霸气首发!

三星有子初长成气宇轩昂 秀美俊逸减之一分则嫌柔增之一分则嫌赘2019年8月7日于纽约巴克莱发布Galaxy Note 10系列用简约 重构美三星Galaxy Note 10与Galaxy Note 10分别搭载了6.3英寸和6.8英寸的超感官全视曲面屏&#xff0c;均采用单摄挖孔屏&#xff0c;开孔位于屏幕正上方。…

lisp 设计盘形齿轮铣刀_机械设计基础——周转轮系传动比的计算

点击上方蓝色字体&#xff0c;关注我们15(视频来源于网络&#xff0c;仅供学习交流&#xff0c;侵权请联系删除)机械计重点学习指导机械原理全书重点提要轴的结构改错机械设计作业集01机械设计作业集02机械设计作业集答案机械原理作业集机械原理作业集答案轴的强度计算院校推荐…

b+树阶怎么确定_B站公布年度弹幕,这个排名我不太服气

也忘记了是从什么时候开始&#xff0c;B站开始公布自己的年度弹幕了&#xff0c;今年的年度弹幕排名前五的分别是&#xff1a;爷青回、武汉加油、有内味了、双厨狂喜、禁止套娃。话说今年真的是不容易啊&#xff0c;过年那段时间以及上半年不会忘记那一幕幕感人深邃的瞬间&…