python 并发函数


python 并发函数

前言

工作需要向几十万表写入亿级别数据,想使用 pyhon 的并发执行。才开始使用 ThreadPoolExecutor 发现奇慢无比,大佬说这其实是串行,并发得用 multiprocessing,立帖研究。

本文以 python3.10 为例,学习一下 python 的并发执行。

并发执行

python 并发执行分三个方面:多线程(threading)、多进程(multiprocessing)、多协程(asynico)

适当的工具选择主要取决于要执行的任务(CPU 密集型或 IO 密集型):

  • CPU 密集型(CPU-bound):也叫做计算密集型,是指 I/O 在很短时间内就可以完成,CPU 需要大量的计算和处理,特点是 CPU 占用率相当高;例如:压缩/解压缩、加密解密、正则表达式搜索、计算
  • IO 密集型(I/O bound):是指系统运作大部分的状况是 CPU 在等 I/O(硬盘,内存)的读写操作,CPU 占用率较低,例如:文件处理,网络爬虫,读写数据库;

在对比这三种方式之前,让我们先了解两个概念:并行和并发

并行和并发的区别

在 Python 中,”并发”和”并行”是两个相关但不同的概念。

并发 (Concurrency)是指程序的设计方式,允许多个任务在重叠的时间段内执行。虽然在同一时刻只能执行一个任务,但任务之间可以通过切换上下文来实现交替执行。这种交替执行的方式可以提高程序的响应性和效率,尤其是在处理 I/O 密集型任务时。在并发编程中,任务之间通常是独立的,它们可以通过多线程、多进程、协程或异步编程等方式来实现。

并行(Parallelism)是指多个任务同时执行的能力。在并行编程中,多个任务真正地同时执行,通常需要多个物理或逻辑处理单元(例如多核 CPU)。并行执行任务可以显著提高计算密集型任务的性能,但对于I/O密集型任务则没有明显的优势。

简单来说,并发是指多个任务在重叠的时间段内交替执行,通过切换上下文实现任务之间的交替执行,以提高程序的响应性和效率;而并行是指多个任务真正地同时执行,通常需要多个物理或逻辑处理单元,用于同时处理不同任务,以提高计算密集型任务的性能。

import time
import concurrent.futures

# 并发执行任务
def task(name):
    print(f'Task {name} started')
    time.sleep(2)
    print(f'Task {name} completed')

# 并行执行任务
def parallel_task(name):
    print(f'Task {name} started')
    time.sleep(2)
    print(f'Task {name} completed')

# 并发示例
with concurrent.futures.ThreadPoolExecutor() as executor:
    tasks = ['A', 'B', 'C']
    executor.map(task, tasks)

# 并行示例
with concurrent.futures.ProcessPoolExecutor() as executor:
    tasks = ['X', 'Y', 'Z']
    executor.map(parallel_task, tasks)

在上面的示例中,’task’ 函数模拟了一个耗时 2 秒的任务,并使用线程池实现了并发执行。’parallel_task’ 函数也是一个耗时 2 秒的任务,但使用了进程池实现了并行执行。我们可以运行这段代码,观察任务启动时 python 进程的数目、执行的顺序和时间,以更好地理解并发和并行的区别。

然后,我们来聊 多进程、多线程、多协程的不同、关系以及怎么选择。

进程、线程、协程的对比

  • 多进程:
    • 优点:可以实现并行,且只有多进程可以实现并行
    • 缺点:占用资源多,可启动数目最少
  • 多线程:
    • 占用资源少,轻量级
    • python 的线程是无法并行的(占用多个 cpu),只能进行并发
    • 切换线程也是有开销的。
    • 适合 IO 密集型运算、同时运行任务不多(线程可启动数量也是有限制的)
  • 多协程:
    • 优点:内存开销最小,可启动数量最多
    • 缺点:支持的库比较少,代码复杂,例如爬虫不支持,所以想用多协程爬取的话,可以用 aiohttp,不能用 requests
    • 适用于:IO 密集型、超多任务运行

进程、线程、协程的关系

  • 一个进程中可以启动很多线程
  • 一个线程中可以启动很多协程

python 慢的原因

两个原因:

  • 是解释型语言,边解释边执行
  • GIL,无法利用多核 CPU

GIL 是什么,为什么有 GIL?

全局解释器锁(Global interpreter lock),是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。

python 设计初期为了解决线程并发的问题引入了 GIL,但是现在很难去除,本质是一种锁,它的好处在于简化了 python 对共享资源的管理,但是导致 python 无法实现真正的多线程执行。

怎样规避 GIL 带来的限制:

  • IO 期间线程会释放 GIL,实现 CPU 和 IO 的并发,因此 GIL 的存在对于 IO 密集型计算是有好的,但是对 CPU 密集型则会拖累速度
  • 利用 multiprocessing,可以利用多核 CPU 的优势

怎样选择

  • IO 密集型运算优先选择多进程
  • 若满足三点:需要超多任务量、有现成协程库支持 、代码复杂度可以接受,则选择协程,否则选择线程

threading — 基于线程的并行

3.10.13 Documentation » Python 标准库 » 并发执行 » threading — 基于线程的并行

multiprocessing — 基于进程的并行

3.10.13 Documentation » Python 标准库 » 并发执行 » multiprocessing — 基于进程的并行

参考


文章作者: Pudding
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Pudding !
  目录