IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> multiprocessing多进程和subprocess子进程总结 -> 正文阅读

[开发测试]multiprocessing多进程和subprocess子进程总结

0. 什么是进程、线程

简述:
进程是操作系统资源分配(内存,显卡,磁盘)的最小单元。
线程是执行cpu调度的最小单元(cpu看到的都是线程而不是进程)。
关系:
一个进程可以有一个或多个线程,线程之间共享进程的资源。
线程依存于进程,不能独立执行,没有进程就没有线程。
对比:
进程创建/切换的开销比线程创建/切换的开销大。
执行:
并发:CPU在同一时刻只执行单个任务,多个任务快速地交替执行。并发的多个任务互相抢占资源。
并行:CPU在同一时刻执行多个任务。并行的多个任务不会互相抢占资源。
通信:
多线程通信:由于资源共享,一个线程的数据可以直接提供给其他线程使用。
多进程通信:由于资源独立,一个进程通过管道、消息队列、信号量、共享内存、信号、套接字与其他进程同步。

1. Python中的多线程/进程

(我们所熟知的)多进程/线程和多核CPU的关系
  • 如果CPU是单核,多进程/线程就不是并行,而是并发。增加了进程/线程切换的开销,使得代价更大;

  • 如果CPU是多核,且总的进程/线程数小于核数,多进程/线程就能并行运行在不同的核中;

  • 如果CPU是多核,且总的进程/线程数大于核数,并行的进程/线程数就不会超过核的数量,其余进程/线程就会不断切换,并发执行。所以无限制地增加进程/线程数不仅不会让你的程序更快,反而会给你的程序增加额外的开销。

(Python中的)多线程/进程和多核CPU的关系
  • 在Python中,多核CPU并行应该用多进程实现。

  • Python存在GIL(global interpreter lock,全局解释器锁),GIL是为了解决多线程共享数据的同步、一致性问题,避免两个线程在同一时刻修改共享的数据。

  • 由于GIL锁的存在,在一个进程内,限制同一时刻仅一个处于执行状态的线程,直到该线程进入I/O状态,或达到执行时间上限,才会释放GIL锁,其他线程才能执行。

  • Python的多线程不能实现真正意义的并行,但是适用于I/O密集型任务(CPU执行时间少,数据读写多的任务会有大量时间等待I/O,这时候可以切换线程);

  • Python的多进程适用于CPU密集型任务(每个进程有各自独立的GIL,有效利用多核CPU资源);

2. Python程序中多进程的用处?

可以为程序提速

程序拆分成N个子任务,利用多进程实现N个子任务的并行执行,从而缩短总运行时长。

例如,进程A和进程B同时执行任务,进程A执行数据1的模型预测,进程B执行数据2的模型预测,最终将两个预测结果拼接。
from multiprocessing import Process
p1 = Process(target=run_predict, args=(data_a,))
p2 = Process(target=run_predict, args=(data_b,))

可以为程序调用外部命令

程序启动子进程,子进程用来执行外部命令,程序等待外部命令执行完成后再继续运行。

例如,在python程序中执行其他 .py文件
import subprocess
subprocess.Popen(‘python xxx.py’, shell=True)

3. 多进程模块multiprocessing的使用

multiprocessing是Python的多进程模块。

常用的multiprocessing.Process()multiprocess.Pool()函数可以实现多个进程的并行。

  • Process():每次只启动一个子进程,并为这个子进程分配一个子任务;
  • Pool():启动一个固定大小的进程池,批量创建多进程,进程池的大小就是待启动的子进程的数量,并迭代地为这几个子进程分配不同的子任务。

代码例子:启动10个子进程,并打印每个子进程的id号

import os
from multiprocessing import Process, Pool

def func(i):
	print('Subprocess %d (%s).' % (i, os.getpid()))

if __name__ == ‘__main__’:

	# 方法1:使用Process()
	p_list = []
	for i in range(10):
		p = Process(target=func, args=(i,))   # 注释 1
		p.start()
		p_list.append(p)    # 注释 2
	for p in p_list:
		p.join()
	
	# 方法2:使用Pool().apply_async()
	p = Pool(8)
	for i in range(10):
		p.apply_async(func, args=(i,))
	p.close()       # 注释 3
	p.join()
	
	# 方法3:使用Pool().map_async()
	p = Pool(8)
	p.map_async(func, range(8), chunksize=8)     # 注释 4
	p.close()
	p.join()

如上3种方法是等价的。

注释1-4说明:

  • 注释1:注意args的末尾逗号’,’不能少
  • 注释2:把10个子进程存入列表,目的是让10个子进程join入主进程。这样做显然有点麻烦,所以如果要启动的子进程较多时,尽量不用Process,而是用Pool。
  • 注释3:和Process的p.start()不同,Pool里使用p.close(),是说此后进程池将关闭,不允许新的子进程加入。
  • 注释4:和apply_async不同,map_async只允许传入可迭代对象(如list、set等),而且这里可迭代对象的每个元素(数字i, 并不是range(8))是作为参数传入func()函数。总结下:map_async是先把可迭代对象等分为chunksize份,chunksize可以设置为进程池的大小,chunksize份数据分别分配给不同的子进程。

Windows和Linux的multiprocessing底层实现不同。

Windows不能os.fork(),因此是在子程序中重新运行一遍主进程的程序,相当于import;Linux可以os.fork(),因此是把主进程的变量直接copy一份来用的。

4. 子进程模块subprocess的使用

subprocess是Python的子进程管理模块。
subprocess.Popen()subprocess.check_output()subprocess.run()函数可以fork一个子进程,用来运行外部的程序。

  • subprocess.Popen():主进程等待子进程结束后再继续执行,子进程的执行结果以字符串格式返回;
  • subprocess.check_output():主进程fork子进程后继续执行,不等待子进程结束,除非显式调用wait()或communicate()命令。

代码例子:执行外部程序xxx.py

# 方法1:使用check_output
out = subprocess.check_output(['python', 'xxx.py']).decode(‘utf-8)  # 注释1

# 方法2:使用Popen
p = subprocess.Popen(['python’, ‘xxx.py'], stdout=subprocess.PIPE)
p.communicate()   # 注释2

# 方法3:使用run
p = subprocess.run([‘python’, ‘xxx.py’], capture_output=True, text=True)
out = p.stdout    # 注释3

注释1-3说明:

  • 注释1:out是xxx.py程序的执行结果。check_output将执行结果以bytes格式返回,因此需要.decode(‘utf-8’)进行解码。在python3.6+,check_output函数中内嵌了encoding参数,也可以写成out = subprocess.check_output(['python', 'xxx.py'], encoding=’utf-8’)
  • 注释2:communicate()用于阻塞主进程,等待子进程。
  • 注释3:out是xxx.py程序的执行结果。注意capture_output参数是python3.7+的特性。

5. 模型训练/推理时,怎么拆分多进程子任务?

如果遇到Pool()执行时间比较长,多进程提速不明显的情况如何解决?

首先分析Pool多进程的主要耗时点:

  • 为进程池中的每一个子进程调用Pool.apply_ascy(),分配任务
  • 完成函数调用和函数返回值的序列化/反序列化
  • 等待共享内存队列锁
  • 底层调用os.fork()的时耗

拆分原则1:为了提速,多进程之间,共享变量/内存要尽可能少。

如调用Process()或Pool().apply_async()启动多进程时,data_loader要放在各个进程内部,而不是让多个进程去调用一个共享的data_loader(某一进程读取dataloader的同时将其阻塞,导致进程间相互竞争,无法同时访问资源,因此进程总要等待前一个进程读取完一个batch的数据后,再读取下一个batch数据,很慢)。

拆分原则2:进程数分配适当,单个进程的CPU计算时间不能过短。

如果每个进程只推理1条数据,总体提速不大。因为multiprocessing每次为进程分配任务都会底层调用os.fork()需要时间开销,进程的切换也存在时间开销。

Reference

Store output of subprocess.Popen call in a string

Multiprocessing pool slower than just using ordinary functions

多线程,多进程,多核总结

python多进程多线程

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-10-06 12:32:02  更:2021-10-06 12:32:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 0:43:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码