Python 异步编程:Asyncio 实现原理

news2024/11/15 21:40:52

常见的并发模型

  • 多进程/多线程
  • 异步
  • Actor
  • Pub/Sub
    在这里插入图片描述

Python 异步的基石:协程

协程简介

概念:协作式多任务的子程序,用户态线程或微线程(Coroutine)。
特点:子程序执行可以中断,恢复后不会丢失之前的上下文状态。
区别:与线程不同,协程是用户级的非抢占式调度,比线程更轻量(纳秒级的 CPU 时间),无资源竞争,无需加锁。
Python 使用协程 + IO 多路复用实现异步编程。

Python 协程的发展历程

  • PEP 255 (Python 2.2):引入生成器,实现可迭代对象的惰性求值。
  • PEP 342:引入 sendthrow 方法,允许在 try/finally 中使用 yield
  • PEP 380 (Python 3.3):引入 yield from,简化生成器/协程的实现和串联。
  • PEP 3156 (Python 3.4):试验性引入异步 I/O 框架 asyncio。
  • PEP 492 (Python 3.5):通过 async/await 语法明确支持协程。
  • Python 3.6asyncio 成为标准库的一部分。

Python 第三方异步 I/O 方案

  • Greenlet
  • Gevent
  • Eventlet
  • Twisted

官方异步模块 Asyncio

认识几个概念

  • Task:协程的高层抽象。
  • Future:沟通协程和事件循环。
  • Coroutine:协程与 await 搭配使用的语法糖。
  • Event Loop:事件循环协程的执行调度。

从一个爬虫例子着手

阻塞方式写法
import socket
def blocking_run():
    sock = socket.socket()
    sock.connect(("example.com", 80))
    request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"
    sock.send(request.encode())
    response = sock.recv(2048)
    return response
for _ in range(10):
    print(blocking_run())
非阻塞方式 - 事件循环 + 回调
import socket
from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector
selector = DefaultSelector()
def no_blocking_run():
    def on_connect(key, mask):
        selector.unregister(key.fd)
        request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"
        sock.send(request.encode())
        selector.register(key.fd, EVENT_READ, on_response)
        return "on_connect success"
    def on_response(key, mask):
        response = sock.recv(2048)
        selector.unregister(key.fd)
        return "on_response success"
    sock = socket.socket()
    sock.setblocking(False)
    try:
        sock.connect(("example.com", 80))
    except BlockingIOError:
        pass
    selector.register(sock.fileno(), EVENT_WRITE, on_connect)
def event_loop():
    for _ in range(10):
        no_blocking_run()
    while True:
        events = selector.select(timeout=3)
        if not events:
            break
        for event_key, event_mask in events:
            callback = event_key.data
            print(callback(event_key, event_mask))
执行过程说明
  1. 启动执行函数,创建 socket 连接并在 selector 上注册可写事件,无阻塞操作,函数立即返回。
  2. 遍历 10 个不同的下载任务,注册连接事件。
  3. 启动事件循环,阻塞在事件监听上。
  4. 当某个下载任务 EVENT_WRITE 被触发,回调其 on_connect 方法。
  5. 进入下一轮事件循环,处理事件。
回调地狱问题
  • 回调函数执行不正常时,错误处理困难。
  • 嵌套回调导致代码复杂。
  • 状态共享和管理困难。
callback_a(callback_b(callback_c(callback_d(...))))

对比基于协程的解决方案

Future 对象

作为数据接收代理,和系统事件沟通的桥梁

class Future:
    def __init__(self):
        self.result = None
        self._callbacks = []
    def add_done_callback(self, func):
        self._callbacks.append(func)
    def set_result(self, result):
        self.result = result
        for func in self._callbacks:
            func(self)
基于协程重构
def no_blocking_v2():
    sock = socket.socket()
    sock.setblocking(False)
    try:
        sock.connect(("example.com", 80))
    except BlockingIOError:
        pass
    f = Future()
    selector.register(sock.fileno(), EVENT_WRITE, lambda: f.set_result(None))
    yield f
    selector.unregister(sock.fileno())
    request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"
    sock.send(request.encode())
    f = Future()
    selector.register(sock.fileno(), EVENT_READ, lambda: f.set_result(sock.recv(2048)))
    yield f
    selector.unregister(sock.fileno())
Task 对象

协程高层抽象,能管理和控制协程

class Task:
    def __init__(self, coro):
        self.coro = coro
        f = Future()
        f.set_result(None)
        self.next(f)
    def next(self, future):
        try:
            next_future = self.coro.send(future.result)
        except StopIteration:
            return
        next_future.add_done_callback(self.next)
加入 Event Loop 驱动协程运行
def event_loop_v2():
    for _ in range(10):
        Task(no_blocking_v2())
    while True:
        events = selector.select(timeout=3)
        if not events:
            break
        for event_key, event_mask in events:
            callback = event_key.data
            callback()

整个过程如图所示
在这里插入图片描述

协程风格与回调风格对比

  • 回调风格
    • 存在链式回调,破坏同步代码结构。
    • 需要在回调之间维护状态。
  • 协程风格
    • 无链式调用。
    • Selector 的回调只需设置 future 的值。
    • 事件循环只管调度,结构更接近同步代码。
    • 无需在多个协程之间维护状态。

结语

上述只是原型的简单介绍,实际 Asyncio 远比上述原型复杂,需要实现零拷贝、公平调度、异常处理、任务状态管理等。理解原理有助于在后续开发中更好地处理异步问题。

如果你对异步编程感兴趣,欢迎体验 AppBoot 项目,它基于FastAPI提供了一个类似 Django 的开发体验,完全异步,并内置 SQLAlchemy 2.0 支持。使用 AppBoot,你可以轻松快速地开发企业级的异步 Web 服务,感受异步编程的强大与乐趣。

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

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

相关文章

uniapp 荣耀手机 没有检测到设备 运行到Android手机 真机运行

背景: 使用uniapp框架搭建的项目,开发的时候在浏览器运行,因为项目要打包成App,所以需要真机联调,需要运行到Android手机,在手机上查看/运行项目。通过真机调试才能确保软件开发的准确性和页面显示的完整性…

mac 2k显示器 配置

前言 今年5月份买了一个2k显示器,刚收到的时候发现只有一个1080 x 720(HiDPI)分辨率是人眼看起来比较舒服的,于是一直用着。但是直到开始写前端代码的时候,我才发现,网页在2k显示器和内建显示器的布局竟然…

Python 循环引用与内存泄漏:深度解析

Python 循环引用与内存泄漏:深度解析 在Python编程中,循环引用和内存泄漏是两个需要特别注意的问题。本文将深入探讨Python中的循环引用现象、其导致的内存泄漏问题,并提供详细的解决思路与方法。同时,我们还将分析一些常见场景&…

【CSDN平台BUG】markdown图片链接格式被手机端编辑器自动破坏(8.6 已修复)

文章目录 bug以及解决方法bug原理锐评后续 bug以及解决方法 现在是2024年8月,我打开csdn手机编辑器打算修改一下2023年12月的一篇文章,结果一进入编辑器,源码就变成了下面这个样子,我起初不以为意,就点击了发布&#…

【IoT NTN】面向 5G/6G 卫星:NTN 标准发展、关键技术与未来思考

目录 国际标准化进展 架构及关键技术 5GNTN组网架构 关键技术 1、时频同步技术 2、覆盖增强技术 3、移动性管理技术 4、混合自动重传请求技术 5、自适应调制与编码技术 挑战与潜在解决方案 星地协同全域覆盖模型 星地协同多维资源调度 星地协同多层卫星路由 星地…

网络主播进入国家职业分类!1500万主播将有新身份

在营销策划界摸爬滚打十多年的我,最近可是被一则劲爆消息给震撼到了——国家正式官宣了19个全新职业,这可是职场版图的一次大扩张啊! 其中最让人眼前一亮的是,网络主播竟然堂而皇之地登上了新职业的大舞台,正式“转正…

【多线程-从零开始-陆】wait、notify和notifyAll

线程饿死 一个或多个线程因为无法获得执行所需的资源(如CPU时间、锁、或其他同步控制)而被长时间阻塞或延迟执行的情况。尽管这些线程可能处于可执行状态并且已经准备好运行,但由于资源分配的不均衡或调度策略的问题,它们无法获得…

快讯 | 单卡4090显卡即可解锁视频生成,智谱AI CogVideoX模型开源!

在数字化浪潮的推动下,人工智能(AI)正成为塑造未来的关键力量。硅纪元视角栏目紧跟AI科技的最新发展,捕捉行业动态;提供深入的新闻解读,助您洞悉技术背后的逻辑;汇聚行业专家的见解,…

认识RAID磁盘阵列

文章目录 1、RAID磁盘阵列介绍2、多种RAID级别RAID 0 (条带化存储)RAID 1 (镜像存储)RAID 5RAID 6RAID 10(先做镜象,再做条带)RAID 01(先做条带,再做镜象)区别…

leetcode日记(64)最小覆盖子串

很复杂的题目,无论是思路还是实践都很难… 思路还是看了答案(?)设定两个指针“框”出一串字符串,初始两个指针都指在s的零位,先移动下指针,直到使框出的字符串中包含t中所有字符串,…

c# 排序、强转枚举

List<Tuple<double,int>> mm中doble从小到大排序 mm本身排序 在C#中&#xff0c;如果你有一个List<Tuple<double, int>>类型的集合mm&#xff0c;并且你想要根据Tuple中的double值&#xff08;即第一个元素&#xff09;从小到大进行排序&#xff0c;同…

TCP全队列连接,tcpdum抓包

TCP全队列连接&#xff0c;tcpdum抓包 1. listen的第二个参数作用2. 理解全连接队列&#xff08;原理&#xff09;3. 为什么要有全连接队列并且队列长度要适当4. 使用不tcpdump 进行抓包&#xff0c;分析TCP过程&#xff08;三次握手&#xff0c;四次挥手&#xff09;4.1安装tc…

CVE-2023-28525~文件上传【春秋云境靶场渗透】

# 今天我们来攻克CVE-2023-28525文件上传漏洞# 看到页面&#xff0c;经过尝试 账号&#xff1a;admin 密码&#xff1a;admin# 发现Posts可以添加文件上传# 尝试发现&#xff0c;只能上传图片格式的文件&#xff0c;那我们试一下看能不能上传成功# 发现上传失败&#xff0c;发现…

console与控制台使用demo

文章目录 console是一个普通(实例)对象A、函数对象拥有2个属性&#xff1a;B、console只有__proto__属性&#xff1a;C、综上&#xff0c;console是一个实例对象 console拥有的函数&#xff08;特殊列举&#xff09;占位符用法以参数为key记录执行次数——count()dir()和dirxml…

Cmake基础教程--第1章:初识cmake

Cmake基础教程--第1章&#xff1a;初识cmake 何为CmakeCmake的优缺点优点缺点 Cmake与makefile之间的关系C/C 如何编译为可执行文件一个最简单的Cmake示例 从本篇文章开始&#xff0c;我会出一系列文章&#xff0c;致力于Cmake工具的学习使用。阅读本文章之前&#xff0c; 作者…

#MFC 编译错误msvcrt.lib(exe_main.obj) : error LNK2019

如果是CMake&#xff0c;需要改如下&#xff1a; set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:CONSOLE") 改为 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS") 如果是mfc工程文件&#xff0c;需…

【数据结构】五、树:7.哈夫曼树、哈夫曼编码

3.哈夫曼树和哈夫曼编码 文章目录 3.哈夫曼树和哈夫曼编码3.1带权路径长度3.2哈夫曼树的定义和原理3.3哈夫曼树的构造代码实现 3.4特点3.5哈夫曼编码压缩比代码实现 3.6哈夫曼树-C 3.1带权路径长度 #mermaid-svg-yeVKyVnDwvdIc5ML {font-family:"trebuchet ms",verda…

【第18章】Spring Cloud之Gateway配置

文章目录 前言一、示例二、Route Metadata Configuration1. 路由元数据配置2. 获取元数据配置 三、Http timeouts configuration(请求超时配置)1. Global timeouts(全局)2. Per-route timeouts(路由) 四、CORS Configuration(跨域配置)1. Global CORS Configuration(全局)2. Ro…

【HarmonyOS NEXT星河版开发学习】小型测试案例07-弹性布局小练习

个人主页→VON 收录专栏→鸿蒙开发小型案例总结​​​​​ 基础语法部分会发布于github 和 gitee上面&#xff08;暂未发布&#xff09; 前言 在鸿蒙&#xff08;HarmonyOS&#xff09;开发中&#xff0c;Flex布局是一种非常有用的布局方式&#xff0c;它允许开发者创建灵活且响…

FPGA知识基础之--存储器知识点总结以及基于ip核的简单双端口RAM的实现和仿真(附RTL代码和Testbench代码)

目录 前言一、存储器的分类二、实验任务三 、简单&#xff08;伪&#xff09;双端口四、程序设计4.1 模块4.2 时序分析4.3 RTL代码ram_wr 写模块2.ram_rd 写模块3.top模块 五、仿真 前言 笔者在最近的存储器学习时&#xff0c;遇到了一些问题&#xff0c;为此笔者用本篇博客来…