本文继续python多任务编程思想(一)和 python多任务编程思想(二)讨论python多进程话题,展开python多进程编程中的最后一个知识点----->python进程间通信的方法。
进程间由于空间独立,资源无法互相直接获取,此时在不同的进程间进行数据传递就需要专门的通信方法。进程间通信的方法包含以下6种,本文将依次进行介绍:
在内存中开辟一段内存空间,形成管道结构。管道对多个进程可见,进程可以对管道进行读写操作
from multiprocessing import Pipe
fd1,fd2 = Pipe(duplex = True)
功能:创建一个管道
参数:默认为双向管道,如果设置为False,则为单向管道
返回值:对于双向管道fd1,fd2都可以进行读写操作;单向管道,fd1只可读,fd2只能写。
读写操作:
fd.recv() ---> 从管道读取内容; 返回值为读取的内容; 如果管道无内容,则阻塞;
fd.send() ---> 向管道写入内容; 参数为发送的内容; 几乎可以发送所有Python支持的数据;
示例代码:
from multiprocessing import Process,Pipe
import os,time
# 创建单向管道
fd1,fd2 = Pipe(False)
def fun(name):
'''向管道内写入内容'''
time.sleep(3)
# 在socket通信时,收发都只能是字节流的形式,管道几乎可以发送所有Python支持的数据
# fd2.send("Hello" + str(name))
# fd2.send({'a':'Alex','b':'Bob'})
# fd2.send([1,2,3,4,5])
# fd2.send((name,))
fd2.send({name})
jobs = []
for i in range(5):
p = Process(target=fun,args=(i,))
# 用列表在创建时记录每个进程,便于子进程的回收
jobs.append(p)
p.start()
for i in range(5):
# 读取管道
data = fd1.recv()
print(data)
for i in jobs:
# 阻塞等待回收子进程
i.join()
在内存中开辟队列结构空间,多个进程可以向队列投放消息,读取时遵循先进先出的原则;
q = Queue(maxsize = 0)
功能:创建队列
参数:maxsize 默认表示根据系统分配空间存储消息,'如果传入一个正整数,则表示最多存放多少条消息'
返回:队列对象
q.put(data,[block,timeout])
功能:存放消息
参数:data:存入的消息(Python数据类型)
block:默认为True表示当队列满的时候阻塞;设置为False表示非阻塞
timeout:当block为True表示超时时间
data = q.get([block,timeout])
功能:取出消息
参数:block默认为True,当队列为空时阻塞;设置为False表示非阻塞
timeout:当block为True表示超时时间
返回值:返回获取到的消息
q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 判断当前序列有多少消息
q.close() 关闭队列
示例代码:
'''通过消息队列Queue实现进程间通信
Queue和Pipe的区别:
Pipe用来在2个进程间通信;Pipe()返回一对连接对象,代表了Pipe的2端。每个对象都有send()和recv()方法;
Queue用来在多个进程间实现通信;使用put()和get()方法向队列中写入和读取数据;
'''
from multiprocessing import Process,Queue
import time
#创建消息队列
q = Queue()
def fun1():
time.sleep(1)
q.put([1,2,3,4])
q.put((5,6,7,8))
q.put({9,0,1,2})
def fun2():
# 队列空时阻塞
print("收到消息:",q.get())
print("收到消息:",q.get())
print("收到消息:",q.get())
p1 = Process(target = fun1)
p2 = Process(target = fun2)
p1.start()
p2.start()
p1.join()
p2.join()
在内存中开辟一段空间,存储数据,对多个进程可见。每次写入共享内存中的数据会覆盖之前的内容。
1. 使用 Value 创建共享内存
obj = Value(ctype,obj)
功能:开辟共享内存空间
参数:ctype 字符串 要转变的C的数据类型
obj 共享内存的初始化数据
返回:共享内存对象
obj.value 表示共享内存中的值。对其修改或者使用即可
2. 使用 Array 创建共享内存
obj = Array(ctype,obj)
功能:开辟共享内存
参数:ctype 字符串 要转变的C的数据类型
obj 要存入共享内存的数据
①列表:将列表存入共享内存,数据类型一致
②正整数:表示开辟几个数据空间
示例代码:
'''Value,Array是python中共享内存映射文件的方法,速度比较快'''
from multiprocessing import Process,Value,Array
def func(n,a):
n.value += 1;
for i in range(len(a)):
a[i] *= 2
if __name__ == "__main__":
# i代表C语言中的整型,1为写入共享内存的数据
num = Value("i",1)
arr = Array("i",range(10))
p = Process(target=func,args=(num,arr))
p.start()
p.join()
print(num.value)
print(arr[::])
print(type(num))
print(type(arr))
1. 一个进程向另一个进程通过信号传递某种讯息,接收方在接收到信号后进行相应的处理;
Linux终端:
kill -l 查看信号名称和编号
kill -signum PID 给PID的进程发送一个信号(e.g. kill -9 进程号 #杀死一个进程)
程序执行的同步和异步
同步 : 按照步骤一步一步顺序执行
异步 : 在程序执行中利用内核,不影响应用层程序持续执行
* 信号是唯一的异步通信方式
关于信号
信号名称 : kill -l 查看到的名称或编号
信号含义 : 信号的作用
默认行为 : 当一个进程接收到信号时采取的行为(终止进程,暂停进程,忽略产生)
e.g.
SIGHUP 终端断开
SIGINT ctrl + c
SIGQUIT ctrl + \
SIGTSTP ctrl + z
SIGKILL 终止进程且不能被处理
SIGSTOP 暂停进程且不能被处理
SIGALRM 时钟信号
SIGCHLD 子进程状态改变发给父进程
2.通过python进行信号处理:
os.kill(pid,sig)
功能 : 发送信号给某个进程
参数 : pid 给哪个进程发送信号
sig 要发送什么信号
signal.alarm(sec)
功能 : 一定时间后给自身发送一个 SIGALRM信号
参数 : 指定时间
* 一个进程中只能设置一个时钟,第二个时钟会覆盖之前的时间
signal.pause()
功能: 阻塞等待一个信号的发生
signal.signal(signum,handler)
功能 : 处理信号
参数 : signum : 要处理的信号
handler : 信号的处理方法,包含以下3种方式:
SIG_DFL 使用默认的方法处理
SIG_IGN 忽略这个信号
func 自定义函数处理信号
def func(sig,frame):
'''sig表示要处理的信号;frame信号的结构对象'''
* signal函数是一个异步处理函数
* signal函数不能处理 SIGKILL SIGSTOP信号
* 在父进程中使用signal(SIGCHLD,SIG_IGN),这样子进程退出时会交给系统处理;时解决僵尸进程的惯用手法!
代码示例:
import signal
from time import sleep
# 设置一个闹钟
signal.alarm(5)
# 忽略SIGINT ctrl + c
signal.signal(signal.SIGINT, signal.SIG_IGN)
# 默认方法处理SIGTSTP ctrl + z
signal.signal(signal.SIGTSTP, signal.SIG_DFL)
# 定义函数处理SIGQUIT ctrl + \ 和SIGALARM
def handler(sig,frame):
'''信号处理函数'''
if sig == signal.SIGALRM:
print("收到时钟信号,终止无效!")
elif sig == signal.SIGQUIT:
print("ctrl + \, 退出无效!")
signal.signal(signal.SIGALRM,handler)
signal.signal(signal.SIGQUIT,handler)
while True:
print("waiting for signal")
sleep(2)
给定一定的数量,对多个进程可见,并且多个进程根据信号量的多少确定不同的行为(可用于操作共享的有限资源)
multiprocessing ---> Semaphore()
sem = Semaphore(num)
功能:生成信号量对象
参数: 信号量的初始值
返回值: 信号量对象
sem.acquire() 信号量数量减1 信号量为0时会阻塞
sem.release() 信号量数量加1
sem.get_value() 获取当前信号量的值
示例代码:
from time import sleep
import os
from multiprocessing import Semaphore,Process
#创建信号量对象,设置信号量的初始值为3
sem = Semaphore(3)
def fun():
print("进程 %d 等待信号量"%os.getpid())
# 当信号量为0时,此处会阻塞
sem.acquire() #信号量 -1
print("进程 %d 消耗信号量"%os.getpid())
sleep(3)
print("进程 %d 添加信号量"%os.getpid())
sem.release() #信号量 +1
jobs = []
for i in range(4):
p = Process(target = fun)
jobs.append(p)
p.start()
for i in jobs:
i.join()
#获取信号量数量
print(sem.get_value())
linux进程间通信还可以采用socket本地套接字,socket函数的第一个参数设置为socket.AF_UNIX表示创建本地套接字;使用方法类似于socket网络编程。此处不再赘述。
目的 : 解决对共有资源操作产生的争夺
临界资源 : 多个进程或者线程都能够操作的资源
临界区 : 操作临界资源的代码段
同步 : 是一种合作关系,为完成某个任务,多进程或者多线程之间形成一种协调。按照约定依次执行对临界资源的操作,相互告知相互促进。
互斥 : 互斥是一种制约关系,当一个进程占有临界资源就会进行加锁的操作,此时其他进程就无法操作该临界资源。直到使用的进程进行解锁操作后才能使用。
Python中通过Event事件或Lock锁实现同步互斥机制:
1.Event
multiprocessing ---> Event
e = Event() #创建事件对象
e.wait([timeout]) #事件阻塞
e.set() #当e被set后,e.wait不再阻塞
e.clear() #当e被clear后,e.wait又会阻塞
e.is_set() #事件判断 判断当前事件对象是否被设置
2.Lock
multiprocessing ---> Lock
lock = Lock()
lock.acquire() #上锁
lock.release() #解锁
*上锁状态执行acquire()操作会阻塞
*解锁状态执行acquire()不阻塞
使用上下文管理器实现:
lock = multiprocessing.Lock()
with lock:--->上锁
...
--->with代码段结束即解锁
示例代码1:event
'''本例中:
1.三个进程都要操作共享资源
2.要求必须主进程先操作
3.子进程中谁先操作都可以,但是有一个子进程不能长期阻塞
'''
from multiprocessing import Process,Event
from time import sleep
def wait_event():
print("想操作临界区,但是要等主进程操作完")
# 只有在e.set()之后,此处才结束阻塞
e.wait()
print("主进程操作完了,可以操作",e.is_set())
def wait_event_timeout(sec):
print("也想操作临界区,但是也要等主进程操作完")
# 设置超时时间,sec后若e.set()仍未执行,也可以结束阻塞
e.wait(sec)
print("等不了了,不等了先执行别的",e.is_set())
e = Event()
p1 = Process(target = wait_event)
p2 = Process(target = wait_event_timeout,\
args = (2,))
p1.start()
p2.start()
print("主进程要先操作资源")
sleep(3)
print("主进程操作完毕,set")
e.set()
p1.join()
p2.join()
示例代码2:lock
from multiprocessing import Process,Lock
import sys
from time import sleep
# sys.stdout 为共享资源,所有进程都可以操作
def writer1():
# 上锁
lock.acquire()
for i in range(5):
sleep(1)
sys.stdout.write("人生苦短\n")
# 解锁
lock.release()
def writer2():
# 利用with上下文管理器实现
with lock:
for i in range(5):
sleep(1)
sys.stdout.write("我用Python\n")
#创建锁
lock = Lock()
w1 = Process(target = writer1)
w2 = Process(target = writer2)
w1.start()
w2.start()
w1.join()
w2.join()