加速你的Python程序(线程/进程池)

简介: 加速你的Python程序(线程/进程池)

加速的方法

对于加速程序速度,有两个思路,对于一个任务量固定的程序而言

  • 同一时刻计算的数据量更多
  • 单次运算计算的数据量更多

前者可以通过使用线程或者进程来进行实现,后者则大部分需要通过指令集来进行实现。这篇文章也主要讲解前者如何加速你的程序。

为什么这样可以加速

这里简单讲一下为什么上述的两种方法可以实现程序加速。对于进程或线程而言,由于CPU拥有多个核心,通过进程或者线程可以将任务分散到CPU的不同核心上,相对于不使用线程或进程的程序而言,相当于将原来只有一个人干的工作现在分给了好几个人去完成;对于指令集可以实现加速是因为,在CPU进行运算的时候,是以二进制进行运算,由于有些数据类型(比如一个长整型有long64位比特位)在实际参与运算的过程中有些比特位并没有数据,但是这一部分也会参与运算,这就造成了资源的浪费,指令集可以做到将几个小的数据(短比特位)汇总成一个长比特位的数据类型,从而实现计算一个长整型的数据就可以同时计算好几个短整型数据,指令集除了支持整型数据之外,也可以支持浮点类型的数据。

线程和进程

线程和进程是两个十分相似的概念,在不同的操作系统中也有区别,比如在Windows系统中有真正的线程和进程,而在Linux系统中只有进程而没有真正的线程(是由进程模拟出来的)。对于操作系统而言,进程是最小的调度单位,调度也即将一个计算任务放到CPU的那个核上面去执行,由于是在不同的核上面运行,也就导致了不同的的进程之间运行是互不影响的,不同进程之间的资源也无法做到共享。线程由于不同的编程语言实现的方式不同,也有差别,对于C语言而言,线程也可以通过调度将多个线程分配到多个CPU的核上,而对于Python语言,由于Python在实现过程中(Cpython)人为的引入了GIL(全局解释器锁),使得Python的多线程程序在运行的时候,同一时刻只能运行一个线程,且同一时刻只能占用CPU的一个核,造成了一核有难、八核围观的窘境。这样一来对于Python而言多线程程序貌似是没有加速程序的作用,但是请注意,这里只是CPU会“卡顿”,对于一些程序不只是只有计算任务,还有读取和写入(IO操作)的任务,如果程序的限速步骤是读取和写入数据,那么Python使用多线程依旧可以做到加速程序的效果。所以对于Python而言,如果是一个IO密集型的程序,完全可以使用多线程来进行加速,如果是计算密集型的程序,使用多线程可能不会对你的程序性能有太大的提高,但是你可以使用Python的多进程来完成计算密集型的任务,Python多进程可以将任务分配到CPU不同的核上,不会有锁的限制。

Python多进程与多线程

在python中有几个与多进程和多线程相关的库

  • threading
  • multiprocessing
  • queue
  • subprocess
  • concurrent.futures

如果你想快速上手多进程和多线程,那么我会推荐你首先学习concurrent.futures,这是一个Python官方封装好的非常容易上手的进程/线程池,使用它可以很方便的将一个常规的任务改造成多线程/多进程版本。

核心是一个「ProcessPoolExecutor」对象(多线程版本的是「ThreadPoolExecutor」),首先进行实例化得到一个**executor,**这里有两种方法,一种直接进行实例化,一种是使用with进行上下文管理。

# 直接进行实例化
# 创建8个进程
executor = ProcessPoolExecutor(8)
executor.shutdown()  # 关闭进程(强行关闭进程)
# executor.shutdown(wait=True)  # 等待所有的进程都执行完毕,后再退出
# 使用with
with ProcessPoolExecutor(8) as executor:
    # 这里就不用主动调用shutdown方法了,with可以自动关闭
    pass

这样就创建好了进程池

往进程池中有两种方式投递任务

  • 「map  一次性投递多个任务」
  • 「submit  一次投递一个任务」

虽然有两种不同的方式,其实核心的方法是submitmap方法内部是将多个任务逐个的使用submit来提交任务。具体的参数也几乎一样,都需要传入一个要执行的函数和函数对应的参数。

# submit
work = executor.submit(work_fn, arg)
# 需要调用work的result方法来来获取结果
work_result = work.result()
# map
works_reult = executor.map(work_fn, args)
# map直接可以返回结果

下面将一段常规任务,改造成他的多进程版本

  • 常规版本
files_path = [
    '1.txt',
    '2.txt',
    '3.txt'
]
def make_zipfile(file_path, save_path):
    """ 给定一个文件路径,将其压缩成压缩文件
        然后保存到一个具体的目录
    """
    # 具体的压缩逻辑
    pass
# 使用循环逐个的进行压缩
for file_path in files_path:
    make_zipfile(file_path)
  • 多进程版本
import os
from concurrent.futures import ProcessPoolExecutor
files_path = [
    '1.txt',
    '2.txt',
    '3.txt'
]
def make_zipfile(arg):
    """ 给定一个文件路径,将其压缩成压缩文件 """
    # 这里的函数只有一个参数,是为了往进程池投递任务方便传参数
    [file_path, save_path] = arg
    # 具体的压缩逻辑
    pass
# 使用map
args = [(file_path, file_path + '.zip') for i in files_path]
process_count = os.cpu_count()
with ProcessPoolExecutor(process_count) as executor:
    result = executor.map(make_zipfile, args)
# 使用submit
args = [(file_path, file_path + '.zip') for i in files_path]
process_count = os.cpu_count()
with ProcessPoolExecutor(process_count) as executor:
    reuslt = []
    for arg in args:
        result.append(executor.submit(make_zipfile, arg))
    [i.result() for i in result]  # 主动去调用result方法去获取函数的返回值

相对应的多线程只需要将「ProcessPoolExecutor」更换成「ThreadPoolExecutor」即可。唯一需要注意的是,要根据任务的类型是以一些数学计算为主,还是以IO(读取文件,写入文件)为主,来去选择是使用多线程还是多进程。

当你后面的任务越来越复杂的时候,可能上面这种方法就不再适合你的任务需求,那么你就需要去学习「threading」和**multiprocessing **具体该如何使用。

最后给自己挖个坑,规划加速系列出四篇推文,本文是第一篇

「加速你的Python程序(线程/进程池)」

加速你的Python程序(线程/进程)

加速你的Python程序(内存)

加速你的Python程序(Python调用C)

相关文章
|
2月前
|
数据采集 存储 JSON
Python爬取知乎评论:多线程与异步爬虫的性能优化
Python爬取知乎评论:多线程与异步爬虫的性能优化
|
2月前
|
人工智能 安全 调度
Python并发编程之线程同步详解
并发编程在Python中至关重要,线程同步确保多线程程序正确运行。本文详解线程同步机制,包括互斥锁、信号量、事件、条件变量和队列,探讨全局解释器锁(GIL)的影响及解决线程同步问题的最佳实践,如避免全局变量、使用线程安全数据结构、精细化锁的使用等。通过示例代码帮助开发者理解并提升多线程程序的性能与可靠性。
|
4月前
|
Kubernetes Linux Go
使用 Uber automaxprocs 正确设置 Go 程序线程数
`automaxprocs` 包就是专门用来解决此问题的,并且用法非常简单,只需要使用匿名导入的方式 `import _ "go.uber.org/automaxprocs"` 一行代码即可搞定。
231 78
|
2月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
3月前
|
JSON 算法 Java
打造终端里的下载利器:Python实现可恢复式多线程下载器
在数字时代,大文件下载已成为日常需求。本文教你用Python打造专业级下载器,支持断点续传、多线程加速、速度限制等功能,显著提升终端下载体验。内容涵盖智能续传、多线程分块下载、限速控制及Rich库构建现代终端界面,助你从零构建高效下载工具。
191 1
|
2月前
|
数据采集 存储 Java
多线程Python爬虫:加速大规模学术文献采集
多线程Python爬虫:加速大规模学术文献采集
|
4月前
|
人工智能 并行计算 开发者
CUDA重大更新:原生Python可直接编写高性能GPU程序
NVIDIA在2025年GTC大会上宣布CUDA并行计算平台正式支持原生Python编程,消除了Python开发者进入GPU加速领域的技术壁垒。这一突破通过重新设计CUDA开发模型,引入CUDA Core、cuPyNumeric、NVMath Python等核心组件,实现了Python与GPU加速的深度集成。开发者可直接用Python语法进行高性能并行计算,显著降低门槛,扩展CUDA生态,推动人工智能、科学计算等领域创新。此更新标志着CUDA向更包容的语言生态系统转型,未来还将支持Rust、Julia等语言。
325 3
CUDA重大更新:原生Python可直接编写高性能GPU程序
|
5月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
229 67
|
3月前
|
数据采集 网络协议 前端开发
Python多线程爬虫模板:从原理到实战的完整指南
多线程爬虫通过并发请求大幅提升数据采集效率,适用于大规模网页抓取。本文详解其原理与实现,涵盖任务队列、线程池、会话保持、异常处理、反爬对抗等核心技术,并提供可扩展的Python模板代码,助力高效稳定的数据采集实践。
141 0

推荐镜像

更多