【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.25 多线程并行:GIL绕过与真正并发

news2025/2/5 18:19:59

在这里插入图片描述

2.25 多线程并行:GIL绕过与真正并发

目录

2.25 多线程并行:GIL绕过与真正并发
2.25.1 NumPy的GIL释放机制
2.25.2 线程池配置与使用
2.25.3 原子操作与竞态条件
2.25.4 图像批处理案例
2.25.5 竞态条件调试

2.25.1 NumPy的GIL释放机制

2.25.1.1 GIL简介

Python 的全局解释器锁(Global Interpreter Lock,简称 GIL)是一个互斥锁,用于保护对 Python 对象的访问。GIL 确保在多线程环境中,同一时刻只有一个线程在执行字节码。这在很多情况下是一个性能瓶颈,特别是在多核处理器上。

2.25.1.2 为什么 NumPy 可以绕过 GIL

NumPy 是一个用 C 语言编写的高效数值计算库。NumPy 的底层函数是用 C 语言实现的,可以暂时释放 GIL 以进行高效的计算。这使得 NumPy 在多线程环境中能够实现真正的并行计算。

2.25.1.3 释放 GIL 的原理

NumPy 通过在 C 扩展中暂时释放 GIL 来实现多线程并行。具体步骤如下:

  1. 释放 GIL:在 C 扩展函数中,使用 Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS 宏来释放和重新获取 GIL。
  2. 使用多线程库:在释放 GIL 的期间,可以使用多线程库(如 OpenMP)来并行执行计算任务。
  3. 重新获取 GIL:完成计算任务后,重新获取 GIL,以确保 Python 解释器的安全性。

2.25.1.4 代码示例

import numpy as np
import threading

def compute_mean(array):
    # 释放 GIL
    np.core._rational.arithmetic._begin_threads()
    
    # 进行计算
    result = np.mean(array)  # 计算数组的均值
    
    # 重新获取 GIL
    np.core._rational.arithmetic._end_threads()
    
    return result

# 创建一个大数组
data = np.random.rand(10000000)

# 创建多个线程
threads = []
for i in range(4):
    thread = threading.Thread(target=compute_mean, args=(data,))
    threads.append(thread)
    thread.start()

# 等待所有线程完成
for thread in threads:
    thread.join()

print("计算完成")

2.25.1.5 优缺点

  • 优点

    • 提高性能:在多核处理器上,释放 GIL 可以显著提高计算性能。
    • 简化开发:NumPy 的高层接口隐藏了多线程的复杂性,使得开发更加简单。
  • 缺点

    • 增加复杂性:在底层释放 GIL 需要对 C 语言有一定的了解。
    • 安全性问题:不当的 GIL 操作可能导致数据不一致和竞态条件。

2.25.2 线程池配置与使用

2.25.2.1 线程池简介

线程池(Thread Pool)是一种多线程处理形式,处理过程中将任务添加到队列中,然后在创建线程后自动启动这些任务。线程池可以有效管理和复用线程资源,减少线程创建和销毁的开销。

2.25.2.2 使用 concurrent.futures 配置线程池

concurrent.futures 是 Python 标准库中的一个高级线程池接口,使用非常方便。

2.25.2.3 代码示例

import numpy as np
from concurrent.futures import ThreadPoolExecutor

def process_data(data):
    result = np.mean(data)  # 计算数组的均值
    return result

# 创建一个大数组
data = np.random.rand(10000000)

# 将数据分割成多个子数组
sub_arrays = np.array_split(data, 4)

# 配置线程池
with ThreadPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(process_data, sub_arrays))

# 计算总均值
total_mean = np.mean(results)  # 计算子数组均值的总均值

print(f"总均值: {total_mean}")

2.25.2.4 优缺点

  • 优点

    • 高效管理:线程池可以高效管理和复用线程资源。
    • 简化代码:使用 concurrent.futures 可以简化多线程编程的代码。
  • 缺点

    • 线程数量限制:线程池的线程数量需要合理配置,过多或过少都会影响性能。
    • 数据同步问题:线程池中的任务需要谨慎处理数据同步问题。

2.25.3 原子操作与竞态条件

2.25.3.1 原子操作简介

原子操作(Atomic Operation)是指不会被线程调度机制打断的操作;也就是说,这个操作在执行过程中是不可分割的。在多线程编程中,原子操作可以避免竞态条件(Race Condition)。

2.25.3.2 竞态条件示例

import threading

counter = 0

def increment():
    global counter
    for _ in range(1000000):
        counter += 1  # 竞态条件

# 创建多个线程
threads = []
for i in range(4):
    thread = threading.Thread(target=increment)
    threads.append(thread)
    thread.start()

# 等待所有线程完成
for thread in threads:
    thread.join()

print(f"最终的 counter 值: {counter}")  # 预期值为 4000000,但实际值可能小于预期

2.25.3.3 使用锁解决竞态条件

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(1000000):
        lock.acquire()  # 获取锁
        counter += 1  # 原子操作
        lock.release()  # 释放锁

# 创建多个线程
threads = []
for i in range(4):
    thread = threading.Thread(target=increment)
    threads.append(thread)
    thread.start()

# 等待所有线程完成
for thread in threads:
    thread.join()

print(f"最终的 counter 值: {counter}")  # 预期值为 4000000,实际值也为 4000000

2.25.3.4 优缺点

  • 优点

    • 线程安全:使用锁可以确保操作的线程安全性。
    • 避免数据不一致:防止竞态条件导致的数据不一致问题。
  • 缺点

    • 性能开销:锁的获取和释放会增加性能开销。
    • 死锁风险:不当的锁管理可能导致死锁。

2.25.4 图像批处理案例

2.25.4.1 图像处理简介

图像处理是计算机视觉中的一个重要领域,NumPy 可以高效地处理图像数据。通过多线程并行计算,可以显著提高图像处理的性能。

2.25.4.2 图像批处理步骤

  1. 读取图像:使用 PILOpenCV 读取图像。
  2. 图像预处理:将图像数据转换为 NumPy 数组。
  3. 多线程处理:使用线程池并行处理多个图像。
  4. 结果汇总:将处理结果汇总并保存。

2.25.4.3 代码示例

import numpy as np
from concurrent.futures import ThreadPoolExecutor
from PIL import Image
import os

def load_image(file_path):
    image = Image.open(file_path)  # 读取图像
    return np.array(image)  # 将图像转换为 NumPy 数组

def process_image(image):
    # 进行图像处理,例如滤波
    processed_image = np.array(image, dtype=np.float32)
    processed_image = np.sqrt(processed_image)  # 应用滤波操作
    return processed_image

def save_image(processed_image, output_path):
    processed_image = np.uint8(processed_image)  # 转换为 8 位无符号整数
    Image.fromarray(processed_image).save(output_path)  # 保存处理后的图像

def batch_process_images(input_dir, output_dir, max_workers=4):
    # 获取输入目录中的所有图像文件
    image_files = [os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.jpg')]
    
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 并行加载图像
        images = list(executor.map(load_image, image_files))
        
        # 并行处理图像
        processed_images = list(executor.map(process_image, images))
        
        # 并行保存处理后的图像
        for i, processed_image in enumerate(processed_images):
            output_path = os.path.join(output_dir, f'processed_{i}.jpg')
            executor.submit(save_image, processed_image, output_path)

# 输入和输出目录
input_dir = 'path/to/input/images'
output_dir = 'path/to/output/images'

# 批处理图像
batch_process_images(input_dir, output_dir)

2.25.4.4 注意事项

  • 文件路径:确保输入和输出目录路径正确。
  • 图像格式:处理不同格式的图像时,需要进行相应的格式转换。
  • 内存管理:处理大图像时,注意内存管理,防止内存溢出。

2.25.5 竞态条件调试

2.25.5.1 竞态条件调试工具

  • threading 模块:Python 标准库中的 threading 模块提供了基本的线程调试工具。
  • logging 模块:使用 logging 模块记录线程的执行日志,帮助调试。
  • Valgrind:C/C++ 中的 Valgrind 工具可以帮助检测线程中的内存和数据访问问题。

2.25.5.2 使用 logging 调试线程

import threading
import logging

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(1000000):
        lock.acquire()  # 获取锁
        counter += 1  # 原子操作
        lock.release()  # 释放锁
        logging.debug(f"当前 counter 值: {counter}")

# 创建多个线程
threads = []
for i in range(4):
    thread = threading.Thread(target=increment)
    threads.append(thread)
    thread.start()

# 等待所有线程完成
for thread in threads:
    thread.join()

print(f"最终的 counter 值: {counter}")

2.25.5.3 使用 Valgrind 调试 C 代码

# 安装 Valgrind
sudo apt-get install valgrind

# 编译 C 代码
gcc -g -o my_program my_program.c

# 运行 Valgrind
valgrind --tool=helgrind ./my_program

2.25.5.4 优缺点

  • 优点

    • 日志记录:使用 logging 可以方便地记录线程的执行情况,帮助定位问题。
    • 问题检测Valgrind 可以检测 C 代码中的线程问题,提高代码的健壮性。
  • 缺点

    • 性能开销:日志记录和 Valgrind 会增加程序的运行开销。
    • 复杂性:调试多线程程序需要一定的技巧和经验。

总结

本文详细介绍了如何在 NumPy 中实现多线程并行计算,通过绕过 GIL 实现真正的并发。我们讨论了 NumPy 的 GIL 释放机制、线程池的配置与使用、原子操作与竞态条件的处理,以及图像批处理的案例。最后,我们还介绍了如何调试竞态条件,确保多线程程序的正确性和性能。

通过本文的学习,你将能够更好地理解和应用多线程技术,提升 Python 程序的性能。希望这些内容对你有所帮助!

参考文献

参考资料链接
NumPy 官方文档https://numpy.org/doc/stable/
Python 官方文档:threading 模块https://docs.python.org/3/library/threading.html
Python 官方文档:concurrent.futures 模块https://docs.python.org/3/library/concurrent.futures.html
Python GIL 讲解https://realpython.com/python-gil/
OpenMP 官方文档https://www.openmp.org/specifications/
Valgrind 官方文档https://valgrind.org/docs/manual/manual.html
Python 多线程优化https://www.geeksforgeeks.org/multiprocessing-vs-threading-python/
Locks in Python: The Good, the Bad, and the Uglyhttps://medium.com/@bfortuner/python-multithreading-vs-multiprocessing-730797d5fbe6
Python 中的线程安全https://www.toptal.com/python/beyond-threads-a-comprehensive-guide-to-concurrent-python
Image Processing with NumPyhttps://scikit-image.org/docs/dev/auto_examples/
图像处理基本原理https://www.cs.columbia.edu/CAVE/publications/pdfs/Brow09_TIP.pdf
Python 线程池详解https://www.jianshu.com/p/392f5b6baf44
CPython 解释器源码https://github.com/python/cpython

这篇文章包含了详细的原理介绍、代码示例、源码注释以及案例等。希望这对您有帮助。如果有任何问题请随私信或评论告诉我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2293427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 大视界 -- Java 大数据在智能医疗影像诊断中的应用(72)

💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也期待你毫无保留地分享独特见解,愿我们于此携手成长,共赴新程!💖 一、…

【Leetcode刷题记录】1456. 定长子串中元音的最大数目---定长滑动窗口即解题思路总结

1456. 定长子串中元音的最大数目 给你字符串 s 和整数 k 。请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。 英文中的 元音字母 为(a, e, i, o, u)。 这道题的暴力求解的思路是通过遍历字符串 s 的每一个长度为 k 的子串&#xf…

upload-labs安装与配置

前言 作者进行upload-labs靶场练习时,在环境上出了很多问题,吃了很多苦头,甚至改了很多配置也没有成功。 upload-labs很多操作都是旧时代的产物了,配置普遍都比较老,比如PHP版本用5.2.17(还有中间件等&am…

从Transformer到世界模型:AGI核心架构演进

文章目录 引言:架构革命推动AGI进化一、Transformer:重新定义序列建模1.1 注意力机制的革命性突破1.2 从NLP到跨模态演进1.3 规模扩展的黄金定律二、通向世界模型的关键跃迁2.1 从语言模型到认知架构2.2 世界模型的核心特征2.3 混合架构的突破三、构建世界模型的技术路径3.1 …

每日一博 - 三高系统架构设计:高性能、高并发、高可用性解析

文章目录 引言一、高性能篇1.1 高性能的核心意义1.2 影响系统性能的因素1.3 高性能优化方法论1.3.1 读优化:缓存与数据库的结合1.3.2 写优化:异步化处理 1.4 高性能优化实践1.4.1 本地缓存 vs 分布式缓存1.4.2 数据库优化 二、高并发篇2.1 高并发的核心意…

【工欲善其事】利用 DeepSeek 实现复杂 Git 操作:从原项目剥离出子版本树并同步到新的代码库中

文章目录 利用 DeepSeek 实现复杂 Git 操作1 背景介绍2 需求描述3 思路分析4 实现过程4.1 第一次需求确认4.2 第二次需求确认4.3 第三次需求确认4.4 V3 模型:中间结果的处理4.5 方案验证,首战告捷 5 总结复盘 利用 DeepSeek 实现复杂 Git 操作 1 背景介绍…

【C++】线程池实现

目录 一、线程池简介线程池的核心组件实现步骤 二、C11实现线程池源码 三、线程池源码解析1. 成员变量2. 构造函数2.1 线程初始化2.2 工作线程逻辑 3. 任务提交(enqueue方法)3.1 方法签名3.2 任务封装3.3 任务入队 4. 析构函数4.1 停机控制 5. 关键技术点解析5.1 完美转发实现5…

数据结构实战之线性表(三)

目录 1.顺序表释放 2.顺序表增加空间 3.合并顺序表 4.线性表之链表实现 1.项目结构以及初始代码 2.初始化链表(不带头结点) 3.链表尾部插入数据并显示 4.链表头部插入数据 5.初始化链表(带头结点) 6.带头结点的链表头部插入数据并显示 7.带头结…

【python】python基于机器学习与数据分析的手机特性关联与分类预测(源码+数据集)【独一无二】

👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉专__注👈:专注主流机器人、人工智能等相关领域的开发、测试技术。 python基于机器学习与数据分析的手机特性关联与分类…

ZOJ 1007 Numerical Summation of a Series

原题目链接 生成该系列值的表格 对于x 的 2001 个值,x 0.000、0.001、0.002、…、2.000。表中的所有条目的绝对误差必须小于 0.5e-12(精度为 12 位)。此问题基于 Hamming (1962) 的一个问题,当时的大型机按今天的微型计算机标准来…

全面解析文件上传下载删除漏洞:风险与应对

在数字化转型的时代,文件上传、下载与删除功能已经成为各类应用程序的标准配置,从日常办公使用的协同平台,到云端存储服务,再到社交网络应用,这些功能在给用户带来便捷体验、显著提升工作效率的同时,也隐藏…

【C语言深入探索】结构体详解(二):使用场景

目录 一、复杂数据的表示 二、数据的封装 三、多态的模拟 四、回调函数的实现 五、多线程编程 六、通信协议的实现和文件操作 6.1. 使用结构体实现简单通信协议 6.2. 使用结构体进行文件操作 七、图形界面编程 结构体在C语言中具有广泛的应用场景,以下是一…

【大模型】AI 辅助编程操作实战使用详解

目录 一、前言 二、AI 编程介绍 2.1 AI 编程是什么 2.1.1 为什么需要AI辅助编程 2.2 AI 编程主要特点 2.3 AI编程底层核心技术 2.4 AI 编程核心应用场景 三、AI 代码辅助编程解决方案 3.1 AI 大模型平台 3.1.1 AI大模型平台代码生成优缺点 3.2 AI 编码插件 3.3 AI 编…

RK3566-移植5.10内核Ubuntu22.04

说明 记录了本人使用泰山派(RK3566)作为平台并且成功移植5.10.160版本kernel和ubuntu22.04,并且成功配置&连接网络的完整过程。 本文章所用ubuntu下载地址:ubuntu-cdimage-ubuntu-base-releases-22.04-release安装包下载_开源…

从零开始实现一个双向循环链表:C语言实战

文章目录 1链表的再次介绍2为什么选择双向循环链表?3代码实现:从初始化到销毁1. 定义链表节点2. 初始化链表3. 插入和删除节点4. 链表的其他操作5. 打印链表和判断链表是否为空6. 销毁链表 4测试代码5链表种类介绍6链表与顺序表的区别7存储金字塔L0: 寄存…

51单片机 06 定时器

51 单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 作用:1、用于计时;2、替代长时间的Delay,提高CPU 运行效率和处理速度。 定时器个数:3个(T0、T1、T2)&#xf…

【C++】P1957 口算练习题

博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 💯前言💯题目描述输入格式:输出格式: 💯我的做法代码实现: 💯老师的做法代码实现: 💯对比分析&am…

Workbench 中的热源仿真

探索使用自定义工具对移动热源进行建模及其在不同行业中的应用。 了解热源动力学 对移动热源进行建模为各种工业过程和应用提供了有价值的见解。激光加热和材料加工使用许多激光束来加热、焊接或切割材料。尽管在某些情况下,热源 (q) 不是通…

CCF-GESP 等级考试 2023年12月认证C++八级真题解析

2023年12月真题 一、单选题(每题2分,共30分) 正确答案:C 考察知识点:数学问题 解析:本题可抽象为分类计数问题,应使用加法原理,而不是乘法原理。答案为 ACB 的方案数 2 加上 ADB 的…

vscode搭建git

vscode搭建git 一、安装git二、vscode上搭建git(1) 先创建本地仓库再上传到远程仓库,远程仓库名是根据本地仓库名一致(2) 先创建远程仓库,再将本地仓库上传到指定远程仓库 一、安装git 网络教程很多,在此就不赘述了 参考:git安装…