前言
举一个简单的例子先了解一下什么是 同步异步
,阻塞和非阻塞
。 (不想了解这些基础概念的同学可以直接跳过)
例子来自知乎:
打电话问书店老板有没有《分布式系统》这本书,如果是同步机制,书店老板会查完之后立马告诉你(返回结果);如果是异步机制,书店老板会先挂掉电话(先不返回结果)然后查完之后再回电你(回调)。
还是同样的例子。打电话问书店老板有没有《分布式系统》这本书,如果是阻塞式调用,你会一直守在电话边什么也不干,直到老板告诉你结果;如果是非阻塞调用,你自己会先一边玩去,偶尔确认一下老板是否回复了你。
同步和异步更多关注的是消息的通信机制(行为方式),阻塞和非阻塞更多关注程序等待调用结果时的状态。
多进程 & 多线程
进程是系统独立调度和分配系统资源(CPU、内存)的基本单位,系统可以开启多个进程(同时开启的最大数量和机器 CPU 数一致),在同一时间内并行执行多个任务。Python3.2 前使用 multiprocessing | threading 模块编写多进程和多线程的代码,之后对其进行了抽象和封装提供了 concurrent.futures 模块,里面的 ProcessPoolExecutor | ThreadPoolExecutor 用于创建多进程和多线程的代码。
其中的重要概念
Exectuor
:它是任务抽象类,提供异步执行的方法 (submit, map)
Future
:Executor 返回的异步操作的实例
进程池代码实现
import time
from concurrent.futures import ProcessPoolExecutor
def task(task_id):
time.sleep(1)
print(f'now is {task_id}')
return task_id
if __name__ == '__main__':
start = time.time()
cpu_count = multiprocessing.cpu_count()
with ProcessPoolExecutor() as p:
futures = [p.submit(task, i) for i in range(cpu_count)]
print([future.result() for future in futures])
print(time.time() - start)
# Result:
# now is 0
# now is 2
# now is 1
# now is 3
# [0, 1, 2, 3]
# 1.3288664817810059
线程池代码实现
多线程不需要切换上下文,所以此时线程池会略快
import time
from concurrent.futures import ThreadPoolExecutor
def task(task_id):
time.sleep(1)
print(f'now is {task_id}')
return task_id
if __name__ == '__main__':
start = time.time()
with ThreadPoolExecutor() as p:
futures = [p.submit(task, i) for i in range(5)]
print([future.result() for future in futures])
print(time.time() - start)
# Result:
# now is 1
# now is 0
# now is 4
# now is 3
# now is 2
# [0, 1, 2, 3, 4]
# 1.0025899410247803
GIL & 协程
这里提一下 GIL 这个 python 老生常谈的问题。
GIL 即 Global Interpreter Lock 全局解释锁,Python 为了线程安全,在多线程执行的时候引入了全局锁,保证同一时刻只有唯一的线程在执行,然后过 100 ticks 切换,在 I/O 密集型的程序执行时影响不大,但对于 CPU 密集型程序来说无疑是限制了执行效率。所以 Python 后来就映入了协程 Coroutine。
协程是一种用户态的轻量级线程,协程的作用是在执行函数 A 时,可以随时中断,去执行函数 B,然后又可以切换函数 A,实现多任务异步执行。
代码示例:
import asyncio
# 定义协程(可以随时执行,随时切换的生成器)
async def task(i):
print(f'start {i} task')
await asyncio.sleep(1)
print(f'end {i} task')
loop = asyncio.get_event_loop() # 获取事件循环对象,可以将函数注册到事件上,满足条件则调用对应的函数
tasks = [task(i) for i in range(3)] # 任务对象
loop.run_until_complete(asyncio.wait(tasks)) # 执行协程任务
# Result:
# start 1 task
# start 2 task
# start 0 task
# end 1 task
# end 2 task
# end 0 task
参考
https://cuiqingcai.com/6160.html
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。