由浅入深掌握各种 Python multiprocessing 进程间通信方式

news2024/11/15 21:57:34

由浅入深掌握各种 Python 多进程间通信方式

  • 1、为什么要掌握进程间通信
  • 2、进程间各类通信方式简介
  • 3、消息机制通信
    • 1) 管道 Pipe 通信方式
    • 2) 消息队列Queue 通信方式
  • 4、同步机制通信
    • (1) 进程间同步锁 – Lock
    • (2) 子进程间协调机制 -- Event
  • 5、共享内存方式通信
    • (1) 共享变量
    • (2) 共享内存 Shared_memory
    • 3) ShareableList 共享列表
  • 6、共享内存管理器Manager
    • 1) Manager的主要数据结构
    • 2) 使用步骤
    • 3) 销毁共享内存变量
    • 4) 向管理器注册自定义类型
  • 7、总结


1、为什么要掌握进程间通信


python的多线程代码效率由于受制于GIL,不能利用多核CPU来加速,而多进程方式可以绕过GIL, 发挥多CPU加速的优势,能够明显提高程序的性能。
在这里插入图片描述

但进程间通信却是不得不考虑的问题。 进程不同于线程,进程有自己的独立内存空间,不能使用全局变量在进程间传递数据。
在这里插入图片描述

实际项目需求中,常常存在密集计算、或实时性任务,进程之间有时需要传递大量数据,如图片、大对象等,传递数据如果通过文件序列化、或网络接口来进行,难以满足实时性要求,采用redis,或者kaffka, rabbitMQ 之第3方消息队列包,又使系统复杂化了。

Python multiprocessing 模块本身就提供了消息机制、同步机制、共享内存等各种非常高效的进程间通信方式

了解并掌握 python 进程间通信的各类方式的使用,以及安全机制,可以帮助大幅提升程序运行性能。

2、进程间各类通信方式简介


进程间通信的主要方式总结如下在这里插入图片描述

关于进程间通信的内存安全
内存安全意味着,多进程间可能会因同抢,意外销毁等原因造成共享变量异常。
Multiprocessing 模块提供的Queue, Pipe, Lock, Event 对象,都已实现了进程间通信安全机制。
采用共享内存方式通信,需要在代码中自已来跟踪、销毁这些共享内存变量,否则可能会出同抢、未正常销毁等。造成系统异常。 除非开发者很清楚共享内存使用特点,否则不建议直接使用此共享内存,而是通过Manager管理器来使用共享内存。

内存管理器Manager
Multiprocessing提供了内存管理器Manager类,可统一解决进程通信的内存安全问题,可以将各种共享数据加入管理器,包括 list, dict, Queue, Lock, Event, Shared Memory 等,由其统一跟踪与销毁。

3、消息机制通信


1) 管道 Pipe 通信方式

类似于1上简单的socket通道,双端均可收发消息。
Pipe 对象的构建方法:

parent_conn, child_conn = Pipe(duplex=True/False) 

参数说明

  • duplex=True, 管道为双向通信
  • duplex=False, 管道为单向通信,只有child_conn可以发消息,parent_conn只能接收。

示例代码:

from multiprocessing import Process, Pipe
   def myfunction(conn):
      conn.send(['hi!! I am Python'])
      conn.close()

if __name__ == '__main__':
      parent_conn, child_conn = Pipe()
      p = Process(target=myfunction, args=(child_conn,))
      p.start()
  	print (parent_conn.recv() )
	p.join()

2) 消息队列Queue 通信方式

Multiprocessing 的Queue 类,是在python queue 3.0版本上修改的, 可以很容易实现生产者 – 消息者间传递数据,而且Multiprocessing的Queue 模块实现了lock安全机制。
在这里插入图片描述
Queue模块共提供了3种类型的队列。

(1) FIFO queue , 先进先出,

class queue.Queue(maxsize=0)

(2) LIFO queue, 后进先出, 实际上就是堆栈

class queue.LifoQueue(maxsize=0)

(3) 带优先级队列, 优先级最低entry value lowest 先了列

class queue.PriorityQueue(maxsize=0)

Multiprocessing.Queue类的主要方法:

methodDescription
queue.qsize()返回队列长度
queue.full()队列满,返回 True, 否则返回False
queue.empty()队列空,返回 True, 否则返回False
queue.put(item)将数据写入队列
queue.get()将数据抛出队列 ,
queue.put_nowait(item), queue.get_nowait()无等待写入或抛出

说明:

  • put(), get() 是阻塞方法, 而put_notwait(), get_nowait()是非阻塞方法。
  • Multiprocessing 的Queue类没有提供Task_done, join方法

Queue模块的其它队列类:
(1) SimpleQueue
简洁版的FIFO队列, 适事简单场景使用

(2) JoinableQueue子类
Python 3.5 后新增的 Queue的子类,拥有 task_done(), join() 方法

  • task_done()表示,最近读出的1个任务已经完成。
  • join()阻塞队列,直到queue中的所有任务都已完成。

producer – consumer 场景,使用Queue的示例

import multiprocessing

def producer(numbers, q):
    for x in numbers:
        if x % 2 == 0:
            if q.full():
                print("queue is full")
                break
            q.put(x)
            print(f"put {x} in queue by producer")
    return None

def consumer(q):
    while not q.empty():
        print(f"take data {q.get()} from queue by consumer")
    return None

if __name__ == "__main__":
    # 设置1个queue对象,最大长度为5
    qu = multiprocessing.Queue(maxsize=5,) 

    # 创建producer子进程,把queue做为其中1个参数传给它,该进程负责写
    p5 = multiprocessing.Process(
        name="producer-1",
        target=producer,
        args=([random.randint(1, 100) for i in range(0, 10)], qu)
    )
    p5.start()
    p5.join()
    #创建consumer子进程,把queue做为1个参数传给它,该进程中队列中读
    p6 = multiprocessing.Process(
        name="consumer-1",
        target=consumer,
        args=(qu,)
    )
    p6.start()
    p6.join()

    print(qu.qsize())

4、同步机制通信


(1) 进程间同步锁 – Lock

Multiprocessing也提供了与threading 类似的同步锁机制,确保某个时刻只有1个子进程可以访问某个资源或执行某项任务, 以避免同抢。

例如:多个子进程同时访问数据库表时,如果没有同步锁,用户A修改1条数据后,还未提交,此时,用户B也进行了修改,可以预见,用户A提交的将是B个修改的数据。

添加了同步锁,可以确保同时只有1个子进程能够进行写入数据库与提交操作。

如下面的示例,同时只有1个进程可以执行打印操作。

from multiprocessing import Process, Lock

def f(l, i):
    l.acquire()
    try:
        print('hello world', i)
    finally:
        l.release()

if __name__ == '__main__':
    lock = Lock()

    for num in range(10):
        Process(target=f, args=(lock, num)).start()

(2) 子进程间协调机制 – Event

Event 机制的工作原理:

1个event 对象实例管理着1个 flag标记, 可以用set()方法将其置为true, 用clear()方法将其置为false, 使用wait()将阻塞当前子进程,直至flag被置为true.
这样由1个进程通过event flag 就可以控制、协调各子进程运行。

Event object的使用方法:
1)主函数: 创建1个event 对象, flag = multiprocessing.Event() , 做为参数传给各子进程
2) 子进程A: 不受event影响,通过event 控制其它进程的运行
o 先clear(),将event 置为False, 占用运行权.
o 完成工作后,用set()把flag置为True。
3) 子进程B, C: 受event 影响
o 设置 wait() 状态,暂停运行
o 直到flag重新变为True,恢复运行

主要方法:
 set(), clear()设置 True/False,
 wait() 使进程等待,直到flag被改为true.
 is_set(), Return True if and only if the internal flag is true.

验证进程间通信 – Event

import multiprocessing
import time
import random

def joo_a(q, ev):
    print("subprocess joo_a start")
    if not ev.is_set():
        ev.wait()
    q.put(random.randint(1, 100))
    print("subprocess joo_a ended")

def joo_b(q, ev):
    print("subprocess joo_b start")
    ev.clear()
    time.sleep(2)
    q.put(random.randint(200, 300))
    ev.set()
    print("subprocess joo_b ended")

def main_event():
    qu = multiprocessing.Queue()
    ev = multiprocessing.Event()
    sub_a = multiprocessing.Process(target=joo_a, args=(qu, ev))
    sub_b = multiprocessing.Process(target=joo_b, args=(qu, ev,))
    sub_a.start()
    sub_b.start()
    # ev.set()
    sub_a.join()
    sub_b.join()
    while not qu.empty():
        print(qu.get())

if __name__ == "__main__":
    main_event()

5、共享内存方式通信


(1) 共享变量

子进程之间共存内存变量,要用 multiprocessing.Value(), Array() 来定义变量。 实际上是ctypes 类型,由multiprocessing.sharedctypes模块提供相关功能

注意 使用 share memory 要考虑同抢等问题,释放等问题,需要手工实现。因此在使用共享变量时,建议使用Manager管程来管理这些共享变量。

def  func(num):
    num.value=10.78   #子进程改变数值的值,主进程跟着改变
 
if  __name__=="__main__":
num = multiprocessing.Value("d", 10.0) 
# d表示数值,主进程与子进程可共享这个变量。

    p=multiprocessing.Process(target=func,args=(num,))
    p.start()
    p.join()
 
    print(num.value)

进程之间共享数据(数组型):

import multiprocessing
 
def  func(num):
    num[2]=9999   #子进程改变数组,主进程跟着改变
 
if  __name__=="__main__":
    num=multiprocessing.Array("i",[1,2,3,4,5])   

    p=multiprocessing.Process(target=func,args=(num,))
    p.start() 
    p.join()
 
    print(num[:])

(2) 共享内存 Shared_memory

如果进程间需要共享对象数据,或共享内容,数据较大,multiprocessing 提供了SharedMemory类来实现进程间实时通信,不需要通过发消息,读写磁盘文件来实现,速度更快。
注意:直接使用SharedMemory 存在着同抢、泄露隐患,应通过SharedMemory Manager 管程类来使用, 以确保内存安全。

创建共享内存区:

multiprocessing.shared_memory.SharedMemory(name=none, create=False, size=0)

方法:
父进程创建shared_memory 后,子进程可以使用它,当不再需要后,使用close(), 删除使用unlink()方法
相关属性:
获取内存区内容: shm.buf
获取内存区名称: shm.name
获取内存区字节数: shm.size

示例:

>>> from multiprocessing import shared_memory
>>> shm_a = shared_memory.SharedMemory(create=True, size=10)
>>> type(shm_a.buf)
<class 'memoryview'>
>>> buffer = shm_a.buf
>>> len(buffer)
10
>>> buffer[:4] = bytearray([22, 33, 44, 55])  # Modify multiple at once
>>> buffer[4] = 100                           # Modify single byte at a time
>>> # Attach to an existing shared memory block
>>> shm_b = shared_memory.SharedMemory(shm_a.name)
>>> import array
>>> array.array('b', shm_b.buf[:5])  # Copy the data into a new array.array
array('b', [22, 33, 44, 55, 100])
>>> shm_b.buf[:5] = b'howdy'  # Modify via shm_b using bytes
>>> bytes(shm_a.buf[:5])      # Access via shm_a
b'howdy'
>>> shm_b.close()   # Close each SharedMemory instance
>>> shm_a.close()
>>> shm_a.unlink()  # Call unlink only once to release the shared memory

3) ShareableList 共享列表

sharedMemory类还提供了1个共享列表类型,这样就更方便了,进程间可以直接共享python强大的列表
构建方法:
multiprocessing.shared_memory.ShareableList(sequence=None, *, name=None)


from multiprocessing import shared_memory
>>> a = shared_memory.ShareableList(['howdy', b'HoWdY', -273.154, 100, None, True, 42])
>>> [ type(entry) for entry in a ]
[<class 'str'>, <class 'bytes'>, <class 'float'>, <class 'int'>, <class 'NoneType'>, <class 'bool'>, <class 'int'>]
>>> a[2]
-273.154
>>> a[2] = -78.5
>>> a[2]
-78.5
>>> a[2] = 'dry ice'  # Changing data types is supported as well
>>> a[2]
'dry ice'
>>> a[2] = 'larger than previously allocated storage space'
Traceback (most recent call last):
  ...
ValueError: exceeds available storage for existing str
>>> a[2]
'dry ice'
>>> len(a)
7
>>> a.index(42)
6
>>> a.count(b'howdy')
0
>>> a.count(b'HoWdY')
1
>>> a.shm.close()
>>> a.shm.unlink()
>>> del a  # Use of a ShareableList after call to unlink() is unsupported


b = shared_memory.ShareableList(range(5))         # In a first process
>>> c = shared_memory.ShareableList(name=b.shm.name)  # In a second process
>>> c
ShareableList([0, 1, 2, 3, 4], name='...')
>>> c[-1] = -999
>>> b[-1]
-999
>>> b.shm.close()
>>> c.shm.close()
>>> c.shm.unlink()

6、共享内存管理器Manager


Multiprocessing 提供了 Manager 内存管理器类,当调用1个Manager实例对象的start()方法时,会创建1个manager进程,其唯一目的就是管理共享内存, 避免出现进程间共享数据不同步,内存泄露等现象。

其原理如下: 在这里插入图片描述
Manager管理器相当于提供了1个共享内存的服务,不仅可以被主进程创建的多个子进程使用,还可以被其它进程访问,甚至跨网络访问。本文仅聚焦于由单一主进程创建的各进程之间的通信。

1) Manager的主要数据结构

相关类:multiprocessing.Manager
子类有:

  • multiprocessing.managers.SharedMemoryManager
  • multiprocessing.managers.BaseManager

支持共享变量类型:

  • python基本类型 int, str, list, tuple, list
  • 进程通信对象: Queue, Lock, Event,
  • Condition, Semaphore, Barrier ctypes类型: Value, Array

2) 使用步骤

1)创建管理器对象
snm = Manager()
snm = SharedMemoryManager()

2)创建共享内存变量
新建list, dict
sl = snm.list(), snm.dict()

新建1块bytes共享内存变量,需要指定大小
sx = snm.SharedMemory(size)

新建1个共享列表变量,可用列表来初始化
sl = snm.ShareableList(sequence) 如
sl = smm.ShareableList([‘howdy’, b’HoWdY’, -273.154, 100, True])

新建1个queue, 使用multiprocessing 的Queue类型
snm = Manager()
q = snm.Queue()

示例 :

from multiprocessing import Process, Manager

def f(d, l):
    d[1] = '1'
    d['2'] = 2
    d[0.25] = None
    l.reverse()

if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()
        l = manager.list(range(10))

        p = Process(target=f, args=(d, l))
        p.start()
        p.join()

        print(d)
        print(l)

将打印

{0.25: None, 1: '1', '2': 2}
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

3) 销毁共享内存变量

方法一:
调用snm.shutdown()方法,会自动调用每个内存块的unlink()方法释放内存。或者 snm.close()
方法二
使用with语句,结束后会自动释放所有manager变量

>>> with SharedMemoryManager() as smm:
...     sl = smm.ShareableList(range(2000))
...     # Divide the work among two processes, storing partial results in sl
...     p1 = Process(target=do_work, args=(sl, 0, 1000))
...     p2 = Process(target=do_work, args=(sl, 1000, 2000))
...     p1.start()
...     p2.start()  # A multiprocessing.Pool might be more efficient
...     p1.join()
...     p2.join()   # Wait for all work to complete in both processes
...     total_result = sum(sl)  # Consolidate the partial results now in sl

4) 向管理器注册自定义类型

managers的子类BaseManager提供register()方法,支持注册自定义数据类型。如下例,注册1个自定义MathsClass类,并生成实例。

from multiprocessing.managers import BaseManager

class MathsClass:
    def add(self, x, y):
        return x + y
    def mul(self, x, y):
        return x * y

class MyManager(BaseManager):
    pass

MyManager.register('Maths', MathsClass)

if __name__ == '__main__':
    with MyManager() as manager:
        maths = manager.Maths()
        print(maths.add(4, 3))         # prints 7
        print(maths.mul(7, 8))  

7、总结


sumary

Python多进程(multiprocessing) 编程是绕开GIL提升程序性能的重要方式,进程间通信方式包括消息机制(pipe, queue)、同步机制( Lock, Event) 、Shared Memory(Value, Array, Shared_Memory, etc)等。
直接使用Shared Memory共享内存是不安全的,Multiprocessing.Manager模块提供了安全管理共享内存变量的管理器功能。
在实际编程时,根据主进程与子进程,子进程之间所要交换数据的类型、大小,频度、实时性等需求,来选择适合的通信方式


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

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

相关文章

【Python】控制自己的手机摄像头拍照,并自动发送到邮箱

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 今天这个案例&#xff0c;就是控制自己的摄像头拍照&#xff0c; 并且把拍下来的照片&#xff0c;通过邮件发到自己的邮箱里。 想完成今天的这个案例&#xff0c;只要记住一个重点&#xff1a;你需要一个摄像头 思路…

Android 7.0 OTA升级(高通)

文章目录1. Full OTA 方式升级介绍1.1 Full OTA 制作第一步&#xff1a;生成 msm89xx-target_files-eng.XXX.zip1.2 Full OTA 制作第二步&#xff1a;Modem 等非 HLOS 加入升级包的方法1.3 Full OTA 制作第三步&#xff1a;生成 update.zip 升级包2. Incremental OTA 方式升级介…

Android 基础知识4-2.6LinearLayout(线性布局)

一、LinearLayout的概述 线性布局&#xff08;LinearLayout&#xff09;主要以水平或垂直方式来排列界面中的控件。并将控件排列到一条直线上。在线性布局中&#xff0c;如果水平排列&#xff0c;垂直方向上只能放一个控件&#xff0c;如果垂直排列&#xff0c;水平方向上也只能…

Java基础-xml

1.xml 1.1概述 万维网联盟(W3C) 万维网联盟(W3C)创建于1994年&#xff0c;又称W3C理事会。1994年10月在麻省理工学院计算机科学实验室成立。 建立者&#xff1a; Tim Berners-Lee (蒂姆伯纳斯李)。 是Web技术领域最具权威和影响力的国际中立性技术标准机构。 到目前为止&#…

python基础语法【自用】

✨始发站&#x1f6a9;Python的基础语法&#xff0c;冲冲冲&#xff01; &#x1f6a9;注&#xff1a;本篇为python基础语法篇&#xff0c;因博主之前使用java&#xff0c;所以本基础语法篇实为自用丐版&#xff01; &#x1f332; 你好&#xff0c;世界&#xff01; 安装环境…

虚拟机快照

1. 快照有什么作用&#xff1f; 通俗理解&#xff1a;快照就是备份。 2. VMware Workstation 和 VMware Fusion 都支持制作快照去使用 一、快照 保存当前虚拟机状态。可以恢复 二、 在VMware Workstation Pro中制作并还原快照 三、在VMware Fusion Pro中制作并还原快照 快照制…

210天从外包踏进华为跳动那一刻,我泪目了

前言 没有绝对的天才&#xff0c;只有持续不断的付出。对于我们每一个平凡人来说&#xff0c;改变命运只能依靠努力幸运&#xff0c;但如果你不够幸运&#xff0c;那就只能拉高努力的占比。 2021年4月&#xff0c;我有幸成为了华为的一名高级测试工程师&#xff0c;正如标题所…

【软件测试】python接口自动化测试编写脚本,资深测试总结方法,你的实用宝典......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 接口测试&#xff0…

美团前端一面手写面试题

实现斐波那契数列 // 递归 function fn (n){if(n0) return 0if(n1) return 1return fn(n-2)fn(n-1) } // 优化 function fibonacci2(n) {const arr [1, 1, 2];const arrLen arr.length;if (n < arrLen) {return arr[n];}for (let i arrLen; i < n; i) {arr.push(arr[…

vulnhub Kioptrix4

总结&#xff1a;sql注入&#xff0c;受限shell绕过&#xff0c;mysql提权 目录 下载地址 漏洞分析 信息收集 sql注入 ssh登录绕过受限shell 提权 下载地址 Kioptrix4_Hyper_v.rar (Size: 210 MB)Download: http://www.kioptrix.com/dlvm/Kioptrix4_Hyper_v.rarDownload …

Linux驱动开发详细解析

Linux驱动开发详细解析 驱动概念 驱动与底层硬件直接打交道&#xff0c;充当了硬件与应用软件中间的桥梁。 具体任务 读写设备寄存器&#xff08;实现控制的方式&#xff09;完成设备的轮询、中断处理、DMA通信&#xff08;CPU与外设通信的方式&#xff09;进行物理内存向虚…

关于字符设备驱动的通用概念和写法

概述 设备驱动程序可以使用模块的方式动态加载到内核中去。加载模块的方式与以往的应用程序开发有很大的不同。以往在开发应用程序时都有一个 main()函数作为程序的入口点&#xff0c;而在驱动开发时却没有 main()函数&#xff0c;模块在调用 insmod 命令时被加载&#xff0c;…

JVM学习笔记一:类加载子系统

目录 前言 类加载子系统的作用 类加载器角色的位置 类加载器分类 虚拟机自带的加载器 启动类加载器&#xff08;引导类加载器&#xff09; 扩展类加载器 系统类加载器 用户自定义类加载器 什么时候需要自定义类加载器&#xff1f; 如何自定义类加载器&#xff1f; …

【验证码的识别】—— 极验验证码的识别

前言 &#xff08;结尾有彩蛋欧&#xff09; 目前&#xff0c;许多网站采取各种各样的措施来反爬虫&#xff0c;其中一个措施便是使用验证码。随着技术的发展&#xff0c;验证码的花样越来越多。验证码最初是几个数字组合的简单的图形验证码&#xff0c;后来加入了英文字母和混…

《计算机系统基础》——计算机系统导论

文章目录《计算机系统基础》——计算机系统导论计算机的基本组成程序开发与执行过程机器语言汇编语言高级语言程序的转换处理程序的数据流动计算机系统层次结构早期计算机系统1GL2GL现代计算机系统3GL4GL指令集体系结构《计算机系统基础》——计算机系统导论 &#x1f680;接下…

LaTeX中表格过宽解决方案

最近使用LaTeX处理表格时遇到了一件十分棘手的问题&#xff0c;由于内容较多将表格分成了好多列&#xff0c;但将内容填入表格时由于表格宽度过大&#xff0c;导致表格右边溢出了页面无法查看&#xff0c;查阅大量资料与博文后给出如下解决方案&#xff0c;全文代码已部署在Ove…

C#基础练习题,编程题汇总

C#基础练习题&#xff0c;编程题汇总一、C#提取输入的最大整数二、秒数换算为相应的时、分、秒三、C#计算电梯运行用时demo四、C#用一维数组求解问题五、C#程序教小学生学乘法六、C#winfrm简单例题七、C#类继承习题八、C#绘图例子一、C#提取输入的最大整数 编程实现在一行内输…

分布式任务调度(XXL-JOB)

什么是分布式任务调度&#xff1f; 任务调度顾名思义&#xff0c;就是对任务的调度&#xff0c;它是指系统为了完成特定业务&#xff0c;基于给定时间点&#xff0c;给定时间间隔或者给定执行次数自动执行任务。通常任务调度的程序是集成在应用中的&#xff0c;比如&#xff1a…

[译]PostgreSQL16-新特性-新增IO统计视图:pg_stat_io

PostgreSQL16-新特性-新增IO统计视图&#xff1a;pg_stat_io我们DBA常遇到的问题是&#xff1a;如何优化数据库的IO操作&#xff1f;获取PG服务产生的所有IO情况历来都是一个挑战。首先&#xff0c;PG将IO行为范围内为写WAL和读写数据目录(也就是数据文件)。真正的挑战是&#…

解决实际项目中stalled时间过久的问题

背景 在公司参与了一个做度量统计的项目&#xff0c;该项目的特点是页面上的表格、卡片、图标非常多。项目经常出现一种情况&#xff1a;页面加载速度较慢&#xff0c;开始怀疑是由于计算量较大&#xff0c;后端接口相应速度较慢。优化了一版后端接口后&#xff08;加缓存、优…