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

目录
打赏
0
0
0
0
33
分享
相关文章
|
2月前
|
使用 Uber automaxprocs 正确设置 Go 程序线程数
`automaxprocs` 包就是专门用来解决此问题的,并且用法非常简单,只需要使用匿名导入的方式 `import _ "go.uber.org/automaxprocs"` 一行代码即可搞定。
200 78
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
183 67
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
468 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
5月前
|
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
127 20
Python爬虫实战:利用短效代理IP爬取京东母婴纸尿裤数据,多线程池并行处理方案详解
本文分享了一套结合青果网络短效代理IP和多线程池技术的电商数据爬取方案,针对京东母婴纸尿裤类目商品信息进行高效采集。通过动态代理IP规避访问限制,利用多线程提升抓取效率,同时确保数据采集的安全性和合法性。方案详细介绍了爬虫开发步骤、网页结构分析及代码实现,适用于大规模电商数据采集场景。
Python实用技巧:轻松驾驭多线程与多进程,加速任务执行
在Python编程中,多线程和多进程是提升程序效率的关键工具。多线程适用于I/O密集型任务,如文件读写、网络请求;多进程则适合CPU密集型任务,如科学计算、图像处理。本文详细介绍这两种并发编程方式的基本用法及应用场景,并通过实例代码展示如何使用threading、multiprocessing模块及线程池、进程池来优化程序性能。结合实际案例,帮助读者掌握并发编程技巧,提高程序执行速度和资源利用率。
161 0
如何区分进程、线程和协程?看这篇就够了!
本课程主要探讨操作系统中的进程、线程和协程的区别。进程是资源分配的基本单位,具有独立性和隔离性;线程是CPU调度的基本单位,轻量且共享资源,适合并发执行;协程更轻量,由程序自身调度,适合I/O密集型任务。通过学习这些概念,可以更好地理解和应用它们,以实现最优的性能和资源利用。
167 11

推荐镜像

更多
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问