0 引言

众所周知,python因为GIL的存在,其多线程只能在一个CPU中调度,对于计算密集型任务完全不能充分利用多核资源,所以需要Python多进程编程。多进程程序最大的性能瓶颈往往出在进程间通信,尤其是进程之间大数据量传递时,选用合适的进程间通信(IPC)方式尤其重要。
本文全面总结Python中进程间通信的各种方法。

1 进程间通信

1.1 概念

进程是操作系统分配和调度系统资源(CPU、内存)的基本单位。进程之间是相互独立的,每启动一个新的进程相当于把数据进行了一次克隆,子进程里的数据修改无法影响到主进程中的数据,不同子进程之间的数据也不能直接共享,这是多进程在使用中与多线程最明显的区别。

进程间通信方法有很多:
(1)信号量( semaphore ) : 信号量是一个共享资源访问者的计数器,可以用来控制多个进程对共享资源的并发访问数。它常作为一种锁机制,防止指定数量的进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段,用于控制某共享资源的并发访问者数量。

(2)信号 ( signal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

(3)管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

(4)有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

(5)消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

(6)共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

(7)套接字( socket ) : socket也是一种进程间通信机制,与其他通信机制不同的是,它主要用于不同机器间的进程通信,同一机器内的进程通信采用此方式是有些浪费的。

(8) 文件:使用文件进行通信是最简单的一种通信方式,一个进程将结果输出到临时文件,另一个进程从文件中读出来。

python提供了多种方法实现了多进程中间的通信和数据共享。

1.2 基于信号量(Semaphore)的IPC

from threading import Semaphore
db_semaphore = Semaphore(2) # 创建信号量
database = []
def insert(data):'''如果insert(data)是一个子进程任务,需要在创建子进程时将信号量db_semaphore作为参数传入子进程任务函数;'''db_semaphore.acquire() # 尝试获取信号量database.append(data)  # 如果信号量获取成功就处理db_semaphore.release() # 释放信号量

1.3 基于信号(Signal)的IPC

Python标准库signal模块提供了在 Python 程序中使用信号处理程序的机制。信号处理程序总是在 Python 主线程中执行,即使信号是在另一个线程中接收的。所以信号不能用作线程间通信的手段,如果需要线程间通信可以使用 threading 模块中的同步函数。此外,只允许主线程设置新的信号处理程序。

信号通信的应用:
(1)故障定位技术(进程的底层故障,例如进程突然中断和一些可能性较小的故障);
(2)对进程的流程控制 ;

signal常用的几个函数
(1)os.kill(pid,sig)
用于从一个进程中发送一个信号给某个进程。
参数解析:
pid 指定发送信号的进程号
sig 要发送的信号代号(需要通过signal模块获取)
(2)signal.alarm(sec)
设置时钟信号,在一定时间后给自身发送一个SIGALRM信号。非阻塞函数,sec为定时长度。
原理:
时钟的创建是进程交由操作系统内核(kernal)帮助创建的,时钟和进程之间是异步执行的,当时钟到时,内核会发送信号给进程,进程接收信号进行相应的响应操作。这就是所谓的python异步处理方案。后面的时钟会覆盖前面的时钟,一个进程。只有一个挂起的时钟

import signal, osdef handler(signum, frame):'''信号处理程序'''print('Signal handler called with signal', signum)raise OSError("Couldn't open device!")# 设置信号处理器
signal.signal(signal.SIGALRM, handler)
# 设置5s的定时,时间到后给自身发送一个SIGALRM信号
signal.alarm(5)# open()可能无限等待,或者打开资源的时间过长
fd = os.open('/dev/ttyS0', os.O_RDWR)
# 关闭定时
signal.alarm(0)

1.4 基于管道(Pipe)的IPC

只有父进程与子进程之前可以用管道传递数据。通过os.read()和os.write()来对文件描述符进行读写操作,使用os.close()关闭描述符。

import os
import sys
import mathdef slice(mink, maxk):s = 0.0for k in range(mink, maxk):s += 1.0/(2*k+1)/(2*k+1)return sdef pi(n):childs = {}unit = n / 10for i in range(10):  # 分10个子进程mink = unit * imaxk = mink + unitr, w = os.pipe()pid = os.fork()if pid > 0:childs[pid] = r  # 将子进程的pid和读描述符存起来os.close(w)  # 父进程关闭写描述符,只读else:os.close(r)  # 子进程关闭读描述符,只写s = slice(mink, maxk)  # 子进程开始计算os.write(w, str(s))os.close(w)  # 写完了,关闭写描述符sys.exit(0)  # 子进程结束sums = []for pid, r in childs.items():sums.append(float(os.read(r, 1024)))os.close(r)  # 读完了,关闭读描述符os.waitpid(pid, 0)  # 等待子进程结束return math.sqrt(sum(sums) * 8)print(pi(10000000))

1.5 基于有名管道(fifo)的IPC

相对于管道只能用于父子进程之间通信,Unix还提供了有名管道可以让任意进程进行通信。有名管道又称fifo,它会将自己注册到文件系统里一个文件,参数通信的进程通过读写这个文件进行通信。
fifo要求读写双方必须同时打开才可以继续进行读写操作,否则打开操作会堵塞直到对方也打开。

import os
import sys
import mathdef slice(mink, maxk):s = 0.0for k in range(mink, maxk):s += 1.0/(2*k+1)/(2*k+1)return sdef pi(n):childs = []unit = n / 10fifo_path = "/tmp/fifo_pi"os.mkfifo(fifo_path)  # 创建named pipefor i in range(10):  # 分10个子进程mink = unit * imaxk = mink + unitpid = os.fork()if pid > 0:childs.append(pid)else:s = slice(mink, maxk)  # 子进程开始计算with open(fifo_path, "w") as ff:ff.write(str(s) + "\n")sys.exit(0)  # 子进程结束sums = []while True:with open(fifo_path, "r") as ff:# 子进程关闭写端,读进程会收到eof# 所以必须循环打开,多次读取# 读够数量了就可以结束循环了sums.extend([float(x) for x in ff.read(1024).strip().split("\n")])if len(sums) == len(childs):breakfor pid in childs:os.waitpid(pid, 0)  # 等待子进程结束os.unlink(fifo_path)  # 移除named pipereturn math.sqrt(sum(sums) * 8)print(pi(10000000))

1.6 基于消息队列(Queue)的IPC

操作系统提供了跨进程的消息队列对象可以让我们直接使用,但是python没有默认提供包装好的api来直接使用。我们必须使用第三方扩展来完成OS消息队列通信。第三方扩展是通过使用Python包装的C实现来完成的。
操作系统提供的消息队列有两种形式,一种是POSIX消息队列,另一种是System V 消息队列,有些操作系统两者都支持,有些只支持其中的一个。

System V 与 POSIX的区别:
(1)System V 存在时间比较老,包括linux等许多系统都支持,但是接口复杂,并且可能各平台上实现略有区别(如ftok的实现及限制)。
(2)POSIX是新标准,现在多数类UNIX系统也已实现,如果只是开发的话,那么还是POSIX好,因为语法简单,并且各平台上实现都一样。

Python中对应的实现工具有posix_ipc与sysv_ipc,二者为同一作者,可以通过pip安装。用法这里就不演示了,可以参考资料:http://semanchuk.com/philip/posix_ipc/。

**注意:**使用posix_ipc与sysv_ipc需要谨慎,如果你的代码需要跨平台,比如在Windows和linux系统上使用,需要:Windows + Cygwin 1.7,Linux with kernel ≥ 2.6

1.7 基于共享内存的IPC

共享内存也是非常高效的多进程通信方式,操作系统负责将同一份物理地址的内存映射到多个进程的不同的虚拟地址空间中。进而每个进程都可以操作这份内存。考虑到物理内存的唯一性,它属于临界区资源,需要在进程访问时搞好并发控制,比如使用信号量。我们通过一个信号量来控制所有子进程的顺序读写共享内存。

python标准库中实现共享内存通信的工具有mmap,但是该库只能用于基本类型,且需要预先分配存储空间,对于自定义类型的对象使用起来有诸多不便。
比较好用的第三方工具有apache开源的pyarrow,可通过pip install pyarrow直接安装,不需要预先定义存储空间且任意可序列化的对象均可存入共享内存。但使用时需要注意:pyarrow反序列化的对象为只读对象不可修改其值,想要修改对象可先通过对象copy。

1.8 基于套接字(Socket)的IPC

看例子:

import os
import sys
import math
import socketdef slice(mink, maxk):s = 0.0for k in range(mink, maxk):s += 1.0/(2*k+1)/(2*k+1)return sdef pi(n):childs = []unit = n / 10servsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 注意这里的AF_INET表示普通套接字servsock.bind(("localhost", 0))  # 0表示随机端口server_address = servsock.getsockname()  # 拿到随机出来的地址,给后面的子进程使用servsock.listen(10)  # 监听子进程连接请求for i in range(10):  # 分10个子进程mink = unit * imaxk = mink + unitpid = os.fork()if pid > 0:childs.append(pid)else:servsock.close()  # 子进程要关闭servsock引用sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.connect(server_address)  # 连接父进程套接字s = slice(mink, maxk)  # 子进程开始计算sock.sendall(str(s))sock.close()  # 关闭连接sys.exit(0)  # 子进程结束sums = []for pid in childs:conn, _ = servsock.accept()  # 接收子进程连接sums.append(float(conn.recv(1024)))conn.close()  # 关闭连接for pid in childs:os.waitpid(pid, 0)  # 等待子进程结束servsock.close()  # 关闭套接字return math.sqrt(sum(sums) * 8)print(pi(10000000))

1.9 基于临时文件(File)的IPC

文件名可以使用子进程的进程id来命名予以区分,进程随时都可以通过os.getpid()来获取自己的进程id。

import os
import sys
import mathdef slice(mink, maxk):s = 0.0for k in range(mink, maxk):s += 1.0/(2*k+1)/(2*k+1)return sdef pi(n):pids = []unit = n / 10for i in range(10):  # 分10个子进程mink = unit * imaxk = mink + unitpid = os.fork()if pid > 0:pids.append(pid)else:s = slice(mink, maxk)  # 子进程开始计算with open("%d" % os.getpid(), "w") as f:f.write(str(s))sys.exit(0)  # 子进程结束sums = []for pid in pids:os.waitpid(pid, 0)  # 等待子进程结束with open("%d" % pid, "r") as f:sums.append(float(f.read()))os.remove("%d" % pid)  # 删除通信的文件return math.sqrt(sum(sums) * 8)print(pi(10000000))

2 一些经验

开发中的应用总结:
(1)仅进程同步不涉及数据传输,可以使用信号、信号量;
(2)若进程间需要传递少量数据,可以使用管道、有名管道、消息队列;
(3)若进程间需要传递大量数据,最佳方式是使用共享内存,推荐使用pyarrow,这样减少数据拷贝、传输的时间内存代价;
(4)跨主机的进程间通信(RPC)可以使用socket通信。

3 参考资料

[1].python官网文档
[2].https://zhuanlan.zhihu.com/p/37370601

Python实现多进程间通信的方法总结相关推荐

  1. python子进程通信_python执行子进程实现进程间通信的方法

    本文实例讲述了python执行子进程实现进程间通信的方法.分享给大家供大家参考.具体实现方法如下: a.py: import subprocess, time subproc = subprocess ...

  2. Python中的进程间通信

    Python中的进程间通信 文章目录 Python中的进程间通信 1 进程间通信 1.1 概念 1.2进程间通信方法 各种进程间通信 1. 基于信号量(Semaphore)的IPC 2 基于信号(Si ...

  3. PHP下操作Linux消息队列完成进程间通信的方法

    2019独角兽企业重金招聘Python工程师标准>>> 来源:http://www.jb51.net/article/24353.htm 关于Linux系统进程通信的概念及实现可查看 ...

  4. 怎么用python找论文_如何利用Python绘制学术论文图表方法

    论文中图表形式多样,常用的处理工具有excel.MATLAB以及Python等,excel自处理的方法有两个缺陷: 1.当数据较多时,容易出现excel"翻白眼"的现象: 2.需要 ...

  5. python加载图片的方法_python从网络读取图片并直接进行处理的方法

    python从网络读取图片并直接进行处理的方法 更新时间:2015年05月22日 10:09:08 作者:pythoner 这篇文章主要介绍了python从网络读取图片并直接进行处理的方法,涉及cSt ...

  6. php判断网页编码,python判断网页编码的方法

    在web开发的时候我们经常会遇到网页抓取和分析,各种语言都可以完成这个功能.我喜欢用python实现,因为python提供了很多成熟的模块,可以很方便的实现网页抓取. 但是在抓取过程中会遇到编码的问题 ...

  7. python使用imbalanced-learn的ClusterCentroids方法进行下采样处理数据不平衡问题

    python使用imbalanced-learn的ClusterCentroids方法进行下采样处理数据不平衡问题 机器学习中常常会遇到数据的类别不平衡(class imbalance),也叫数据偏斜 ...

  8. python使用imbalanced-learn的AllKNN方法进行下采样处理数据不平衡问题

    python使用imbalanced-learn的AllKNN方法进行下采样处理数据不平衡问题 机器学习中常常会遇到数据的类别不平衡(class imbalance),也叫数据偏斜(class ske ...

  9. python使用imbalanced-learn的NearMiss方法进行下采样处理数据不平衡问题

    python使用imbalanced-learn的NearMiss方法进行下采样处理数据不平衡问题 机器学习中常常会遇到数据的类别不平衡(class imbalance),也叫数据偏斜(class s ...

最新文章

  1. C#.NET 添加图片水印
  2. PAT 1041. 考试座位号(15)
  3. jQuery运行方式818
  4. 2015-4-20 BAV推广页面修改前后对比-安全网购
  5. 黄金白银、古董与收藏
  6. window中使用jedis连接虚拟机中的redis
  7. java中mvc事务_Spring MVC 事务配置
  8. php 明天凌晨,用php判断时间戳来输出刚刚,分钟前,小时前昨天和时间
  9. 形式语义学-JLU-ZHH
  10. [转载].NET开发常用的10条实用代码
  11. Linux内核IP Queue机制的分析
  12. configure: line syntax error near unexpected token `win32-dll'
  13. 职业计算机试题,职业高中计算机专业综合练习试题一
  14. 揭密如何写ASP木马后门
  15. 笨办法学python3 视频打包_正版 笨办法学Python 3 进阶篇+笨办法 学Python 3 视频教学 笨方法学Python核...
  16. 嵌入式linux字体库,嵌入式 初探freetype字体库
  17. 使用Photoshop去掉图片上的文字的几种方法
  18. Word设置子标题跟随上级标题变化
  19. 两台主机如何共享一套键鼠一台显示器?
  20. 最新的鄞比 服务器mp4,阳光之下播出,彭冠英好适合傅慎行,和李承鄞比肩的人出来了...

热门文章

  1. 如何提高部门之间的沟通协作效率
  2. 【Writing】记录一:名词复数形式-可数名词和不可数名词
  3. 仿新浪微博@功能 JS的实现 ——使用JQ At.js 和HTML5 contentEditable
  4. Python文件格式转换
  5. 最全的 Vue 跨平台性能优化指南出炉
  6. 打造稳健的MT4 黄金交易策略,挑战1年翻3倍不爆仓的量化交易EA(附mql4程序)
  7. chrome浏览器解决跨域调试问题
  8. r5 4500u和r7 4800u 哪个好
  9. (通用)解决IDEA注释顶格问题
  10. 手机 html自动播放视频格式,Video 在网页和移动端无法自动播放问题??