Python进阶之协程详解
作者:云物互联 发布时间:2023-08-23 04:52:52
协程
协程(co-routine,又称微线程)是一种多方协同的工作方式。当前执行者在某个时刻主动让出(yield)控制流,并记住自身当前的状态,以便在控制流返回时能从上次让出的位置恢复(resume)执行。
简而言之,协程的核心思想就在于执行者对控制流的 “主动让出” 和 “恢复”。相对于,线程此类的 “抢占式调度” 而言,协程是一种 “协作式调度” 方式。
协程的应用场景
抢占式调度的缺点
在 I/O 密集型场景中,抢占式调度的解决方案是 “异步 + 回调” 机制。
其存在的问题是,在某些场景中会使得整个程序的可读性非常差。以图片下载为例,图片服务中台提供了异步接口,发起者请求之后立即返回,图片服务此时给了发起者一个唯一标识 ID,等图片服务完成下载后把结果放到一个消息队列,此时需要发起者不断消费这个 MQ 才能拿到下载是否完成的结果。
可见,整体的逻辑被拆分为了好几个部分,各个子部分都会存在状态的迁移,日后必然是 BUG 的高发地。
用户态协同调度的优势
而随着网络技术的发展和高并发要求,协程所能够提供的用户态协同调度机制的优势,在网络操作、文件操作、数据库操作、消息队列操作等重 I/O 操作场景中逐渐被挖掘。
协程将 I/O 的处理权从内核态的操作系统交还给用户态的程序自身。用户态程序在执行 I/O 时,主动的通过 yield(让出)CPU 的执行权给其他协程,多个协程之间处于平等、对称、合作的关系。
协程的运行原理
当程序运行时,操作系统会为每个程序分配一块同等大小的虚拟内存空间,并将程序的代码和所有静态数据加载到其中。然后,创建和初始化 Stack 存储,用于储存程序的局部变量,函数参数和返回地址;创建和初始化 Heap 内存;创建和初始化 I/O 相关的任务。当前期准备工作完成后,操作系统将 CPU 的控制权移交给新创建的进程,进程开始运行。
一个进程可以有一个或多个线程,同一进程中的多个线程将共享该进程中的全部系统资源,如:虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈和线程本地存储。
协程是一种比线程更加轻量级的存在,协程不是 * 作系统内核所管理,而完全是由用户态程序所控制。协程与线程以及进程的关系如下图所示。可见,协程自身无法利用多核,需要配合进程来使用才可以在多核平台上发挥作用。
协程之间的切换不需要涉及任何 System Call(系统调用)或任何阻塞调用。
协程只在一个线程中执行,切换由用户态控制,而线程的阻塞状态是由操作系统内核来完成的,因此协程相比线程节省线程创建和切换的开销。
协程中不存在同时写变量的冲突,因此,也就不需要用来守卫关键区块的同步性原语,比如:互斥锁、信号量等,并且不需要来自操作系统的支持。
协程通过 “挂起点” 来主动 yield(让出)CPU,并保存自身的状态,等候恢复。例如:首先在 funcA 函数中执行,运行一段时间后调用协程,协程开始执行,直到第一个挂起点,此后就像普通函数一样返回 funcA 函数。 funcA 函数执行一些代码后再次调用该协程,注意,协程这时就和普通函数不一样了。协程并不是从第一条指令开始执行而是从上一次的挂起点开始执行,执行一段时间后遇到第二个挂起点,这时协程再次像普通函数一样返回 funcA 函数,funcA 函数执行一段时间后整个程序结束。
可见,协程之所可以能够 “主动让出” 和 “被恢复”,是解析器在函数运行时堆栈中保存了其运行的 Context(上下文)。
Python 中的协程
Python 对协程的支持经历了多个版本:
Python2.x 对协程的支持比较有限,通过 yield 关键字支持的生成器实现了一部分协程的功能但不完全。
第三方库 gevent 对协程有更好的支持。
Python3.4 中提供了 asyncio 模块。
Python3.5 中引入了 async/await 关键字。
Python3.6 中 asyncio 模块更加完善和稳定。
Python3.7 中内置了 async/await 关键字。
async/await 的示例程序:
import asyncio
from pathlib import Path
import logging
from urllib.request import urlopen, Request
import os
from time import time
import aiohttp
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
CODEFLEX_IMAGES_URLS = ['https://codeflex.co/wp-content/uploads/2021/01/pandas-dataframe-python-1024x512.png',
'https://codeflex.co/wp-content/uploads/2021/02/github-actions-deployment-to-eks-with-kustomize-1024x536.jpg',
'https://codeflex.co/wp-content/uploads/2021/02/boto3-s3-multipart-upload-1024x536.jpg',
'https://codeflex.co/wp-content/uploads/2018/02/kafka-cluster-architecture.jpg',
'https://codeflex.co/wp-content/uploads/2016/09/redis-cluster-topology.png']
async def download_image_async(session, dir, img_url):
download_path = dir / os.path.basename(img_url)
async with session.get(img_url) as response:
with download_path.open('wb') as f:
while True:
# 在 async 函数中使用 await 关键字表示等待 task 执行完成,也就是等待 yeild 让出控制权。
# 同时,asyncio 使用事件循环 event_loop 来实现整个过程。
chunk = await response.content.read(512)
if not chunk:
break
f.write(chunk)
logger.info('Downloaded: ' + img_url)
# 使用 async 关键字声明一个异步/协程函数。
# 调用该函数时,并不会立即运行,而是返回一个协程对象,后续在 event_loop 中执行。
async def main():
images_dir = Path("codeflex_images")
Path("codeflex_images").mkdir(parents=False, exist_ok=True)
async with aiohttp.ClientSession() as session:
tasks = [(download_image_async(session, images_dir, img_url)) for img_url in CODEFLEX_IMAGES_URLS]
await asyncio.gather(*tasks, return_exceptions=True)
if __name__ == '__main__':
start = time()
# event_loop 事件循环充当管理者的角色,将控制权在几个协程函数之间切换。
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(main())
finally:
event_loop.close()
logger.info('Download time: %s seconds', time() - start)
来源:https://blog.csdn.net/Jmilk/article/details/122546181
猜你喜欢
- 前言昨天因为小程序功能要获取小程序程序码,看了微信文档爬了好多坑。(留一下记录以防后面被坑)操作因为我获取到了微信那里的图片的图片流一直不知
- 若数据库是sql server,我以前为了返回插入记录的id,一般是用下面的存储过程:程序代码 create procedure&
- for 循环本系列前面 “探索 Python,第 5 部分:用 Python 编程” 一文讨论了 if 语句和 while 循环,讨论了复合
- banner 设计会严重影响广告投放效果,在此建议相关设计人员,在设计FLASH BANNER的时候考虑到以下问题.1.
- 小贤是一条可爱的小狗(Dog), 它的叫声很好听(wow), 每次看到主人的时候就会乖乖叫一声(yelp).从这段描述可以得到以下对象:fu
- 在我发表上一篇《Zen Coding: 一种快速编写HTML/CSS代码的方法》之后,有网友表示不知道怎么在Dreamweaver上使用ze
- MYSQL在一个字段值前面加字符窜,如下:member 表名card 字段名update member SET card = '00
- 微博如火如荼,大家都选择用微博带来社会化流量,顺便推广产品和网站,几乎所有的网站都有分享到代码,但是还有一种更快捷的分享方式,javascr
- Background高斯噪声,顾名思义是指服从高斯分布(正态分布)的一类噪声。有的时候我们需要向标准数据中加入合适的高斯噪声让数据更加符合实
- 在实际的工作和学习中,许多人的SQL Server 2005数据库日志文件可能会发生损坏,例如硬件故障、计算机非正常重启或关机等等。在SQL
- 看到豆瓣上有网友提了这个问题,看到回答的人不多,忍不住写了下面的内容。工作中最常用到的统计方法有哪些?根据我自己的经验给举些例子。1.通过一
- 大家经常在网上登陆的时候经常会看到让你输入验证码,有的是文字的,有的呢是图片,比如chinaren.com校友录中留言的时候,我们就会看到数
- RegExp对象的语义和使用:检查字符串匹配获取字符串中的部分内容在原字符串的基础上构建一个新的字符串(包括添加、删除和修改)构建一个Reg
- using System; using System.Data; using System.Configuration; using Sys
- 解决SQL2000最大流水号的两个好方法问:请问怎样才能解决ms serer 2000 最大流水号的问题?答:我可以介绍两种方法给你:方法1
- 自从2005年8月11日阿里巴巴宣布收购雅虎中国的全部资产后,做为阿里巴巴集团的创始人马云心里盘算的事应该是如何把雅虎中虎整合进阿里巴巴这个
- 今天工作中遇到,拿出来说说。网上CSS下拉菜单不少,不过都存在这样那样的问题,主要问题是,如果你菜单下面有一个FLASH的话,很多都会被FL
- 原文地址:30 Days of Mootools 1.2 Tutorials - Day 14 - Periodical and Intro
- 问:握怎样测试 MySQL安装?答:可以通过以下命令测试MySQL服务器是否工作:C:\> C:\Program Files\MySQ
- EcmaScript正則表達式( 深入淺出系列之淺出 ^_^ )在线正则表达式测试:http://www.aspxhome.com/RegE