众所周知,python中存在GIL锁,导致同一时间只能有一个线程在CPU上运行,而且是单个CPU上运行,不管你的CPU有多少核数。然而如今大多数的个人电脑或者服务器都是多核CPU,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。

1.如何理解进程?

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

2.Process类

在python中常用到的多进程相关的包有 multiprocessingconcurrent,归根结底上都是使用到了Process类,只是后者简化了操作,本文只介绍multiprocessing,对于concurrent会在之后进行详细介绍。

磨刀不费砍材功,我们先了解Process类的相关用法,然后在了解多进程的使用。

语法:Process([group [, target [, name [, args [, kwargs]]]]])

Process参数:

参数 释义
group 参数未使用,默认值为None。
target 表示调用对象,即子进程要执行的任务
args 表示调用的位置参数元组
kwargs 表示调用对象的字典。如kwargs = {‘name’:Jack, ‘age’:18}。
name 子进程名称。

Process属性方法:

方法/属性 说明
start() 启动进程,调用进程中的run()方法。
run() 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 。调用run()函数的不可以调用join()
terminate() 强制终止进程,不会进行任何清理操作。如果该进程终止前,创建了子进程,那么该子进程在其强制结束后变为僵尸进程;如果该进程还保存了一个锁那么也将不会被释放,进而导致死锁。使用时,要注意。is_alive() 判断某进程是否存活,存活返回True,否则False。
is_alive() 判断某进程是否存活,存活返回True,否则False
join([timeout]) 主线程等待子线程终止。timeout为可选择超时时间;需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程 。
daemon 默认值为False,如果设置为True,代表该进程为后台守护进程;当该进程的父进程终止时,该进程也随之终止;并且设置为True后,该进程不能创建子进程,设置该属性必须在start()之前
name 进程名称。
exitcode 进程运行时为None,如果为-N,表示被信号N结束了。
authkey 进程身份验证,默认是由os.urandom()随机生成32字符的字符串。这个键的用途是设计涉及网络连接的底层进程间的通信提供安全性,这类连接只有在具有相同身份验证才能成功。

3.如何创建进程?

Python 创建的子进程执行的内容,和启动该进程的方式有关。而根据不同的平台,启动进程的方式大致可分为以下 3 种:

 multiprocessing.set_start_method('fork')# python 启动进程方式·# spawn  windows or mac os# fork,forkserver  linux
名称 释义
spawn window和MacOS下默认使用该方式,是最常见的方式。父进程会启动一个新的 Python 解释器,该解释器只会从父进程继承必要的资源来支持运行进程对象的run方法。值得注意的是,spwan产生的进程不会复制父进程中不必要的文件标识符或者句柄,这是相对于fork的一个优势。但是代价是spawn产生子进程的速度比较慢。
fork 只适用于Unix,创建的子进程会默认复制一份主进程的数据。双方数据互相不影响
forserver 是一个独立的进程,此后需要产生子进程的时候,父进程需要联系该进程 fork 一个子进程。因为 forkserver 本身是一个单线程进程,所以是线程安全的。而且,与 spawn 类似,子进程只会继承必要的资源。

方法

​ 此方法是直接使用multiprocessing。

​ 将要执行的操作直接封装到方法中,在创建Process的时候,传入函数名称以及相关参数。

import time
from multiprocessing import Process# 无参数
def process_without_args()->None:print("process_without_args")time.sleep(3)# 有参数
def process_with_args(name:str)->None:print("process_with_args age is", name)time.sleep(3)if __name__ == '__main__':# 查看自己的CPU核心数print(multiprocessing.cpu_count())processes = []for i in range(3):# 无参数without_arg = Process(target=process_without_args)# 有参数,args传入元组with_arg = Process(target=process_with_args, args=(f"process {str(i)}",))without_arg.start()with_arg.start()processes.append(with_arg)#  join 主线程等待子线程,#  如果主线程等待子线程结束,end将最后输出#  反之 end不会再最后输出[with_arg.join() for with_arg in processes]print("end ")#结果#process_with_args age is process 1# process_with_args age is process 2# process_without_args# process_without_args# process_without_args# process_with_args age is process 0# end

方法二

​ 此方法是直接使用multiprocessing。

​ 通过继承Process类的方式,需要重写其中的run方法。

from multiprocessing import Processclass MyProcess(Process):def __init__(self, name: str, i: int):super().__init__()self.name = nameself.i = idef run(self):print(f"name {self.name} i {self.i}")if __name__ == '__main__':p1 = MyProcess('张三', 3)p2 = MyProcess('李四', 4)p3 = MyProcess('王五', 5)p1.start()# 自动调用run()p2.start()p3.run()  # 直接调用run()p2.join()p1.join()# p3.join()  # 调用run()函数的不可以调用join()print("主进程结束")#name 王五 i 5# name 张三 i 3# name 李四 i 4# 主进程结束

4.进程间通信与锁

进程是系统独立调度核分配系统资源(CPU、内存)的基本单位,进程之间是相互独立的。

每启动一个新的进程相当于把数据进行了一次克隆,子进程里的数据修改无法影响到主进程中的数据,不同子进程之间的数据也不能共享,这是多进程在使用中与多线程最明显的区别。

但是难道Python多进程中间难道就是孤立的吗?当然不是,python也提供了多种方法实现了多进程中间的通信和数据共享(可以修改一份数据)

Python提供了不少进程间通信的方式,包含管道,队列,数据共享,共享内存等方式。

下文将一一解释。

队列

创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。

Queue[maxsize]:maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。底层队列使用管道和锁定实现。另外,还需要运行支持线程以便队列中的数据传输到底层管道中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olwVDOrK-1636926935464)(file:///C:\Users\小佰\AppData\Roaming\Tencent\Users\707979910\TIM\WinTemp\RichOle\ELGLRDOA(D(115B)]2`S17IP.png)

import os
import time
import multiprocessingdef input_queue(q):info = f"放入数据 {str(time.asctime())}"q.put(info)print(info)def output_queue(q):info = q.get()print(f'从队列中取数据 {info}')if __name__ == '__main__':multiprocessing.freeze_support()record_one = []record_two = []queue = multiprocessing.Queue(3)# 放入数据for i in range(10):p = multiprocessing.Process(target=input_queue, args=(queue,))p.start()record_one.append(p)# 取出数据for i in range(10):p = multiprocessing.Process(target=output_queue, args=(queue,))p.start()record_one.append(p)for p in record_one:p.join()for p in record_two:p.join()

管道

Pipe([duplex]):在线程之间创建一条管道,并返回元祖(con1,con2),其中con1,con2表示管道两端连接的对象。

duplex:默认管道为全双工的,如果将duplex映射为False,con1只能用于接收,con2只能由于发送。

应该特别注意管道端点的正确管理问题,如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。如果忘记执行这些步骤,程序可能在消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生成EOFError异常。因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点。

pipe的相关方法,如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mciYKpfY-1636926935468)(C:\Users\小佰\AppData\Roaming\Typora\typora-user-images\image-20211114160715289.png)]

from multiprocessing import Process, Pipedef func(conn):conn.send({"1":2})conn.send("路在脚下!")  # 发送conn.close()if __name__ == '__main__':parent_conn, child_conn = Pipe()p = Process(target=func, args=(child_conn,))p.start()print(parent_conn.recv())  # 接收print(parent_conn.recv())  # 接收p.join()

Manager

​ 队列和管道可以认为是两个进程之间的通信。使用manager,则是数据共享。manager支持python的基本数据类型, 例如dict 和 list,通过manager来进行创建,使用方式与普通的一致。manager是进程安全的。

from multiprocessing import Process, Managerdef fun1(dic, lis, index):dic[index] = 'a'lis.append(index)  if __name__ == '__main__':with Manager() as manager:dic = manager.dict()  # 注意字典的声明方式,不能直接通过{}来定义l = manager.list([])  # []process_list = []for i in range(10):p = Process(target=fun1, args=(dic, l, i))p.start()process_list.append(p)for res in process_list:res.join()print(dic)print(l)

因为多个进程同时访问一个数据,与多线程一样,会产生数据竞争,在使用的时候也可以加锁,来保证进程的安全性。

# 使用进程锁
def task(lock):lock.acquire()do somethinglock.release()
if __name__ == '__main__':lock = Lock()for i in range(10):p = Process(target=task, args=(lock,))p.start()

SharedMemory

共享内存,python3.8新功能。

官方网址:https://docs.python.org/zh-cn/3/library/multiprocessing.shared_memory.html

共享内存是指 “System V 类型” 的共享内存块(虽然可能和它实现方式不完全一致)而不是 “分布式共享内存”。这种类型的的共享内存允许不同进程读写一片公共(或者共享)的易失性存储区域。一般来说,进程被限制只能访问属于自己进程空间的内存,但是共享内存允许跨进程共享数据,从而避免通过进程间发送消息的形式传递数据。相比通过磁盘、套接字或者其他要求序列化、反序列化和复制数据的共享形式,直接通过内存共享数据拥有更出色性能。

本文只是简述ShareableList用法。提供一个可修改的类 list 对象,其中所有值都存放在共享内存块中。这限制了可被存储在其中的值只能是 int, float, bool, str (每条数据小于10M), bytes (每条数据小于10M)以及 None 这些内置类型。它另一个显著区别于内置 list 类型的地方在于它的长度无法修改(比如,没有 append, insert 等操作)且不支持通过切片操作动态创建新的 ShareableList 实例。

class multiprocessing.shared_memory.ShareableList(sequence=None, *, name=None)
提供一个可修改的类 list 对象,其中所有值都存放在共享内存块中。这限制了可被存储在其中的值只能是 int, float, bool, str (每条数据小于10M), bytes (每条数据小于10M)以及 None 这些内置类型。它另一个显著区别于内置 list 类型的地方在于它的长度无法修改(比如,没有 append, insert 等操作)且不支持通过切片操作动态创建新的 ShareableList 实例。

sequence 会被用来为一个新的 ShareableList 填充值。 设为 None 则会基于唯一的共享内存名称关联到已经存在的 ShareableList。

name 是所请求的共享内存的唯一名称,与 SharedMemory 的定义中所描述的一致。 当关联到现有的 ShareableList 时,则指明其共享内存块的唯一名称并将 sequence 设为 None。

count(value)
返回 value 出现的次数。

index(value)
返回 value 首次出现的位置,如果 value 不存在, 则抛出 ValueError 异常。

format
包含由所有当前存储值所使用的 struct 打包格式的只读属性。

shm
存储了值的 SharedMemory 实例。

示例代码:

# 生成share memory list
import time
from multiprocessing import shared_memory
shm_a = shared_memory.ShareableList(['张三', 2, 'abc'], name='123')
time.sleep(20)
print(shm_a)
shm_a.shm.close()
shm_a.shm.unlink()# 获取并修改
import time
from multiprocessing import shared_memoryshm_a = shared_memory.ShareableList(name='123')
print(shm_a)
shm_a[0] = "9999"
print(shm_a)
shm_a.shm.close()
shm_a.shm.unlink()

如果您觉得这篇文章对您有帮助,欢迎关注微信公众号:码上小佰,笔者会分享更多干货的文章。

参考:

​ https://blog.csdn.net/qq_33567641/article/details/81947832

​ https://zhuanlan.zhihu.com/p/64702600

python中如何优雅地使用多进程(1)相关推荐

  1. 全网惟一面向软件测试人员的Python基础教程-在Python中如何优雅的切西瓜呢?

    全网惟一面向软件测试人员的Python基础教程 起点:<python软件测试实战宝典>介绍 第一章 为什么软件测试人员要学习Python 第二章 学Python之前要搞懂的道理 第三章 你 ...

  2. Python中如何优雅地使用switch语句

    文章目录 Python中如何优雅地使用switch语句 案例一(简单情况) 案例二(带条件判断) 版权声明:本文为博主原创文章,转载请注明原文出处! 写作时间:2019-03-07 13:49:45 ...

  3. python中的多任务-多线程和多进程

    多线程和多进程都是实现多任务的一种方式,但是对于很多初学者来说想分清楚他们往往是一件非常头疼的事,首先我们需要了解多任务的概念. 所谓的多任务就是在同一时刻同时做很多事情,比如我们一边使用浏览器上网一 ...

  4. 在Python中如何优雅地创建表格

    1. 引言 如果能够将我们的无序数据快速组织成更易读的格式,对于数据分析非常有帮助. Python 提供了将某些表格数据类型轻松转换为格式良好的纯文本表格的能力,这就是 tabulate 库. 闲话少 ...

  5. Python中如何优雅的使用定时任务?

    在项目中,我们可能遇到有定时任务的需求.其一:定时执行任务.例如每天早上 8 点定时推送早报.其二:每隔一个时间段就执行任务.比如:每隔一个小时提醒自己起来走动走动,避免长时间坐着.今天,我跟大家分享 ...

  6. Python 中更优雅的环境变量设置方案

    本文授权转载自公众号:进击的Coder 在运行一个项目的时候,我们经常会遇到设置不同环境的需求,如设置是开发环境.测试环境还是生产环境,或者在某些设置里面可能还需要设置一些变量开关,如设置调试开关.日 ...

  7. Python中的多进程并行简明教程

    1. 什么是多进程并行 一般来说,多进程意味着并行运行两个或者多个任务.在Python中我们可以使用python内置的multiprocessing模块来实现这一功能. 我们来举个栗子,假设我们需要运 ...

  8. 如何优雅的在python中暂停死循环?

    死循环 有时候在工作中可能会遇到要一直执行某个功能的程序,这时候死循环就派上用途了,python中死循环的具体形式大致如下 while True:run_your_code() 结束死循环 通常我们结 ...

  9. python廖雪峰_【Python】python中实现多进程与多线程

    进程与线程 进程(process)就是任务,是计算机系统进行资源分配和调度的基本单位[1].比如,打开一个word文件就是启动了一个word进程. 线程(thread)是进程内的子任务.比如word中 ...

最新文章

  1. 【错误记录】VMware 虚拟机报错 ( VMWare 中的 Ubuntu 虚拟机网络设置 | 第一次网络设置 )
  2. 微信公众号发多个消息php,微擎系统微信公众号关键字触发回复多条消息实现
  3. mysql存储过程1267_mysql存储过程一例
  4. Java命令学习系列
  5. 测试Java EE 8规范
  6. 物联网在改造海上钻井平台监测方面发挥着关键作用
  7. HDU4349--Xiao Ming's Hope(数论)
  8. MongoDB在windows下的安装步骤:
  9. 阐述Spring框架中Bean的生命周期?
  10. forEach遍历对象数组案例
  11. 快速阅读《构建之法》——构建之法阅读笔记01
  12. [WPF]控件应用多个样式
  13. linux java不能运行命令,linux java不能运行命令
  14. 濡沫江湖一直显示获取服务器地址,濡沫江湖霜儿剧情任务怎么完成?霜儿专属剧情任务接取以及完成攻略...
  15. css锚点定位不准确问题
  16. 2021年4月品牌精选海报合集
  17. 品•文案——聊聊产品文案优化设计
  18. 关于分销体系是怎么理解的?
  19. 牛顿迭代法(C++)
  20. 清华大学邓俊辉《数据结构(C++语言版)第3版》随书资源

热门文章

  1. 0418第十五次课:日常运维-防火墙、定时任务、系统管理
  2. 5gwifi信号测试软件,5G频段无线信号强度较量
  3. Android 获取手机信号强度
  4. 生成式AI助力出门问问赴港IPO,李志飞能否成为中国的OpenAI?
  5. Redis的高可用性
  6. 用女友测试Ubuntu易用性
  7. 运营商纷纭筹建挪动支付公司
  8. 2022年-年度总结报告
  9. nginx配置一个服务器对多个服务器分压(同端口)
  10. ConversionPattern参数的格式含义