python 的并发模型

Table of Contents

并发指同时处理多件事
并行指同时做多件事
二者不同, 但有联系
一个关于结构, 一个关于执行
并发用于制定方案, 用来解决可能但未必并行的问题

  • Rob Pike
    Go 语言创造者之一

python 解释器仅使用一个线程运行用户的程序和内存垃圾回收程序, 使用 threading 或 current.future 库可以启动额外的 python 线程, 但是 python 有一个 GIL(Global Interpreter Lock ) 全局解释器锁, 任意时间点上只能有一个 python 线程可以持有 GIL , 这个锁控制对象引用计数和解释器的内部状态, 任意时间点上只有一个线程才能执行 python 代码, 哪怕你有多个 CPU 或者 CPU 有多个核.

为防止一个 Python 线程无限期持有 GIL, pyhon 解释器每 5 毫秒暂停当前线程, 释放 GIL , 由操作系统调度程序来挑选一个等待的线程

Python 标准库中发起系统调用的函数都可以释放 GIL, 例如执行磁盘 IO, 网络 IO, 以及 time.sleep(), 你也可以自己以 C 语言来实现 python 扩展来释放 GIL

所以 IO 密集型的应用程序可以多用 python 线程或者协程, 而计算密集型的应用程序最好使用多进程. 而协程是指可以暂时执行挂起自身并在以后再恢复执行的函数.

什么是并发与并行?

并发与并行是高性能编程中的两个常用术语。尽管它们常常被混淆,但有细微差别。

  • 并发:程序通过切换任务来执行多个任务,而这些任务可能并不同时运行。例如,一个程序可以一边下载文件一边处理其他数据,但这些任务是交替执行的。

  • 并行:多个任务同时执行,通常在多核处理器上实现。例如,程序可以利用多核处理器同时执行多个任务,每个任务运行在不同的核心上。

Python 对并发的支持

  1. threading 模块:使用线程实现并发,适用于 I/O 密集型任务。
  2. asyncio 模块:提供协程支持,实现异步编程,适合大量 I/O 操作。
  3. multiprocessing 模块:用于多进程,适用于 CPU 密集型任务。

示例 1:使用 threading 模块

threading 模块允许我们创建多个线程来执行任务,线程适用于 I/O 密集型操作,如文件读写或网络请求。

代码示例:

import threading
import time

def task(name):
    print(f"线程 {name} 开始")
    time.sleep(2)  # 模拟 I/O 操作
    print(f"线程 {name} 结束")

# 创建线程
threads = []
for i in range(5):
    t = threading.Thread(target=task, args=(i,))
    threads.append(t)
    t.start()

# 等待所有线程完成
for t in threads:
    t.join()

print("所有线程执行完毕")

解释:

• 我们创建了 5 个线程,并让它们并发执行任务 task。
• time.sleep(2) 模拟一个耗时的 I/O 操作。

运行结果:

线程 0 开始
线程 1 开始
线程 2 开始
线程 3 开始
线程 4 开始
线程 0 结束
线程 1 结束
线程 2 结束
线程 3 结束
线程 4 结束
所有线程执行完毕

示例 2:使用 asyncio 模块

asyncio 模块提供了协程支持,允许我们以异步方式执行任务,适用于 I/O 密集型任务。它与 threading 的区别在于,它并不依赖多线程,而是依赖于事件循环。

代码示例:

import asyncio

async def async_task(name):
    print(f"任务 {name} 开始")
    await asyncio.sleep(2)  # 模拟异步 I/O 操作
    print(f"任务 {name} 结束")

async def main():
    tasks = [async_task(i) for i in range(5)]
    await asyncio.gather(*tasks)

# 运行事件循环
await main()

解释:

  • 我们定义了一个异步任务 async_task,并使用 asyncio.gather 同时运行多个任务。
  • await asyncio.sleep(2) 模拟了一个异步的 I/O 操作。

运行结果:

任务 0 开始
任务 1 开始
任务 2 开始
任务 3 开始
任务 4 开始
任务 0 结束
任务 1 结束
任务 2 结束
任务 3 结束
任务 4 结束

示例 3:使用 multiprocessing 模块

multiprocessing 模块允许我们在多个进程上执行任务,适用于 CPU 密集型操作。与线程不同,每个进程拥有独立的内存空间。

代码示例:

import multiprocessing
import time

def cpu_task(name):
    print(f"进程 {name} 开始")
    time.sleep(2)  # 模拟 CPU 密集型操作
    print(f"进程 {name} 结束")

# 创建进程
processes = []
for i in range(5):
    p = multiprocessing.Process(target=cpu_task, args=(i,))
    processes.append(p)
    p.start()

# 等待所有进程完成
for p in processes:
    p.join()

print("所有进程执行完毕")

解释:

  • 这里我们使用 multiprocessing.Process 来创建 5 个独立的进程,每个进程执行 cpu_task。
  • 进程间不会共享内存,适合 CPU 密集型任务。

运行结果:

进程 0 开始
进程 1 开始
进程 2 开始
进程 3 开始
进程 4 开始
进程 0 结束
进程 1 结束
进程 2 结束
进程 3 结束
进程 4 结束
所有进程执行完毕

结论

  • threading 适用于 I/O 密集型任务,如文件读写、网络请求等。
  • asyncio 适合大量 I/O 任务,并且它使用协程代替线程,减少了线程的开销。
  • multiprocessing 更适合 CPU 密集型任务,因为它利用了多个进程,每个进程可以使用不同的 CPU 核心。

Comments |0|

Legend *) Required fields are marked
**) You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Category: 似水流年