加速你的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)

相关文章
|
5天前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
34 20
|
18天前
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
96 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
1月前
|
存储 NoSQL 数据库连接
在Python程序中实现LevelDB的海量key的分批次扫描
通过本文的步骤,您可以在Python程序中实现对LevelDB海量key的分批次扫描。这样不仅能够有效地管理大规模数据,还可以避免一次性加载过多数据到内存中,提高程序的性能和稳定性。希望这篇指南能为您的开发工作提供实用的帮助。
74 28
|
2月前
|
Shell 开发工具 Python
如何在vim里直接运行python程序
如何在vim里直接运行python程序
|
2月前
|
安全 API C语言
Python程序的安全逆向(关于我的OPENAI的APIkey是如何被盗的)
本文介绍了如何使用C语言编写一个简单的文件加解密程序,并讨论了如何为编译后的软件添加图标。此外,文章还探讨了Python的.pyc、.pyd等文件的原理,以及如何生成和使用.pyd文件来增强代码的安全性。通过视频和教程,作者详细讲解了生成.pyd文件的过程,并分享了逆向分析.pyd文件的方法。最后,文章提到可以通过定制Python解释器来进一步保护源代码。
87 6
|
2月前
|
IDE 程序员 开发工具
Python编程入门:打造你的第一个程序
迈出编程的第一步,就像在未知的海洋中航行。本文是你启航的指南针,带你了解Python这门语言的魅力所在,并手把手教你构建第一个属于自己的程序。从安装环境到编写代码,我们将一步步走过这段旅程。准备好了吗?让我们开始吧!
|
3月前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
3月前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
68 6
|
6月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。
|
7月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
220 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)

热门文章

最新文章

推荐镜像

更多