python之进程间通信

进程锁, 信号量 , 事件  

  这些有锁的功能的东西(能够阻塞)是全局的,所有的进程都是可以这些锁的状态的(这些东西的底层都是socket,而且是基于文件的socket通信,跟数据共享不是同一个类型的)

 

同步锁:处理并发出现的相关的问题

同步锁应用
#注意:首先在当前文件目录下创建一个名为db的文件
#文件db的内容为:{"count":1},只有这一行数据,并且注意,每次运行完了之后,文件中的1变成了0,你需要手动将0改为1,然后在去运行代码。
#注意一定要用双引号,不然json无法识别
from multiprocessing import Process,Lock
import json,time,random

def look():
    # f = open('db')
    dic = json.load(open('db'))
    # f.close()

def get():
    dic = json.load(open('db'))
    time.sleep(0.1)
    if dic['count'] >0:
        dic['count'] -= 1
        time.sleep(0.1)
        json.dump(dic,open('db','w',encoding = 'utf-8'))
        print('购票成功呢')
    else:
        print('没票了')
def lo(Lcok):
    look()
    Lcok.acquire()
    get()
    Lcok.release()

if __name__ == '__main__':
    lock = Lock()
    for i in range(10):
        p = Process(target=lo(lock))
        p.start()
View Code

主要架构:在主进程中创建一个Lcok对象,并在需要只能要一个用户在操作的时候,对相应的代码进行加锁

def gg(L):将所锁的对象进行接收

  L.acquire()#某个子进程首先去读到这里的时候,之后所有的子进程在读到这里,就只能干等,第一个##子进程执行完L.release 之后,后面的子进程才能进行操作

  数据操作

  L.release()

if __name == ‘__main__’:

  L = Lock()

将锁的对象以实参的形式传递出去

 使用加锁的形式实现了顺序的执行,但是程序又重新变成串行了,这样确实会浪费了时间,却保证了数据的安全。

 

信号量相当于在进程锁和进程异常中折中的处理,可以自己定义锁的数量

信号量:

import time
from multiprocessing import Process,Semaphore
def fun(i,s):
    s.acquire()
    print('服务%s号'%i)
    time.sleep(1)

    print('%s服务结束'%i)
    time.sleep(1)
    s.release()


if __name__ == '__main__':
    s = Semaphore(4)#相当于某个时刻只能允许4个子进程进行异步的操作
    for i in range(10):
        p = Process(target= fun, args = (i,s))
        p.start()
View Code

应用方式与lock 基本相同 

 

事件

 事件 在主程序在执行过程中需要等待子进行执行后返回某些值的时候会用到
from multiprocessing import Process,Event
e = Event()
#初始有两个状态,默认状态是False Ture/False
print('开始了')
print(e.is_set())#查看当前对象的状态
e.set()#将时间对象的状态该为True

e.clear()#将事件状态改为False

print(e.is_set())#查看当前对象的状态
e.wait()#如果你状态是False 就等待,True就继续执行下面的 命令
print('结束了')
#
View Code

使用例子
import threading


def do(event):
    print('start')

    event.wait()
    print('execute')



event_obj = threading.Event()
for i in range(10):
    t = threading.Thread(target=do, args=(event_obj,))
    t.start()

event_obj.clear()
inp = input('input:')
if inp == '1':
    event_obj.set()
#同一进程内资源的是共享的
事件在线程中的使用

加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。 虽然可以用文件共享数据实现进程间通信,但问题是: 1.效率低(共享数据基于文件,而文件是硬盘上的数据) 2.需要自己加锁处理 进程之间的通信: 队列、管道、数据共享

队列:
因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。 队列和管道都是将数据存放于内存中 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来

进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。队列就像一个特殊的列表,但是可以设置固定长度,并且从前面插入数据,从后面取出数据,先进先出。


守护进程:主进程运行结束之后,作为守护进程的子进程都会直接跟着结束
import time
from multiprocessing import Process
def func1():
    time.sleep(2)
    print('ddd')
if __name__ == '__main__':
    p = Process(target=func1)
    p.daemon  = True
    p.start()
    print('主进程完了')
主进程运行完了之后,作为守护进程的子进程不管是出于何种状态都会直接结束进程
View Code

队列说明
class Queue(object):
    def __init__(self, maxsize=-1):#共享队列的长度
        self._maxsize = maxsize

    def qsize(self):
        return 0

    def empty(self): #判断队列是否为空,
        return False

    def full(self):#判断队列是否满了
        return False

    def put(self, obj, block=True, timeout=None):#往队列里面塞东西,obj是要塞的的元素,block 是塞满是否等待,timeout设置等待的时间
        pass

    def put_nowait(self, obj):#在塞进一个元素obj的时候,如果满了直接报错,不等待
        pass

    def get(self, block=True, timeout=None):#往队列里面取东西,block 队列为空时是否等待,为False是与get_nowait,timeout设置等待超时的时间
        pass

    def get_nowait(self):#去取东西的时候,如果队列为空直接报错,不等待
        pass

    def close(self):
        # 关闭队列,防止队列中加入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,
        # 但将在此方法完成时马上关闭。如果q被垃圾收集,将自动调用此方法。
        # 关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。
        # 例如,如果某个使用者正被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
        pass

    def join_thread(self):
        #不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。
        pass

    def cancel_join_thread(self):#连接队列的后台线程。此方法用于在调用q.close()方法后,等待所有队列项被消耗。默认情况下
        #此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。
        pass
View Code

 

队列 :  Queue的使用
import time
from multiprocessing import Process,Queue
q = Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5)
q.put(6) # 如果队列满了,就会进入等待的状态,
q.get()#如果队列空了,也是会进入到等待的状态
q.full() #判断是否满了 #在同时存在多个子进程的时候不可信
q.empty() #判断是否是空了 # 在同时存在多个子进程的时候不可信
for i in range(5):
    print(q.get())#
    if  q.empty():
        print('完了')
q.get_nowait()  #如果队列空了,不进入等待状态,直接报错功能
q.put_nowait(1)# 队列满了,不进入等待状态,直接报错
View Code

 

 队列在子进程中的简单调用

import time
from multiprocessing import Process,Queue
def func1(q):
    print(q.get())

if __name__ == '__main__':
    q = Queue(5)
    q.put('你好啊')
    p = Process(target=func1,args=(q,))
    p.start()
    print('结束')
View Code

 

高配版 队列 >>> JoinableQueue

说明

class JoinableQueue(multiprocessing.Queue):
    def task_done(self):#调用此方法向表示向队列说明q.get()的取出的值已经被处理完了,#如果调用此方法的次数大于从#队列中删除项目的数量,将引发ValueError异常
        pass

    def join(self):#调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队
#列中的每个取出的元素都返回task_done,之后执行q.join 后面的代码
        pass
View Code

 

生产者与消费者模型

这个模型相当于在消费者和生产者之间添加了一个缓冲区(队列)

低阶说明版(如果同时存在多个消费者就会存在问题)

import time
from multiprocessing import  Process,Queue

def prodect(q):
    for i in range(10):
        time.sleep(0.5)
        q.put('baozi%s'%i)
        print('baozi%s生产完成'%i)
    q.put(False)

def customer(q):
    while 1:
        bao  = q.get()
        if not bao:
            break
        print('%s被吃掉了'%bao)

        time.sleep(1)
def customer2(q):
    while 1:
        bao  = q.get()
        if not bao:
            break
        print('%s被吃掉了'%bao)

        time.sleep(1)


if __name__ == '__main__':
    q  = Queue(10)
    p = Process(target=prodect,args = (q,))
    p1 = Process(target=customer,args = (q,))
    p.start()
    p1.start()
View Code

 

完善版:

import time
from multiprocessing import  Process,JoinableQueue
def prodect(q):
    for i in range(10):
        time.sleep(0.5)
        q.put('baozi%s'%i)
        print('baozi%s生产完成'%i)
        q.join()#等待所有的包子都被消费者吃完所有的包子才结束程序
        print('已经生产完了')

def customer(q):
    while 1:
        bao  = q.get()
        print('%s已经被customer吃掉了'%bao)
        q.task_done()


if __name__ == '__main__':
    q  = JoinableQueue(10)
    p = Process(target=prodect,args = (q,))
    p1 = Process(target=customer,args = (q,))
    p1.daemon = True #消费者设置为主进程的守护进程,主进程运行结束,消费者的进行就结束
    #这样子做存在的问题是主进程运行完代码后直接结束了,导致消费者都没有时间吃包子就结束
    #由于生产者是等到消费者处理完包子才结束的,所有生产者结束的时候,消费者已经空闲的了,
    #所以主进程可以通过等待生产者进程结束才结束进程,就可以避免了消费者没有吃完包子的情况,处理如下:
    p.start()
    p1.start()
    p.join()#处理方式
View Code

 

进程间通信方式2:管道

管道说明

  在进程之间建立一条管道,并返回元组(conn1,conn2) conn1,conn2表示的是管道两端的connnection对象,
# 在默认的情况下,管道是双向的,如果将duplex 设置为False ,conn1只能用于接收,conn2只能用于发送,
# 注意的是必须是在创建和使用管道的Process对象之前调用Pipe方法

# Pipe()方法返回的connection 对象的实例c具有的方法如下
class Connection(object):
    def send(self, obj):#连接发送的对象,obj是与序列化兼容的任意对象
        pass

    def recv(self):#接收c.send 方法返回的 对象,如果连接另一端已经关闭,在也不存在
        #任何的数据,会引发EOFError 异常
        pass

    def fileno(self):#返回连接使用的整数文件描述符
        return 0

    def close(self):#关闭连接,如果c被回收,自动调用此方法
        pass

    def poll(self, timeout=None):#timeout等待数据到达的时间,默认是永久等待
        #如果连接上的数据可用,返回True,
        pass

    def send_bytes(self, buffer, offset=-1, size=-1):
        #通过连接发送字节数据缓冲区,buffer 是支持缓冲区接口的任意对象
        #size是要发送的字节,结果以单条消息的形式发出,使用crecv_bytes方法接收
        pass

    def recv_bytes(self, maxlength=-1):
        #接收send_bytes()发送的一条完整的字节消息
        pass

    def recv_bytes_into(self, buffer, offset=-1):
        #接收一条完整的字节消息,把并把它保存在buffer对象中
        pass

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass


def Pipe(duplex=True):#一个函数
    return Connection(), Connection()
View Code

 

from multiprocessing import Pipe,Process
def func(conn2):
    msg = conn2.recv()
    print('>>>>>',msg)

if __name__ == '__main__':
    conn1,conn2 = Pipe()
    conn1.send('你好啊')
    p = Process(target=func,args=(conn2,))
    p.start()
    print('jiesu')

def Pipe(duplex=True):#默认是全双工的,
    return Connection(), Connection()
# 主要方法:
#     conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
#     conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象
#
#主进程将管道的两端都传送给子进程,子进程和主进程共用管道的两种报错情况,都是在recv接收的时候报错的:
#
#     1.主进程和子进程中的管道的相同一端都关闭了,出现EOFError;
#
#     2.如果你管道的一端在主进程和子进程中都关闭了,但是你还用这个关闭的一端去接收消息,那么就会出现OSError;
#
from multiprocessing import Process,Pipe

def func(con):
    c1,c2 = con
    c1.close()#主进程用conn1发送数据,子进程要用对应的conn2接受,所以讲conn1关闭,不关闭程序会阻塞
    while 1:
        try:#异常处理,出现异常退出
            print(c2.recv())#将conn2接受的数据打印
        except:#说明所有数据已经全部接受,进程会抛出异常
            break

if __name__ == '__main__':
    conn1,conn2 = Pipe()#开启管道
    p = Process(target=func, args=((conn1,conn2),))#将管道的两个返回值以元组形式传给子进程
    p.start()
    conn2.close()#用conn1发送数据,conn2不用,将其关闭
    for i in range(10):
        conn1.send(i)
    conn1.close()#发送完数据后,将conn1关闭

#
#  如果单进程使用管道 : conn1发数据,conn2接受数据. conn2发数据,conn1接受数据.
#
#  如果多进程使用管道 : 父进程conn1发数据,子进程conn2接收数据.
#
#             父进程conn2发数据,子进程conn1接收数据.
#
#             父进程conn1接收数据,子进程conn2发数据.
#
#             父进程conn2接收数据,子进程conn1发数据.
进程间通信方式2:管道

 

from multiprocessing import Pipe,Process,Manager,Lock
#Manager是通过共享进程的方式共享数据。
# Value、Array是通过共享内存的方式共享数据
def func(dic,loc):
    # loc.acquire()
    # dic['num'] -= 1
    # loc.release()
    #加锁的另一种方式
    with loc: #不加锁而操作共享的数据,肯定会出现数据错乱
        dic['num'] -= 1

if __name__ == '__main__':
    m = Manager()
    l = Lock()
    dic  = m.dict({'num':100})#Manager管理的共享数据类型有:Value、Array、dict、list、Lock、Semaphore等等
    # ,同时Manager还可以共享类的实例对象。
    g = []
    for i in range(100):
        p = Process(target=func,args=(dic,loc))
        p.start()
        g.append(p)
    [i.join() for i in g]
    print(dic)
进程间的通信方式3 :数据共享

数据共享的详细内容:https://blog.csdn.net/lechunluo3/article/details/79005910



posted @ 2018-11-27 17:11  好大一个圈  阅读(650)  评论(0编辑  收藏  举报