Python 多线程和多进程并发执行

Python 多线程和多进程并发执行

  • 引言
  • 多线程
    • 主要特点和概念
    • 多线程的使用
      • threading 模块
      • concurrent.futures 线程池
  • 多进程
    • 主要特点和概念:
    • 多进程的使用:
      • multiprocessing 模块
      • concurrent.futures 进程池
    • 选择合适的进程数
  • 总结
    • I/O 密集型任务
    • CPU 密集型任务

引言

在测试领域,为了提高测试效率,通常采用并行方式执行脚本,可通过多线程和多进程机制来实现,今天来了解一下多线程和多进程的概念,区别以及使用。

多线程

多线程是一种并发执行的机制,它允许程序同时执行多个线程,每个线程都是独立执行的最小单位。线程是在进程内部运行的,多线程共享同一个进程的地址空间,因此它们可以更方便地共享数据和通信。

主要特点和概念

  • 轻量级: 线程相对于进程来说更轻量,因为它们共享同一个进程的资源,包括内存空间。线程的创建和切换开销较小。

  • 共享内存: 线程在同一进程中共享相同的内存空间,这使得线程之间可以直接访问共享的数据,也容易进行通信。

  • 并发执行: 多线程允许程序的不同部分并发执行,从而提高程序的整体性能。每个线程都有自己的执行路径,它们可以同时执行不同的任务。

  • 线程安全: 在多线程编程中,需要考虑到多个线程同时访问和修改共享数据可能引发的问题。为了确保线程安全,可能需要使用锁、信号量等同步机制。

  • 全局解释锁(GIL): 在 CPython 解释器中,由于全局解释锁的存在,一次只允许一个线程执行 Python 字节码。这使得在多线程中并发执行 CPU 密集型任务时性能提升有限,但对于 I/O 密集型任务仍然有效。

多线程的使用

threading 模块

在 Python中,可以使用 threading 模块来创建和管理线程。使用 threading 模块时,需要手动管理线程的创建和执行。以下是一个简单的多线程示例

import threading
import timedef square_number(number):result = number * numbertime.sleep(1)print(f"Result for {number}: {result}")numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]# 创建一个线程池,用于存储线程对象
threads = []# 启动线程
for num in numbers:thread = threading.Thread(target=square_number, args=(num,))threads.append(thread)thread.start()# 等待所有线程执行完成
for thread in threads:thread.join()print("All tasks have finished.")

在这个例子中,我们手动创建了一个线程的线程池。然后,我们定义了一个 thread_worker 函数,该函数接受一个数字并调用 square_number 函数。我们为每个数字创建一个线程,并将这些线程加入到一个列表中。最后,通过遍历线程列表,等待所有线程执行完成。

执行结果:

Result for 2: 4
Result for 1: 1
Result for 4: 16
Result for 3: 9
Result for 5: 25
Result for 7: 49
Result for 10: 100
Result for 9: 81
Result for 6: 36
Result for 8: 64
All tasks have finished.

concurrent.futures 线程池

concurrent.futures.ThreadPoolExecutor 是 Python 标准库中 concurrent.futures 模块提供的一个线程池实现,线程池自动管理了线程的生命周期,使得代码更为简洁,用于简化并发编程。它提供了高级别的接口,使得在多线程环境中提交和管理任务变得更加容易。

import concurrent.futures
import timedef square_number(number):result = number * numbertime.sleep(1)print(f"Result for {number}: {result}")# 创建一个包含3个线程的线程池
max_threads = 4
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:# 提交任务给线程池futures = [executor.submit(square_number, num) for num in numbers]# 等待所有任务执行完成
concurrent.futures.wait(futures)print("All tasks have finished.")

在这个例子中,我们定义了一个简单的任务 square_number,该任务接受一个数字并计算其平方,然后打印结果。我们使用 ThreadPoolExecutor 创建了一个包含 4 个线程的线程池,并使用 submit 方法提交了一系列任务,每个任务处理一个数字。submit 方法会返回一个 concurrent.futures.Future 对象,可以用来监控任务的执行状态。

由于我们限制了线程池中的最大线程数为 4,因此任务会并发执行,但最多只有4个任务同时运行。等待所有任务完成后,程序输出 “All tasks have finished.”。

最后,使用 concurrent.futures.wait 等待一系列任务完成。这个例子中,wait 会阻塞主线程,直到所有的任务都完成。

ThreadPoolExecutor 简化了线程的管理和任务的提交过程,使得在多线程环境中更容易实现并发编程。需要注意的是,与原生的 threading 模块相比,ThreadPoolExecutor 提供了更高级别的抽象,更易于使用。

执行结果:

Result for 3: 9
Result for 1: 1
Result for 4: 16
Result for 2: 4
Result for 7: 49
Result for 6: 36
Result for 5: 25
Result for 8: 64
Result for 10: 100
Result for 9: 81
All tasks have finished.

多进程

多进程是一种并发执行的机制,允许程序同时执行多个独立的进程。每个进程都拥有独立的内存空间,因此它们不会互相干扰。多进程的优点在于能够充分利用多核处理器,实现真正的并行执行,特别适合处理 CPU 密集型的任务。

主要特点和概念:

独立内存空间: 每个进程都有独立的内存空间,不同进程之间的数据不能直接共享。进程之间的通信通常需要使用一些特殊的机制,例如管道、消息队列等。

并行执行: 多进程能够在多个 CPU 核心上并行执行,因此适用于 CPU 密集型任务。每个进程都有自己的 Python 解释器,避免了全局解释锁(GIL)对并行性能的限制。

稳定性: 进程之间相互隔离,一个进程的崩溃通常不会影响其他进程。这提高了系统的稳定性和可靠性。

创建和销毁开销较大: 与线程相比,创建和销毁进程的开销较大,因为每个进程都有独立的资源和状态。

多进程的使用:

multiprocessing 模块

在 Python中,可以使用 multiprocessing 模块来创建和管理多进程。以下是一个简单的多进程示例:

import multiprocessing
import timedef square_number(number):result = number * numbertime.sleep(1)print(f"Result for {number}: {result}")if __name__ == '__main__':# 创建一个包含3个进程的进程池max_processes = 3numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]with multiprocessing.Pool(processes=max_processes) as pool:# 使用map方法将任务分配给进程池pool.map(square_number, numbers)print("All tasks have finished.")

在这个例子中,我们创建了一个包含 3 个进程的进程池(max_processes = 3)。使用 pool.map 方法,我们将任务函数 square_number 应用于列表 numbers 中的每个数字,进程池会自动分配任务给空闲的进程。最后,等待所有任务完成后,程序输出 “All tasks have inished.”。

使用 Pool 类可以很方便地实现多进程编程,而不必手动管理进程的创建和销毁。每个进程在执行任务时独立运行,从而实现了并行处理的效果。

注意:multiprocessing 的某些环境(比如在交互式环境中如 Jupyter Notebook中)使用 if name == ‘main’:是必要的,这是因为在Unix 系统上,multiprocessing 在 fork 子进程时会复制整个进程的状态,包括已经创建的线程,而在 Windows上,由于没有 fork,必须通过重新导入模块来确保每个进程都能正确地运行主程序。if name == ‘main’: 语句确保代码只在主模块中运行,而不是在子进程中运行。这是为了避免多次执行程序,因为在子进程中也会执行导入的代码。这是一个在使用 multiprocessing 模块时常见的实践。

concurrent.futures 进程池

当使用 concurrent.futures.ProcessPoolExecutor 时,你可以使用 submit 方法来提交可调用的对象(函数)给进程池,并获得一个 concurrent.futures.Future 对象,该对象代表异步计算的结果。以下是一个简单的例子:

import concurrent.futures
import timedef square_number(number):result = number * numbertime.sleep(1)print(f"Result for {number}: {result}")return resultdef processes_work():# 创建一个包含3个进程的进程池max_processes = 3numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]with concurrent.futures.ProcessPoolExecutor(max_workers=max_processes) as executor:# 使用submit方法提交任务给进程池futures = [executor.submit(square_number, num) for num in numbers]# 等待所有任务执行完成# concurrent.futures.wait(futures)# 或获取结果for future in concurrent.futures.as_completed(futures):result = future.result()# 在这里处理结果,例如打印或进行其他操作print("print result: ", result)print("All tasks have finished.")if __name__ == '__main__':processes_work()

在这个例子中,我们使用 concurrent.futures.ProcessPoolExecutor 创建了一个包含 3 个进程的进程池(max_workers = 3)。然后,我们使用 executor.submit 提交了一系列任务,每个任务是 square_number 函数对不同的数字进行平方运算。我们通过 concurrent.futures.as_completed 来获取已完成的任务,并通过 future.result() 获取任务的返回结果。在这个例子中,处理结果的操作是简单地打印结果。

需要注意,在这个例子中同样使用了 if name == ‘main’: 条件判断,以确保在主模块中运行,避免在 Windows 等环境下可能出现的问题。

执行结果:

Result for 2: 4
Result for 1: 1
print result:  4
print result:  1
Result for 3: 9
print result:  9
Result for 4: 16
Result for 5: 25
print result:  16
print result:  25
Result for 6: 36
print result:  36
Result for 8: 64
Result for 7: 49 
print result:  64
print result:  49
Result for 9: 81 
print result:  81
Result for 10: 100
print result:  100
All tasks have finished.

选择合适的进程数

选择合适的进程数取决于任务的性质、系统资源和硬件配置。通常,你可以通过试验和性能测试来确定最佳的进程数。以下是一些考虑因素:

  • CPU 核心数: 一般来说,进程数不应该超过系统的物理 CPU 核心数,否则可能导致竞争和性能下降。在多核系统上,你可以选择使用与核心数相当的进程数以充分利用硬件。

可以使用 Python 中的 os 模块来获取系统的 CPU 核心数

import osdef get_cpu_core_count():return os.cpu_count()if __name__ == '__main__':core_count = get_cpu_core_count()if core_count is not None:print(f"The system has {core_count} CPU core(s).")else:print("Unable to determine the number of CPU cores.")
  • 任务类型: 如果任务是 CPU 密集型的(需要大量计算),则增加进程数可能会提高性能。但对于 I/O 密集型任务(等待外部资源,如文件 I/O 或网络请求),增加进程数可能不会显著提高性能。

  • 系统资源: 考虑系统的可用内存和其他资源。创建太多的进程可能导致内存不足,从而影响整体性能。

  • 并发任务数: 任务的并发数也是一个重要的考虑因素。如果有大量的并发任务,适当增加进程数可能有助于更好地并行执行。

  • 性能测试: 进行性能测试是确定最佳进程数的有效方法。通过尝试不同的进程数,测量执行时间和资源利用率,以找到最佳的配置。

根据这些因素,你可以根据具体情况来选择一个适当的进程数。注意,增加进程数并不总是能够线性提高性能,因此在选择进程数时需要平衡系统资源和任务的特性。

总结

总体而言,多线程适用于轻量级任务和 I/O 密集型任务,而多进程能够在多个 CPU 核心上真正并行执行,适用于 CPU 密集型任务和需要更高稳定性的场景。选择使用多线程还是多进程取决于任务的性质、硬件配置和系统要求。

I/O 密集型任务

I/O 密集型任务指的是程序在执行过程中主要涉及输入/输出操作(I/O 操作)的任务。这些任务通常涉及从外部设备(如磁盘、网络、数据库)读取或写入数据。在这样的任务中,大部分的时间都花费在等待 I/O 操作的完成上,而不是在计算或处理数据上。

I/O 密集型任务的特点包括:

  • 高度依赖外部资源: 这类任务需要频繁地与外部设备进行交互,例如读取文件、从网络下载数据、与数据库通信等。

  • 等待时间较长: 由于涉及到外部设备,I/O 操作通常需要较长的时间完成。在这段时间内,程序可以执行其他任务而不是等待 I/O 操作的完成。

  • CPU 利用率较低: 在执行 I/O 操作期间,CPU 大部分时间都是空闲的,因为它不需要进行大量的计算工作。这导致了 CPU 利用率较低。

  • 并发性较高: 由于大部分时间都在等待外部操作完成,因此在这期间可以同时执行其他任务,提高了程序的并发性。

  • 性能瓶颈在 I/O 操作上: 对于 I/O 密集型任务,性能瓶颈主要出现在等待外部设备完成操作的时间上,而不是在 CPU 处理数据的速度上。

一些典型的 I/O 密集型任务包括网络通信、文件读写、数据库查询等。在这些场景中,使用多线程通常是一种有效的并发处理方式,因为一个线程在等待 I/O 操作完成的同时,其他线程仍然可以执行任务,从而提高整体系统的吞吐量。

CPU 密集型任务

CPU 密集型任务指的是程序在执行过程中主要涉及大量计算或处理大量数据的任务,而不涉及大量的输入/输出操作。在这类任务中,大部分的时间都花费在计算或处理数据上,而不是等待外部设备的操作完成。

CPU 密集型任务的特点包括:

  • 大量计算: 这类任务需要进行大量的计算工作,可能涉及复杂的数学运算、算法执行或大规模的数据处理。

  • 相对较短的等待时间: 与 I/O 密集型任务不同,CPU 密集型任务的等待时间相对较短,因为它们主要依赖 CPU 进行计算。

  • CPU 利用率高: 在执行 CPU 密集型任务时,CPU 大部分时间都处于繁忙状态,因为它需要进行大量的计算工作。

  • 并发性较低: 由于 CPU 密集型任务主要依赖 CPU 进行计算,而不是等待外部设备,因此在执行期间很难同时执行其他任务。

  • 性能瓶颈在 CPU 处理速度上: 对于 CPU 密集型任务,性能瓶颈主要出现在 CPU 处理速度上,而不是在等待外部设备完成操作的时间上。

一些典型的 CPU 密集型任务包括科学计算、图像处理、密码学操作等。在这些场景中,通常采用多进程或其他并行计算的方式,以充分利用多核 CPU 的性能。在 Python 中,由于全局解释锁 (GIL) 的存在,使用多线程可能无法充分发挥多核 CPU 的性能,因此对于 CPU 密集型任务,通常使用多进程是更为有效的选择。

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

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

相关文章

js过滤的方法示例

以下是几个常用的 JavaScript 过滤方法示例: filter() filter() 方法创建一个新的数组,其中包含通过测试函数的所有元素。该方法不会改变原始数组。 const numbers [1, 2, 3, 4, 5]; const filteredNumbers numbers.filter(num > num > 3); c…

销售技巧培训之如何提高建材销售技巧

建材销售市场竞争也日趋激烈。在这个充满挑战与机遇的市场中,掌握一定的销售技巧对于一个建材销售人员来说至关重要。本文将结合实际案例,探讨一些实用的建材销售技巧,帮助你更好地拓展业务。 一、了解客户需求 在销售过程中,首先…

sql2005日志文件过大如何清理

由于安装的时候没有计划好空间,默认装在系统盘,而且又没有做自动备份、截断事务日志等,很快LDF文件就达到十几G,或者几十G ,此时就不得不处理了。 备份和计划就不说了,现在就说下怎么把它先删除吧&#xf…

【深度学习】一维数组的 K-Means 聚类算法理解

刚看了这个算法,理解如下,放在这里,备忘,如有错误的地方,请指出,谢谢 需要做聚类的数组我们称之为【源数组】 需要一个分组个数K变量来标记需要分多少个组,这个数组我们称之为【聚类中心数组】…

mysql中count(*)、count(1)、count(主键)、count(字段)的区别

文章目录 count函数的语义count(主键)count(1)count(*)count(字段)替代方案explain或者show table status中间表或者其他数据库计数 以下分析都是基于 select count(?) from table 这个语句来分析,不带过滤条件。 count函数的语义 count() 是一个聚合函数&#x…

BFC(Block Formatting Contexts)块级格式化上下文

块格式化上下文(block formatting context) 是页面上的一个独立的渲染区域,容器里面的子元素不会在布局上影响到外面的元素。它是决定块盒子的布局及浮动元素相互影响的一个因素。 下列情况将创建一个块格式化上下文: 使用float…

阿里云轻量应用服务器与云服务器ECS对比

与云服务器ECS相比,轻量应用服务器使用门槛较低,配置简便,能让您快速上手并部署简单的应用或网站,更适合个人开发者、学生等用户。本文介绍轻量应用服务器与云服务器ECS的特点和区别。 产品对比 轻量应用服务器与云服务器ECS的产…

IO多路转接之select

IO多路转接之select 1. IO多路转接(复用)2. select2.1 函数原型2.2 细节描述 3. 并发处理3.1 处理流程3.2 通信代码 原文链接 1. IO多路转接(复用) IO多路转接也称为IO多路复用,它是一种网络通信的手段(机…

算法训练营Day10(栈和队列)

理论知识 java语言的栈和队列。这篇文章总结的不错 http://t.csdnimg.cn/cOC8q 232.用栈实现队列 232. 用栈实现队列 - 力扣&#xff08;LeetCode&#xff09; public class MyQueue {// 3 4// in// out 4 3Stack<Integer> in;Stack<Integer> out;public My…

C# 计算两个日期的相差天数

string str1 "2017-2-13 23:59:59"; string str2 "2017-2-14 0:00:01"; DateTime d1 Convert.ToDateTime(str1); DateTime d2 Convert.ToDateTime(str2); Console.WriteLine(d1.Date); Console.WriteLine(DateTime.Now);//当前时间 Console.WriteLine(…

【目标检测算法】IOU、GIOU、DIOU、CIOU

目录 参考链接 前言 IOU(Intersection over Union) 优点 缺点 代码 存在的问题 GIOU(Generalized Intersection over Union) 来源 GIOU公式 实现代码 存在的问题 DIoU(Distance-IoU) 来源 DIOU公式 优点 实现代码 总结 参考链接 IoU系列&#xff08;IoU, GIoU…

kubernetes的服务发现(二)

如前面的文章我们说了&#xff0c;kubernetes的服务发现是服务端发现模式。它有一个服务注册中心&#xff0c;使用DNS作为服务的注册表。每个集群都会运行一个DNS服务&#xff0c;默认是CoreDNS服务。每个服务都会在这个DNS中注册。注册的大致过程&#xff1a; 1、向kube-apise…

WPF使用WebBrowser报脚本错误问题处理

前言 WPF使用WebBrowser报脚本错误问题处理,我们都知道WPF自带的WebBrowser都用的IE内核,但是在特殊的条件下我们还需要用到它,比如展示纯html简单的页面。再展示主流页面的时候比如用到Jquery高级库或者VUE等当前主流站点时经常就会报JS脚本错误,在Winform里面我们一句代…

【精选】设计模式——工厂设计模式

工厂设计模式是一种创建型设计模式&#xff0c;其主要目的是通过将对象的创建过程封装在一个工厂类中来实现对象的创建。这样可以降低客户端与具体产品类之间的耦合度&#xff0c;也便于代码的扩展和维护。 工厂设计模式&#xff1a; 以下是Java中两个常见的工厂设计模式示例…

C++ 关于结构体struct的一些总结

文章目录 一、 结构体(struct)是什么&#xff1f;&#xff08;1&#xff09;概念&#xff08;2&#xff09;struct 与 calss 的区别 二、定义、声明与初始化&#xff08;1&#xff09;三种定义结构体的方法&#xff1a;&#xff08;2&#xff09;结构体变量初始化 三、结构体嵌…

C++实现进程端口网络数据接收系统设计示例程序

一、问题描述 最近做了一道简单的系统设计题&#xff0c;大概描述如下&#xff1a; 1.一个进程可以绑定多个端口&#xff0c;用于监听接收网络中的数据&#xff0c;但是一个端口只能被一个进程占用 2.1 < pid < 65535, 1 < port < 100000, 1 < topNum < 5, …

ros2/ros安装ros-dep||rosdep init错误

第一个错误的做法&#xff1a; sudo apt-get install python3-pip sudo pip3 install 6-rosdep sudo 6-rosdep 如果使用上述代码将会摧毁整个系统&#xff0c;不重装系统反正我是搞不定啊&#xff0c;因为我不知道那个写软件的人到底做了什么。因为这个我安装的版本是humble&…

AlexNet 阅读笔记

“ImageNet Classification with Deep Convolutional Neural Networks” (Krizhevsky 等, 2012, p. 1) 使用深度卷积神经网络进行 ImageNet 分类 3公式&#xff0c;26个引用&#xff0c;4张图片&#xff0c;2个简单表格 Abstract 我们训练了一个大型深度卷积神经网络&#…

Leetcode刷题详解——环绕字符串中唯一的子字符串

1. 题目链接&#xff1a;467. 环绕字符串中唯一的子字符串 2. 题目描述&#xff1a; 定义字符串 base 为一个 "abcdefghijklmnopqrstuvwxyz" 无限环绕的字符串&#xff0c;所以 base 看起来是这样的&#xff1a; "...zabcdefghijklmnopqrstuvwxyzabcdefghijklm…

卷积之后通道数为什么变了

通道数增多与卷积之后得到的图像特征数量有关 卷积层的作用本来就是把输入中的特征分离出来变成新的 feature map&#xff0c;每一个输出通道就是一个卷积操作提取出来的一种特征。在此过程中ReLU激活起到过滤的作用&#xff0c;把负相关的特征点去掉&#xff0c;把正相关的留…