【Python 进阶】并发的相关概念及代码示例

前言

举一个简单的例子先了解一下什么是 同步异步阻塞和非阻塞。 (不想了解这些基础概念的同学可以直接跳过)

例子来自知乎:

打电话问书店老板有没有《分布式系统》这本书,如果是同步机制,书店老板会查完之后立马告诉你(返回结果);如果是异步机制,书店老板会先挂掉电话(先不返回结果)然后查完之后再回电你(回调)。

还是同样的例子。打电话问书店老板有没有《分布式系统》这本书,如果是阻塞式调用,你会一直守在电话边什么也不干,直到老板告诉你结果;如果是非阻塞调用,你自己会先一边玩去,偶尔确认一下老板是否回复了你。

同步和异步更多关注的是消息的通信机制(行为方式),阻塞和非阻塞更多关注程序等待调用结果时的状态

多进程 & 多线程

进程是系统独立调度和分配系统资源(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

https://www.cnblogs.com/hucho...

https://www.zhihu.com/questio...

阅读 339

推荐阅读