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

相关文章
|
13天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
23天前
|
算法 数据处理 Python
Python并发编程:解密异步IO与多线程
本文将深入探讨Python中的并发编程技术,重点介绍异步IO和多线程两种常见的并发模型。通过对比它们的特点、适用场景和实现方式,帮助读者更好地理解并发编程的核心概念,并掌握在不同场景下选择合适的并发模型的方法。
|
5天前
|
数据采集 JavaScript 前端开发
使用Python打造爬虫程序之破茧而出:Python爬虫遭遇反爬虫机制及应对策略
【4月更文挑战第19天】本文探讨了Python爬虫应对反爬虫机制的策略。常见的反爬虫机制包括User-Agent检测、IP限制、动态加载内容、验证码验证和Cookie跟踪。应对策略包括设置合理User-Agent、使用代理IP、处理动态加载内容、验证码识别及维护Cookie。此外,还提到高级策略如降低请求频率、模拟人类行为、分布式爬虫和学习网站规则。开发者需不断学习新策略,同时遵守规则和法律法规,确保爬虫的稳定性和合法性。
|
6天前
|
SQL 安全 Go
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
在Python Web开发中,确保应用安全至关重要,主要防范SQL注入、XSS和CSRF攻击。措施包括:使用参数化查询或ORM防止SQL注入;过滤与转义用户输入抵御XSS;添加CSRF令牌抵挡CSRF;启用HTTPS保障数据传输安全;实现强身份验证和授权系统;智能处理错误信息;定期更新及审计以修复漏洞;严格输入验证;并培训开发者提升安全意识。持续关注和改进是保证安全的关键。
14 0
|
7天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
27 0
|
22天前
|
安全 Linux API
Android进程与线程
Android进程与线程
18 0
|
22天前
|
数据采集 Java API
python并发编程: Python使用线程池在Web服务中实现加速
python并发编程: Python使用线程池在Web服务中实现加速
18 3
python并发编程: Python使用线程池在Web服务中实现加速
|
22天前
|
分布式计算 算法 搜索推荐
优化 Python 程序的五大技巧
本文介绍了优化 Python 程序的五大技巧,涵盖了代码结构优化、算法选择、内置函数利用、库的使用以及并行处理等方面。通过对这些技巧的实践,可以提升 Python 程序的性能和效率,从而更好地满足各类应用的需求。
|
23天前
|
Python
python使用tkinter库,封装操作excel为GUI程序
python使用tkinter库,封装操作excel为GUI程序
|
25天前
|
Java 测试技术 Python
Python开启线程和线程池的方法
Python开启线程和线程池的方法
17 0
Python开启线程和线程池的方法