Python并发编程5——进程间通信

进程间通信(IPC)

1.必要性: 进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。

  1. 常用进程间通信方法
  • 无名管道
    特点:多用于亲缘关系进程间通信,方向为单向;为阻塞读写;通信进程双方退出后自动消失
    问题:多进程用同一管道通信容易造成交叉读写问题
  • 有名管道
    FIFO,方向为单向(双向需要两个FIFO),以磁盘文件的方式存在;通信双方一方不存在则阻塞
  • 消息队列
    可用于同一主机任意多进程的通信,但其可存放的数据有限,应用于少量的数据传递
  • 共享内存
    可实现通一主机任意进程间大量数据的通信,但多进程对共享内存的访问存在着竞争
  • 信号
  • 信号量
  • 套接字
  • 文件
    文件是进程安全的

同一主机进程间的同步机制:信号量
同一主机进程间的异步机制:信号
多进程共享内存:
最简单的就是文件,文件时进程安全的
若数据量不时很大,可以用python自带的Queue,Pipe, Manager等
若数尽量很大的话,使用中间媒介,如:数据库,redis
网络主机间数据交互:
Socket套接字

管道通信(Pipe)

  1. 通信原理

在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现通信

  1. 实现方法
from  multiprocessing import Pipe

fd1,fd2 = Pipe(duplex = True)
功能: 创建管道
参数:默认表示双向管道
如果为False 表示单向管道
返回值:表示管道两端的读写对象
	如果是双向管道均可读写
	如果是单向管道fd1只读  fd2只写

fd.recv()
功能 : 从管道获取内容
返回值:获取到的数据

fd.send(data)
功能: 向管道写入内容
参数: 要写入的数据

示例

from multiprocessing import Pipe, Process
import os

fd1, fd2 = Pipe()

jobs = []


def send_msg(name):
    fd1.send({name: os.getpid()})


for i in range(5):
    p = Process(target=send_msg, args=(i,))
    jobs.append(p)
    p.start()

for i in range(5):
    data = fd2.recv()
    print(data)

for i in jobs:
    i.join()

消息队列

  1. 通信原理

在内存中建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。

  1. 实现方法
from multiprocessing import Queue
	
	q = Queue(maxsize=0)
	功能: 创建队列对象
	参数:最多存放消息个数
	返回值:队列对象
	
	q.put(data,[block,timeout])
	功能:向队列存入消息
	参数:data  要存入的内容
	block  设置是否阻塞 False为非阻塞
	timeout  超时检测
	
	q.get([block,timeout])
	功能:从队列取出消息
	参数:block  设置是否阻塞 False为非阻塞
	timeout  超时检测
	返回值: 返回获取到的内容
	
	q.full()   判断队列是否为满
	q.empty()  判断队列是否为空
	q.qsize()  获取队列中消息个数
	q.close()  关闭队列

示例:

   from multiprocessing import Queue
    from time import sleep
    from random import randint

    p = Queue()
    
    
    def request():
        for i in range(20):
            x = randint(0, 100)
            y = randint(0, 100)
            q.put((x, y))


    def handle():
        while True:
            sleep(0.5)
            try:
                x, y = q.get(timeout=3)
            except:
                break
            else:
                print("%d +%d = %d" % (x, y, x + y))


    p1 = Process(target=request)

    p2 = Process(target=handle)

    p1.start()
    p2.start()

    p1.join()
    p2.join()
    
--------------------------------------------

共享内存

  1. 通信原理:在内中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前内容。

  2. 实现方法

  • Value:将一个值存放在内存中,
  • Array:将多个数据存放在内存中,但要求数据类型一致
from multiprocessing import Value,Array

obj = Value(ctype,data)
功能 : 开辟共享内存
参数 : ctype  表示共享内存空间类型 'i'表示singed int类型,  'f'表示float类型,  'c'“表示char类型
	data   共享内存空间初始数据
返回值:共享内存对象

obj.value  对该属性的修改查看即对共享内存读写


obj = Array(ctype,data)
功能: 开辟共享内存空间
参数: ctype  表示共享内存数据类型
      data   整数则表示开辟空间的大小,其他数据类型				表示开辟空间存放的初始化数据
返回值:共享内存对象

Array共享内存读写: 通过遍历obj可以得到每个值,直接可以通过索引序号修改任意值。

* 可以使用obj.value直接打印共享内存中的字节串

'''
    Value实例
'''

from multiprocessing import  Value,Process
import time
import random


# 创建共享内存

money = Value('i',5000)


#操作共享内存

def man():
    for i in range(30):
        time.sleep(0.2)
        money.value += random.randint(1,1000)

def girl():
    for i in range(30):
        time.sleep(0.15)
        money.value -= random.randint(100,800)

m = Process(target=man)
g = Process(target=girl)

g.start()
m.start()

m.join()
g.join()

print("一月余额:",money.value)
   '''
        Array实例
    '''

    from multiprocessing import Process,Array

    shm = Array('c',['dd','kk'])

    def fun():
        for i in shm:
            print(i)


    p = Process(target=fun)
    p.start()
    p.join()
  • Manager
    Manager是通过共享进程的方式共享数据,它支持的数据类型比Value和Array更丰富。
#数据类型
value= Manager().Value()  # 值对象
list_ = Manager().list()  # 列表对象
dict_ = Manager().dict()  # 字典对象
array =  Manager().Array ()  # 数组对象
queue = Manager().Queue()  # 队列
 
lock = Manager().Lock()  # 普通锁
rlock = Manager().RLock()  # 可冲入锁
cond = Manager().Condition()  # 条件锁
semaphore = Manager().Semaphore()  # 信号锁
event = Manager().Event()  # 事件锁
 
namespace = Manager().Namespace()  # 命名空间
boundedSemaphore = Manager().BoundedSemaphore()  # 有限制的信号锁

示例:

from multiprocessing import Process, Manager
from time import sleep


class Test:
    def __init__(self):
        self.id = 12


def read(d):
    sleep(5)
    d['1'] = 123
    d['obj'] = Test()


def calculation(manager_dict):
    while 1:
        if manager_dict.get('obj'):
            print('res',manager_dict.get('obj').id)
            break
        else:
            print('...')
            sleep(1)


if __name__ == '__main__':
    p_list = []
    manager_dict = Manager().dict()
    p1 = Process(target=read,args=(manager_dict,))
    p1.start()
    p_list.append(p1)
    p2 = Process(target=calculation, args=(manager_dict,))
    p2.start()
    p_list.append(p2)

    for res in p_list:
        res.join()

这里重点介绍的是Manager().Namespace()。它会开辟一个空间,在这个命名空间中,可以更“随性”使用Python中的数据类型,访问这个空间只需要对象名.xxx即可。像下面这样:

from multiprocessing import Process, Manager
 
def producer(namespace):  # 生产者生产数据
    namespace.name = "zty"
    namespace.info = {"Id": 12345, "Addr": "chengdu"}
    namespace.age = 19
 
def consumer(namespace):
    import time
    time.sleep(1)
    print(namespace.name)
    print(namespace.info)
    print(namespace.age)
 
 
if __name__ == "__main__":
    namespace = Manager().Namespace()
 
    proProcess = Process(target=producer, args=(namespace,))
    conProcess = Process(target=consumer, args=(namespace,))
	...
 
# 输出:
zty
{'Id': 12345, 'Addr': 'chengdu'}
19

不过它有一个缺点:无法直接修改可变类型的数据。拿list举例,即便是在一个子进程中修改了命名空间中列表的值,然而在另一个子进程中获取这个列表,得到的依然是未修改之前的数据。

def producer(namespace):
    namespace.nums[2] = 3  # nums = [5, 1, 3]
 
def consumer(namespace):
    time.sleep(1)
    print(namespace.nums)  # 输出:[5, 1, 2]
 
if __name__ == "__main__":
    namespace = Manager().Namespace()
 
    namespace.nums = [5, 1, 2]
    namespace.alphas = ["z", "t", "y"]
    proProcess = Process(target=producer, args=(namespace,))
    conProcess = Process(target=consumer, args=(namespace,))

    ...

解决方法,更新列表引用(重新赋值):

def producer(namespace):  # 生产者生产数据
    nums = namespace.nums
    nums[2] = 3
    namespace.nums = nums
 
def consumer(namespace):
    time.sleep(1)
    print(namespace.nums)  # 输出:51

本地套接字

  1. 功能 : 用于本地两个程序之间进行数据的收发

  2. 套接字文件 :用于本地套接字之间通信时,进行数据传输的介质。

  3. 创建本地套接字流程

【1】 创建本地套接字

sockfd = socket(AF_UNIX,SOCK_STREAM)

【2】 绑定本地套接字文件

sockfd.bind(file)

【3】 监听,接收客户端连接,消息收发

listen()–>accept()–>recv(),send()

  '''
        发送端
    '''

    from socket import *

    # 确保两边使用相同的套接字文件
    sock_file = "./sock"

    sockfd = socket(AF_UNIX,SOCK_STREAM)
    sockfd.connect(sock_file)

    while True:
        msg = input(">>")
        if not msg:
            break
        sockfd.send(msg.encode())

    sockfd.close()
'''
    接收端
'''
from socket import *
import os

# 确定本地套接字文件
sock_file = "./sock"

# 判断文件是否存储,存在就删除
if os.path.exists(sock_file):
    os.remove(sock_file)


# 创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)

# 绑定本地套接字
sockfd.bind(sock_file)

# 监听,链接
sockfd.listen(3)
while True:
    c,addr = sockfd.accept()
    while True:
        data = c.recv(1024)
        if not data:
            break
        print(data.decode())
    c.close()
sockfd.close()

信号量(信号灯集)

  1. 通信原理
    给定一个数量对多个进程可见。多个进程都可以操作该数量增减,并根据数量值决定自己的行为。

  2. 实现方法

from multiprocessing import Semaphore

sem = Semaphore(num)
功能 : 创建信号量对象
参数 : 信号量的初始值
返回值 : 信号量对象

sem.acquire()  将信号量减1 当信号量为0时阻塞
sem.release()  将信号量加1
sem.get_value() 获取信号量数量
'''
	信号量
'''

from multiprocessing import Semaphore,Process
from time import sleep
import os


#创建信号量
#服务程序最多允许三个进程同时执行事件

sem = Semaphore(3)

def handle():
    print("%d 想执行时间"%os.getpid())
    # 想执行事件,必须获取信号量
    sem.acquire() #减少信号量
    print("%d 开始执行操作"%os.getpid())
    sleep(3)
    print("%d 完成操作"%os.getpid())
    sem.release() #增加信号量
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值