python 中 asyncio 和 gevent 是两种协程(在一个线程内实现并发)的实现, 这篇文章对比介绍这两者实现.
下面先介绍一下基础概念:

Coroutines 协程

在 Python 中, 协程是可以暂停和继续运行的函数, 使得其是否适合并发编程. 定义使用 async def 语法, 协程运行编写非阻塞的操作. 在协程内, await 关键字用于暂停执行, 直到给定的任务完成, 从而运行其他协程在此其间并发运行.

Event Loop 事件循环

事件循环是一种控制结构, 它不断地处理一系列事件, 处理任务并管理程序的执行流程. 等待事件发生, 处理后再等待下一个事件. 这种机制确保程序能够以高效有序的方式响应事件, 例如用户输入、计时器或者消息.

下面是事件循环如何管理协程:

Asyncio In Action

PYTHON
import asyncio
import time

async def task1():
    print("Task 1 started")
    await asyncio.sleep(1)  # 将控制权让给事件循环
    print("Task 1 resumed")
    await asyncio.sleep(1)  # 将控制权让给事件循环

async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)  # 将控制权让给事件循环
    print("Task 2 resumed")
    await asyncio.sleep(1)  # 将控制权让给事件循环

async def main():
    await asyncio.gather(task1(), task2())

start_time = time.time()
asyncio.run(main())
end_time = time.time()

print(f"Total time: {end_time - start_time:.2f} seconds")

'''
任务 1 启动,并使用 await asyncio.sleep(1) 让出控制权。
任务 2 启动,并使用 await asyncio.sleep(1) 让出控制权。
1 秒后,两个任务都恢复。
任务 1 恢复,并使用 await asyncio.sleep(1) 让出控制权。
任务 2 恢复,并使用 await asyncio.sleep(1) 让出控制权。
又过了 1 秒,两个任务都完成。
总耗时为 2 秒。
'''
Click to expand and view more

通过上面的例子, 可以看到如何在进行 I/O 操作时通过切换任务来获得好处. 同样的逻辑如果按顺序执行需要 4 秒, 但使用 asyncio,可以将时间缩短一半. 在提供的代码中, 事件循环就像一个在单个线程上运行的管理器. 它跟踪 task1 和 task2 这样的任务, 确保它们轮流运行. CPU 逐一处理这些任务, 但当一个任务等待某事时(例如使用 await asyncio.sleep 暂停), 它会将控制权交给事件循环. 这使得事件循环可以切换到另一个准备好运行的任务. 这样, 即使所有事情都在一个线程中发生, 任务也能高效且并发地执行, 而无需等待彼此完全完成.

Asyncio 术语

Greenlets 和 Gevent

Coroutines 协程 和 greenlets(green threds 绿色线程) 都是管理并发执行的方法, 但它们在实现、控制和使用场景方面有明显的区别.
Greenlets 是由 Python 的 greenlet 库提供的低级、用户空间协程实现

PYTHON
from greenlet import greenlet
import time

def task1():
    start_time = time.time()
    print("Task 1 started")
    time.sleep(1)  # 模拟工作
    print("Task 1 yielding")
    gr2.switch()  # 将控制权让给 task2
    print("Task 1 resumed")
    time.sleep(1)  # 模拟更多工作
    end_time = time.time()
    print(f"Task 1 completed in {end_time - start_time:.2f} seconds")

def task2():
    start_time = time.time()
    print("Task 2 started")
    time.sleep(1)  # 模拟工作
    print("Task 2 yielding")
    gr1.switch()  # 将控制权让给 task1
    print("Task 2 resumed")
    time.sleep(1)  # 模拟更多工作
    end_time = time.time()
    print(f"Task 2 completed in {end_time - start_time:.2f} seconds")

# 创建 greenlets
gr1 = greenlet(task1)
gr2 = greenlet(task2)

# 启动 task1 并切换到 task2
start_time = time.time()
gr1.switch()
gr2.switch()
end_time = time.time()

print(f"Total execution time: {end_time - start_time:.2f} seconds")

'''
Task 1 started
Task 1 yielding
Task 2 started
Task 2 yielding
Task 1 resumed
Task 1 completed in 3.01 seconds
Task 2 resumed
Task 2 completed in 3.01 seconds
Total execution time: 4.02 seconds
'''
Click to expand and view more

Greenlet 在协作式多任务处理方式中为用户提供了完全的灵活性, 可以切换不同的执行上下文, 但它缺乏对异步 I/O 操作的内置支持.

Gevent 是一个构建在 Greenlet 之上的更高级的库, 提供对非阻塞 I/O 的内置支持和更高级的抽象, 适用于 I/O 密集型应用. Gevent 抽象了上下文切换的复杂性, 并提供了对非阻塞 I/O 操作的内置支持.

PYTHON
import gevent
import time

def task1():
    print("Task 1 started")
    gevent.sleep(1)
    print("Task 1 resumed")
    gevent.sleep(1)

def task2():
    print("Task 2 started")
    gevent.sleep(1)
    print("Task 2 resumed")
    gevent.sleep(1)

start_time = time.time()

# 创建 greenlets
g1 = gevent.spawn(task1)
g2 = gevent.spawn(task2)

# 启动 greenlets 并等待它们完成
gevent.joinall([g1, g2])

end_time = time.time()

print(f"Total time: {end_time - start_time:.2f} seconds")

'''
Task 1 started
Task 2 started
Task 1 resumed
Task 2 resumed
Total time: 2.03 seconds
'''
Click to expand and view more

下面对比 gevent 和 asyncio :

Asyncio 通常是新应用的首选, 因为它倾向于现代异步编程实践, 它与 Python 的标准库无缝集成, 非常适合网络应用、实时通信和需要高并发的服务.
Gevent 通常是现有同步代码库的首选, 这些代码库需要进行改造以支持并发, 它能够对标准库模块进行猴子补丁, 使其非常适合需要将阻塞的 I/O 操作转换为非阻塞的应用, 例如在网络服务器、聊天应用和实时系统中.

Examples

下面是一些使用 asyncio 和 gevent 的例子

Wrapping Up

总而言之, asyncio 和 gevent 都提供了在 Python 中实现并发的强大工具, 但它们满足不同的需求和使用场景. Asyncio 是新应用的绝佳选择, 它利用了 Python 的原生异步能力, 而 gevent 则擅长将异步行为集成到现有的同步代码库中, 尤其是在处理 I/O 密集型任务时. 具体使用哪种还是要根据不同的开发环境判断.

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut