Python多线程通信,Python进程间通信

  Python多线程通信,Python进程间通信

  引言众所周知,python的多线程由于GIL的存在,只能在一个CPU上调度,对于计算密集型的任务,无法充分利用多核资源,所以需要Python多进程编程。多进程程序最大的性能瓶颈往往在于进程间的通信,尤其是在进程间传输大量数据的时候。选择合适的IPC方法尤为重要。

  本文全面总结了Python中进程间通信的各种方法。

  1进程间通信1.1概念进程是操作系统分配和调度系统资源(CPU、内存)的基本单位。进程是相互独立的。每启动一个新进程,就相当于克隆了一次数据。子流程中的数据修改不能影响主流程中的数据,不同子流程之间的数据不能直接共享。这是多进程和多线程最明显的区别。

  有许多进程间通信方法:

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

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

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

  (4)有名管道 (named pipe):众所周知的管道也是半双工通信模式,但它允许不相关进程之间的通信。

  (5)消息队列( message queue ):消息队列是存储在内核中的消息的链表,由消息队列标识符标识。消息队列克服了信号传输信息量少、流水线只能承载无格式字节流和缓冲区大小有限的缺点。

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

  (7)套接字( socket ): Socket也是一种进程间通信机制。与其他通信机制不同,它主要用于不同机器之间的进程间通信。在同一台机器上使用这种方法进行进程间通信有些浪费。

  (8)文件:用文件进行交流是最简单的交流方式。一个进程将结果输出到一个临时文件,另一个进程从该文件中读取结果。

  Python提供了许多方法来实现多个进程之间的通信和数据共享。

  1.2基于信号量的IPC从线程导入信号量db _ Semaphore=Semaphore(2)# create Semaphore database=[]def insert(data): 如果insert(data)是一个子进程任务,那么在创建子进程时需要将semaphore db_semaphore作为参数传递到子进程任务函数中;Db_semaphore.acquire() #尝试获取信号量database . append(data)# Process db _ semaphore . Release()#释放信号量1.3基于信号的IPC Python标准库信号模块提供了在Python程序中使用信号处理程序的机制。信号处理程序总是在Python主线程中执行,即使信号是在另一个线程中接收的。因此,信号不能用作线程间通信的手段。如果需要线程间通信,可以使用线程模块中的同步功能。此外,只允许主线程设置新的信号处理程序。

  信号的应用:

  (1)故障定位技术(流程底层故障,如流程突然中断和一些可能性较小的故障);

  (2)过程的过程控制;

  信号常用的几种功能

  (1)os.kill(pid,sig)

  用于从一个进程向另一个进程发送信号。

  参数分辨率:

  指定pid发送信号的过程号。

  sig发送的信号代码(需要通过信号模块获取)

  (2)信号报警(秒)

  设置时钟信号,并在一定时间后给自己发送一个信号。非阻塞功能,sec是计时长度。

  原则:

  时钟是由进程在操作系统内核的帮助下创建的。时钟与流程异步执行。时钟到了,内核会给进程发送一个信号,进程会收到信号执行相应的响应操作。这就是所谓的python异步处理方案。后面的时钟将覆盖前面的时钟,这是一个过程。只有一个挂钟。

  Import,OS def handler (signum,frame): signal handler print( signal handler called with signal ,signum)引发OS错误(无法打开设备!)#设置信号处理器signal.signal(信号。sigalrm,handler) #设置定时为5s,时间到了给自己发一个SIGALRM信号。signal.alarm(5)# open()可能会无限期等待,或者资源可能会打开太久fd=os.open(/dev/ttyS0 ,os)。o _ RDWR)# shut down Timing signal . alarm(0)1.4基于管道的管道之前只有父进程和子进程可以通过管道传输数据。通过os.read()和os.write()读写文件描述符,用os.close()关闭描述符。

  import OSI import sys import mathdef slice(mink,maxk): s=0.0 for k in range(mink,MaxK):s=1.0/(2 * K1)/(2 * K1)return sdef pi(n):children={ } unit=n/10 for I in range(10):#分成10个子进程mink=unit * i maxk=mink unit r, w=OS . pipe()pid=OS . fork()if PID 0:children[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,Erchilds.items 1024)) os.close (r) #读取后关闭读取描述符os.waitpid(pid,0) #等待子进程返回Math结束。 基于著名管道(fifo)的SQRT(sum(SUMS)* 8)Print(PI(1000000))1.5 IPC只能用于与管道相关的父进程和子进程之间的通信。Unix还提供了一个著名的管道来允许任何进程进行通信。一个众所周知的管道,也叫fifo,它把自己注册在文件系统的一个文件中,参数通信的进程通过读写这个文件进行通信。

  要求fifo两边必须同时打开才能继续读写,否则打开操作会被阻塞,直到另一边也被打开。

  import OSI import sys import mathdef slice(mink,maxk): s=0.0 for k in range(mink,maxk):s=1.0/(2 * k 1)/(2 * k 1)return sdef pi(n):childs=[]unit=n/10 FIFO _ path=/tmp/FIFO _ pi OS . MK FIFO(FIFO _ path)#为i in range(10)创建命名管道:#分成10个子进程mink=unit * I maxk=mink unit PID=OS . fork() Maxk) #子进程开始计算用open (FIFO _ path, w )as ff:ff . write(str(s) \ n )sys . exit(0)#子进程结束sums=[] while true:用open (FIFO _ path,R) as ff: #子进程关闭写结束,读进程会收到eof #,所以必须循环打开。 多次阅读#即可完成循环。总和。Extend ([float (x) for x in ff。阅读(1024)。剥离()。split( \ n )])if len(sums)==len(childs):break for PID in childs:OS . wait PID(PID,0) #等待子进程结束OS . unlink(FIFO _ path)# remove named pipe return math。sqrt(sum(sums)* 8)print(pi(1000000))1.6基于消息队列的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,内核 2.6的Linux。

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

  python标准库中共享内存通信的工具是mmap,但是这个库只能用于基本类型,存储空间需要提前分配,所以不方便使用自定义类型的对象。

  一个很好的第三方工具是apache的开源pyarrow,可以通过pip install pyarrow直接安装。不需要预先定义存储空间,任何可序列化的对象都可以存储在共享内存中。但是用的时候需要注意:pyarrow反序列化的对象为只读对象不可修改其值,想要修改对象可先通过对象copy。

  1.8以Socket(套接字)为例的IPC:

  import OSI import sys import math import socket def slice(mink,maxk): s=0.0 for k in range(mink,maxk):s=1.0/(2 * k 1)/(2 * k 1)return sdef pi(n):childs=[]unit=n/10 server sock=socket。插座(插座.AF_INET,插座.袜子_流)#注意这里的AF_INET表示普通套接字servsock.bind((localhost ,0)) # 0表示随机端口server _ address=服务器套接字。getsockname()#拿到随机出来的地址,给后面的子进程使用servsock.listen(10) #监听子进程连接请求对于范围(10)内的我:#分10个子进程貂=单位* I maxk=貂单位PID=OS。如果PID为0:childs,则使用fork()。附加(PID)else:服务器sock。关闭()#子进程要关闭servsock引用sock=socket.socket(插座.AF_INET,插座.SOCK _ STREAM)袜子。连接(服务器地址)#连接父进程套接字s=slice(mink,maxk) #子进程开始计算袜子。sendall(str(s))袜子。关闭()#关闭连接sys.exit(0) #子进程结束sums=[] for pid in childs: conn,_=servsock.accept() #接收子进程连接总和。append(float(conn . recv(1024)))conn . close()#关闭连接对于蔡尔兹中的PID:OS。waitpid(PID,0) #等待子进程结束servsock.close() #关闭套接字回归数学。sqrt(sum(sums)* 8)print(pi(10000000))1.9基于临时文件(文件)的工业程序控制(工业过程控制的缩写)文件名可以使用子进程的进程编号来命名予以区分,进程随时都可以通过os.getpid()来获取自己的进程身份证。

  import OSI import sys import mathdef slice(mink,maxk): s=0.0 for k in range(mink,maxk):s=1.0/(2 * k 1)/(2 * k 1)return sdef pi(n):pids=[]unit=n/10 for I in range(10):#分10个子进程貂=单位* I maxk=貂单位PID=OS。如果PID为0:pids,则为fork()。追加(PID)else:s=slice(mink,maxk) #子进程开始计算with open(%d % os.getpid(), w )as f:f . write(str(s))sys。退出(0)#子进程结束pid中pid的sums=[]:OS。waitpid(PID,0) #等待子进程结束用open(%d % pid, r )作为f:sums。append(float(f . read()))OS。删除( % d % PID )#删除通信的文件回归数学。sqrt(sum(sums)* 8)print(pi(10000000))2一些经验开发中的应用总结:

  (1)仅进程同步不涉及数据传输,可以使用信号、信号量;

  (2)若进程间需要传递少量数据,可以使用管道、有名管道、消息队列;

  (3)若进程间需要传递大量数据,最佳方式是使用共享内存,推荐使用皮阿罗,这样减少数据拷贝、传输的时间内存代价;

  (4)跨主机的进程间通信(RPC)可以使用窝通信。

  3参考资料[1].大蟒官网文档

  [2].转兰https://号。胡志。com/p/37370601

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: