Skip to content

Commit 6753de5

Browse files
committed
post: python concurrency
1 parent cb74495 commit 6753de5

File tree

23 files changed

+1027
-74
lines changed

23 files changed

+1027
-74
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
+++
2+
date = '2025-08-29T8:00:00+08:00'
3+
draft = false
4+
title = 'Asyncio vs Gevents in Python'
5+
tags = ['Python', 'Concurrency']
6+
+++
7+
8+
python 中 asyncio 和 gevent 是两种协程(在一个线程内实现并发)的实现, 这篇文章对比介绍这两者实现.
9+
下面先介绍一下基础概念:
10+
11+
### Coroutines 协程
12+
在 Python 中, 协程是可以暂停和继续运行的函数, 使得其是否适合并发编程. 定义使用 `async def` 语法, 协程运行编写非阻塞的操作. 在协程内, `await` 关键字用于暂停执行, 直到给定的任务完成, 从而运行其他协程在此其间并发运行.
13+
14+
### Event Loop 事件循环
15+
事件循环是一种控制结构, 它不断地处理一系列事件, 处理任务并管理程序的执行流程. 等待事件发生, 处理后再等待下一个事件. 这种机制确保程序能够以高效有序的方式响应事件, 例如用户输入、计时器或者消息.
16+
17+
下面是事件循环如何管理协程:
18+
- 任务提交: 当向事件循环提交一个协程时, 其被封装在一个 `Task` 对象中, 然后任务被安排在事件循环上运行.
19+
- 内部队列: 事件循环使用几个内部数据结构来管理和调度这些任务
20+
21+
- 就绪队列 (Ready Queue): 包含可以立即运行的任务.
22+
- I/O 选择器 (I/O Selector): 监控文件描述符, 并根据 I/O 准备情况调度任务
23+
- 计划回调 (Scheduled Callbacks): 管理计划在一定延迟后运行的任务.
24+
25+
- 调度: 事件循环不断检查这些队列和数据结构, 以确定哪些任务已准备好执行. 然后它运行这些任务, 在遇到 await 语句时, 根据需要暂停和恢复它们.
26+
- 并发管理: 通过交错执行多个协程, 事件循环无需多个线程即可实现并发. 在任何时候, 只有一个任务会运行, 但如果一个任务是 I/O 密集型的, 它会切换到另一个任务, 给人一种并行的错觉.
27+
28+
### Asyncio In Action
29+
```Python
30+
import asyncio
31+
import time
32+
33+
async def task1():
34+
print("Task 1 started")
35+
await asyncio.sleep(1) # 将控制权让给事件循环
36+
print("Task 1 resumed")
37+
await asyncio.sleep(1) # 将控制权让给事件循环
38+
39+
async def task2():
40+
print("Task 2 started")
41+
await asyncio.sleep(1) # 将控制权让给事件循环
42+
print("Task 2 resumed")
43+
await asyncio.sleep(1) # 将控制权让给事件循环
44+
45+
async def main():
46+
await asyncio.gather(task1(), task2())
47+
48+
start_time = time.time()
49+
asyncio.run(main())
50+
end_time = time.time()
51+
52+
print(f"Total time: {end_time - start_time:.2f} seconds")
53+
54+
'''
55+
任务 1 启动,并使用 await asyncio.sleep(1) 让出控制权。
56+
任务 2 启动,并使用 await asyncio.sleep(1) 让出控制权。
57+
1 秒后,两个任务都恢复。
58+
任务 1 恢复,并使用 await asyncio.sleep(1) 让出控制权。
59+
任务 2 恢复,并使用 await asyncio.sleep(1) 让出控制权。
60+
又过了 1 秒,两个任务都完成。
61+
总耗时为 2 秒。
62+
'''
63+
```
64+
通过上面的例子, 可以看到如何在进行 I/O 操作时通过切换任务来获得好处. 同样的逻辑如果按顺序执行需要 4 秒, 但使用 asyncio,可以将时间缩短一半.
65+
在提供的代码中, 事件循环就像一个在单个线程上运行的管理器. 它跟踪 task1 和 task2 这样的任务, 确保它们轮流运行.
66+
CPU 逐一处理这些任务, 但当一个任务等待某事时(例如使用 `await asyncio.sleep` 暂停), 它会将控制权交给事件循环.
67+
这使得事件循环可以切换到另一个准备好运行的任务.
68+
这样, 即使所有事情都在一个线程中发生, 任务也能高效且并发地执行, 而无需等待彼此完全完成.
69+
70+
**Asyncio 术语**
71+
72+
- `asyncio.run(coro)`
73+
- 运行主协程 `coro` 并管理事件循环, 创建一个新的事件循环, 运行协程直到完成, 然后关闭循环
74+
- 被设计用于异步函数外部, 通常在程序的入口点调用
75+
- 不能在已存在的事件循环内部运行
76+
77+
- `asyncio.create_task(coro)`
78+
- 安排协程 `core` 并发运行, 并返回一个 `Task` 对象, 这个函数对启动多个协程非常有用
79+
- 此命令需要一个已存在的事件循环才能执行
80+
- 用于启动一个应该与其他任务并发运行的协程, 非常适合需要与其他异步操作并行运行的任务
81+
82+
- `asyncio.gather(*coros)`
83+
- 并发运行多个协程并等待它们全部完成, 它将它们的结果收集到一个列表中
84+
- 需要一个活动的事件循环来管理协程
85+
86+
- `event_loop.run_untill_complete(core)`
87+
- 使用已存在的事件循环运行协程, 直到完成. 会阻塞直到协程完成并返回结果.
88+
- 不应该在异步函数内部使用, 它旨在运行协程直到其完成, 应在异步函数外部使用, 通常在同步上下文中
89+
90+
91+
### Greenlets 和 Gevent
92+
Coroutines 协程 和 greenlets(green threds 绿色线程) 都是管理并发执行的方法, 但它们在实现、控制和使用场景方面有明显的区别.
93+
Greenlets 是由 Python 的 greenlet 库提供的低级、用户空间协程实现
94+
```Python
95+
from greenlet import greenlet
96+
import time
97+
98+
def task1():
99+
start_time = time.time()
100+
print("Task 1 started")
101+
time.sleep(1) # 模拟工作
102+
print("Task 1 yielding")
103+
gr2.switch() # 将控制权让给 task2
104+
print("Task 1 resumed")
105+
time.sleep(1) # 模拟更多工作
106+
end_time = time.time()
107+
print(f"Task 1 completed in {end_time - start_time:.2f} seconds")
108+
109+
def task2():
110+
start_time = time.time()
111+
print("Task 2 started")
112+
time.sleep(1) # 模拟工作
113+
print("Task 2 yielding")
114+
gr1.switch() # 将控制权让给 task1
115+
print("Task 2 resumed")
116+
time.sleep(1) # 模拟更多工作
117+
end_time = time.time()
118+
print(f"Task 2 completed in {end_time - start_time:.2f} seconds")
119+
120+
# 创建 greenlets
121+
gr1 = greenlet(task1)
122+
gr2 = greenlet(task2)
123+
124+
# 启动 task1 并切换到 task2
125+
start_time = time.time()
126+
gr1.switch()
127+
gr2.switch()
128+
end_time = time.time()
129+
130+
print(f"Total execution time: {end_time - start_time:.2f} seconds")
131+
132+
'''
133+
Task 1 started
134+
Task 1 yielding
135+
Task 2 started
136+
Task 2 yielding
137+
Task 1 resumed
138+
Task 1 completed in 3.01 seconds
139+
Task 2 resumed
140+
Task 2 completed in 3.01 seconds
141+
Total execution time: 4.02 seconds
142+
'''
143+
```
144+
Greenlet 在协作式多任务处理方式中为用户提供了完全的灵活性, 可以切换不同的执行上下文, 但它缺乏对异步 I/O 操作的内置支持.
145+
146+
Gevent 是一个构建在 Greenlet 之上的更高级的库, 提供对非阻塞 I/O 的内置支持和更高级的抽象, 适用于 I/O 密集型应用.
147+
Gevent 抽象了上下文切换的复杂性, 并提供了对非阻塞 I/O 操作的内置支持.
148+
```Python
149+
import gevent
150+
import time
151+
152+
def task1():
153+
print("Task 1 started")
154+
gevent.sleep(1)
155+
print("Task 1 resumed")
156+
gevent.sleep(1)
157+
158+
def task2():
159+
print("Task 2 started")
160+
gevent.sleep(1)
161+
print("Task 2 resumed")
162+
gevent.sleep(1)
163+
164+
start_time = time.time()
165+
166+
# 创建 greenlets
167+
g1 = gevent.spawn(task1)
168+
g2 = gevent.spawn(task2)
169+
170+
# 启动 greenlets 并等待它们完成
171+
gevent.joinall([g1, g2])
172+
173+
end_time = time.time()
174+
175+
print(f"Total time: {end_time - start_time:.2f} seconds")
176+
177+
'''
178+
Task 1 started
179+
Task 2 started
180+
Task 1 resumed
181+
Task 2 resumed
182+
Total time: 2.03 seconds
183+
'''
184+
```
185+
186+
下面对比 gevent 和 asyncio :
187+
188+
- 事件循环管理
189+
- Gevent: 管理自己的事件循环, 并依赖猴子补丁(monkey patching)使标准 I/O 操作变为异步. 这意味着它会修改标准库模块的行为以支持其并发模型
190+
- Asyncio: 包含一个内置事件循环, 它是 Python 标准库的一部分. 提供对管理异步操作的原生支持, 无需猴子补丁
191+
192+
- 猴子补丁
193+
- Gevent: 需要显式猴子补丁来将阻塞的 I/O 操作转换为非阻塞的. 这涉及修改标准库模块以与 gevent 的事件循环集成
194+
- Asyncio: 不需要猴子补丁, 它使用 Python 原生的 async/await 功能, 该功能与标准库的异步 I/O 操作无缝集成
195+
196+
- 性能
197+
- Gevent: 对于 I/O 密集型任务是高效的, 特别是在已经使用猴子补丁的系统中. 由于需要猴子补丁, 它可能会增加开销, 但在许多场景下仍然有效
198+
- Asyncio: 通常通过对异步编程的原生支持提供高性能. 它针对现代应用进行了优化, 并提供高效的 I/O 密集型任务处理, 没有猴子补丁的开销
199+
200+
- 错误处理
201+
- Gevent: 由于使用了猴子补丁的库, 可能需要仔细管理异常. 错误处理需要在 greenlets 内部进行管理
202+
- Asyncio: 在协程中利用标准的 Python 错误处理, 原生的语法使得在异步代码中处理异常更容易
203+
204+
- 使用场景
205+
- Gevent: 非常适合将异步行为集成到现有的同步代码库中, 或与兼容 greenlets 的库一起工作, 它适用于需要将现有 I/O 操作打补丁为异步的应用
206+
- Asyncio: 最适合采用现代异步编程实践的新应用或代码库, 它非常适合高性能网络应用和 I/O 密集型任务, 这些任务从原生异步支持中受益
207+
208+
209+
Asyncio 通常是新应用的首选, 因为它倾向于现代异步编程实践, 它与 Python 的标准库无缝集成, 非常适合网络应用、实时通信和需要高并发的服务.
210+
Gevent 通常是现有同步代码库的首选, 这些代码库需要进行改造以支持并发, 它能够对标准库模块进行猴子补丁, 使其非常适合需要将阻塞的 I/O 操作转换为非阻塞的应用, 例如在网络服务器、聊天应用和实时系统中.
211+
212+
### Examples
213+
下面是一些使用 asyncio 和 gevent 的例子
214+
215+
- Web Servers
216+
- FastAPI: 虽然主要基于 Starlette 和 Pydantic 构建, 但 FastAPI 利用 asyncio 来处理异步请求, 使其成为一个用于构建 API 的高性能 Web 框架
217+
- Gunicorn with gevent workers: 一个流行的 Python 应用 WSGI HTTP 服务器, 可以使用 gevent workers 来高效地处理大量并发连接
218+
- Flask with gevent: 尽管 Flask 本身是同步的, 但将其与 gevent 结合可以并发处理多个请求, 使其适用于实时应用
219+
220+
- 实时通信
221+
- Discord.py: 一个 Discord 的 API 封装库, 它使用 asyncio 来高效地处理实时事件和交互
222+
223+
- 网络工具
224+
- AsyncSSH: 一个用于 SSHv2 协议实现的库, 在 asyncio 之上构建, 为使用 SSH、SFTP 和 SCP 提供了异步 API
225+
- ZeroMQ with gevent: 对于需要高性能消息传递的应用, gevent 经常与 ZeroMQ 一起使用, 以有效地处理异步通信模式
226+
227+
- 数据库访问
228+
- Gevent with SQLAlchemy: 对于需要异步数据库访问的应用, 将 gevent 与 SQLAlchemy 结合可以处理数据库查询而不会阻塞主线程
229+
230+
### Wrapping Up
231+
总而言之, asyncio 和 gevent 都提供了在 Python 中实现并发的强大工具, 但它们满足不同的需求和使用场景.
232+
Asyncio 是新应用的绝佳选择, 它利用了 Python 的原生异步能力, 而 gevent 则擅长将异步行为集成到现有的同步代码库中, 尤其是在处理 I/O 密集型任务时.
233+
具体使用哪种还是要根据不同的开发环境判断.

content/posts/2025-08-29_redis-learn-05.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

public/archives/index.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@
5151
<span class="max-w-[4rem] md:max-w-none truncate">Home</span></a></li><li class="flex items-center gap-1 md:gap-2 min-w-0"><span class="text-muted-foreground/50 flex-shrink-0"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
5252
</span><span class="text-foreground flex items-center gap-0.5 md:gap-1 font-medium min-w-0 flex-shrink-0"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg>
5353
<span class="max-w-[3rem] md:max-w-none truncate">Archives</span></span></li></ol></nav><header class=mb-8><div class="mb-4 flex items-center gap-3"><svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg><h1 class="text-foreground text-3xl font-bold">Archives</h1></div><p class="text-muted-foreground mb-6">Browse all articles in chronological order and discover what interests you.</p><div class="text-muted-foreground flex items-center gap-4 text-sm"><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
54-
<span>30 posts total</span></div><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
55-
<span>Timeline view</span></div></div></header><div class=relative><div class="bg-border absolute top-0 bottom-0 left-4 w-0.5"></div><div class=mb-12><div class="relative mb-8 flex items-center"><div class="bg-primary absolute left-0 z-10 flex h-8 w-8 items-center justify-center rounded-full"><svg class="h-4 w-4 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg></div><div class=ml-12><h2 class="text-foreground text-2xl font-bold">2025</h2><p class="text-muted-foreground text-sm">28
56-
posts</p></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">August 2025</h3><p class="text-muted-foreground text-xs">28
57-
posts</p></div></div><div class="ml-12 space-y-3"><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/prompt-organization/ class=block>Prompt Organization</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
54+
<span>31 posts total</span></div><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
55+
<span>Timeline view</span></div></div></header><div class=relative><div class="bg-border absolute top-0 bottom-0 left-4 w-0.5"></div><div class=mb-12><div class="relative mb-8 flex items-center"><div class="bg-primary absolute left-0 z-10 flex h-8 w-8 items-center justify-center rounded-full"><svg class="h-4 w-4 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg></div><div class=ml-12><h2 class="text-foreground text-2xl font-bold">2025</h2><p class="text-muted-foreground text-sm">29
56+
posts</p></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">August 2025</h3><p class="text-muted-foreground text-xs">29
57+
posts</p></div></div><div class="ml-12 space-y-3"><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/asyncio-vs-gevents-in-python/ class=block>Asyncio vs Gevents in Python</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
58+
<time datetime=2025-08-29>08-29</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
59+
<span>6
60+
min</span></div></div></div></div></article><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/prompt-organization/ class=block>Prompt Organization</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
5861
<time datetime=2025-08-28>08-28</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
5962
<span>3
6063
min</span></div></div></div></div></article><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/from-python-to-go/ class=block>From Python to Go</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>

0 commit comments

Comments
 (0)