asyncio 异步编程

碰到 Python 使用的新概念:异步编程 asyncio 库,这个和多线程编程的 threading 有什么区别呢?

特性asyncio (异步编程)threading (多线程编程)
执行模型事件循环和协程,非阻塞操作系统线程,阻塞
资源消耗较低,单线程,适合 I/O 密集型任务较高,多线程,适合 CPU 密集型任务
上下文切换由程序控制,协程间切换,开销小由操作系统控制,线程间切换,开销大
共享资源和同步通常不需要锁,因为协程在等待 I/O 时会主动让出控制权需要锁来同步共享资源,以避免竞态条件
编程模型使用 async 和 await 关键字定义和调用异步函数传统的多线程编程模型,使用 threading. Thread 创建和管理线程
错误处理可以使用 try… Except 捕获异常异常处理相对复杂,线程中的异常可能不会直接反映到主线程
适用场景I/O 密集型和高并发场景,如 Web 服务器、网络爬虫等CPU 密集型任务和需要并行处理的场景
性能在 I/O 密集型任务中性能通常优于多线程在 CPU 密集型任务中性能可能优于单线程的异步编程

这里引入了协程的概念,该如何去理解呢?

特性进程 (Process)线程 (Thread)协程 (Coroutine)
定义操作系统进行资源分配和调度的独立单位,应用程序运行的实例。进程中的一个实体,系统独立调度和分派的基本单位。程序组件,允许不同的执行线程暂停和恢复执行。
资源拥有独立的内存空间,不共享。共享进程的资源,如内存空间、文件句柄等。共享线程的资源,因为它们运行在同一线程中。
开销创建和销毁开销大,涉及系统资源分配和回收。创建和销毁开销相对较小,因为共享进程资源。创建和销毁开销非常小,不需要系统层面的切换。
调度由操作系统调度。由操作系统调度。通常由程序自己调度,或由框架 / 库管理。
独立性完全独立。半独立,共享进程资源。高度依赖,共享线程资源。
适用场景适用于隔离性强的并发任务。适用于资源共享的并发任务。适用于 I/O 密集型的并发任务。
层次关系进程包含线程。线程包含协程。协程依附于线程。
资源共享进程间不共享资源。线程间共享进程资源。
1
2
3
4
5
6
7
8
9
10
11
12
import asyncio
from datetime import datetime
import nest_asyncio
nest_asyncio.apply()
async def hello():
print('1-',datetime.now().strftime("%H:%M:%S"))
await asyncio.sleep(1)
print('2-',datetime.now().strftime("%H:%M:%S"))
if __name__=='__main__':
print('0-',datetime.now().strftime("%H:%M:%S"))
asyncio.run(hello())
print('3-',datetime.now().strftime("%H:%M:%S"))
0- 08:58:10
1- 08:58:10
2- 08:58:11
3- 08:58:11
1
2
3
4
5
6
7
8
9
10
11
12
13
import random
async def hello(flag:str):
sleep_time= random.randint(1,5)
print(flag+'-1-',datetime.now().strftime("%H:%M:%S"))
print(flag+'-sleep_time:',sleep_time)
await asyncio.sleep(sleep_time)
print(flag+'-2-',datetime.now().strftime("%H:%M:%S"))
async def main():
await asyncio.gather(hello('Thread1'),hello('Thread2'))
if __name__=='__main__':
print('0-',datetime.now().strftime("%H:%M:%S"))
asyncio.run(main())
print('3-',datetime.now().strftime("%H:%M:%S"))
0- 09:38:11
Thread1-1- 09:38:11
Thread1-sleep_time: 3
Thread2-1- 09:38:11
Thread2-sleep_time: 5
Thread1-2- 09:38:14
Thread2-2- 09:38:16
3- 09:38:16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import threading
import time
import random
from datetime import datetime
# 模拟异步函数hello的行为
def hello(flag: str):
sleep_time = random.randint(1, 5)
print(f"{flag}-1- {datetime.now().strftime('%H:%M:%S')}")
print(f"{flag}-sleep_time: {sleep_time}")
# 使用time.sleep来模拟异步等待
time.sleep(sleep_time)
print(f"{flag}-2- {datetime.now().strftime('%H:%M:%S')}")
# 线程工作函数
def thread_work(flag: str):
hello(flag)
if __name__=='__main__':
# 创建两个线程
thread1 = threading.Thread(target=thread_work, args=("Thread1",))
thread2 = threading.Thread(target=thread_work, args=("Thread2",))
# 启动线程
print('0-',datetime.now().strftime("%H:%M:%S"))
thread1.start()
thread2.start()
# 等待线程结束
thread1.join()
thread2.join()
print('3-',datetime.now().strftime("%H:%M:%S"))
0- 09:40:40
Thread1-1- 09:40:40
Thread1-sleep_time: 5
Thread2-1- 09:40:40
Thread2-sleep_time: 2
Thread2-2- 09:40:42
Thread1-2- 09:40:45
3- 09:40:45

从代码运行结果来看,asyncio 与 threading 有相同输出,可以认为:asyncio 一直是单线程,通过在软件层面抽象出协程实现并发