05_进程间通信 IPC

1.进程间的通信方式

    1.磁盘交互: 速度慢,不安全

    2.socket套接字

    3.管道通信(Pipe)

    4.消息队列(Queue, Manager().Queue, JoinableQueue)

    5.共享内存(Value, Array)

    6.信号(os.kill, signal)

    7.信号量(Semaphore)

    8.共享数据(Manager)

2.管道通信-Pipe

    1.概述: 在内存中开辟一块空间,对多个进程可见,通过管道实现多进程通信

    2.语法

from multiprocess import Pipe

fd1, fd2 = Pipe(duplex=True)  # duplex默认为True表示双向管道,设置False则表示单向管道
    返回值: 返回两个管道流对象,表示管道的两端
        如果 duplex=True 则是双向管道则 fd1可读可写,fd2可读可写
        如果 duplex=False 则是单向管道则 fd1只能,读fd2只能写
date = fd1.recv()  # 接收并返回接收的消息(每次接收一条),如果管道没有消息会阻塞等待
fd2.send(data)  # 发送消息,可以是字符串或其他类型,date为要发送的内容,如果没有接收端则管道破裂

    3.示例1

from multiprocessing import Process
from multiprocessing import Pipe
import time
import os

# 创建一个双向管道
fd1, fd2 = Pipe()

# 如果参数为False,则表示创建一个单向管道,此时fd1只等recv,fd2只能send
# fd1, fd2 = Pipe(False)

def fun(name):
    time.sleep(1)
    # 每个子进程都向管道中发送字符串消息
    fd1.send("hello %s" % name)  # 收发支持Python的多种数据类型,数值,字符串,列表等等
    print("fun进程的pid是:%s 父进程的pid是:%s" % (os.getpid(), os.getppid()))


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

# 父进程从管道中取消息
for i in range(5):
    data = fd2.recv()
    print(data)

for i in jobs:
    i.join()
"""执行结果
    fun进程的pid是:24334 父进程的pid是:24333
    hello 0
    hello 1
    fun进程的pid是:24335 父进程的pid是:24333
    hello 2
    fun进程的pid是:24336 父进程的pid是:24333
    hello 3
    fun进程的pid是:24337 父进程的pid是:24333
    hello 4
    fun进程的pid是:24338 父进程的pid是:24333
"""

    4.示例2

from multiprocessing import Pipe
from multiprocessing import Process


def func(con):
    con1, con2 = con
    con1.close()  # 子进程使用con2和父进程通信,所以关闭con1
    while 1:
        try:
            print(con2.recv())  # 当主进程的con1发数据时,子进程要死循环的去接收
        except EOFError:  # 如果主进程的con1发完数据并关闭con1,子进程的con2继续接收时,就会报错,使用try的方式,获取错误
            con2.close()  # 获取到错误,就是指子进程已经把管道中所有数据都接收完了,所以用这种方式去关闭管道
            break


if __name__ == '__main__':
    con1, con2 = Pipe()
    p = Process(target=func, args=((con1, con2),))
    p.start()
    con2.close()  # 在父进程中,使用con1去和子进程通信,所以不需要con2,就提前关闭
    for i in range(10):  # 生产数据
        con1.send(i)  # 给子进程的con2发送数据
    con1.close()  # 生产完数据,关闭父进程这一端的管道

3.消息队列通信-Queue, Manager().Queue, JoinableQueue

    1.消息队列概述
        在内存中开辟队列模型,用来存放消息,任何拥有队列的进程都可以存取消息,消息队列是先进先出
        from queue import Queue  # 是进程内非阻塞队列即线程队列,类似于普通列表
        from multiprocessing import Queue  # 是跨进程通信队列,用于解决多进程间的通信问题
        from multiprocessing import Manager  # 是进程池中各子进程间的通信,使用锁 lock = manager.Queue().Lock()

    2.语法

q = Queue(maxsize=0)  # 创建一个消息队列,返回消息队列对象
    参数: maxsize默认为0表示队列可存放消息,不指定或数量为负值时容量由内存而定,大于0表示队列最多存放多少条消息
q.put(item,[block[, timeout]])  # 将item消息写入队列,当队列满时会阻塞,要存放的消息(字符串,整数,列表)
    可选参数:
        block: 默认为True表示阻塞,这种为False则非阻塞
        timeout: 在block为True时设置超时时间,单位是秒, 例如: q.put("test", True, 3)
            如果block值为默认的True
                没有设置timeout,消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止
                设置了timeout,则会等待timeout秒,若还没空间,则抛出"Queue.Full"异常
            如果block值为False:
                消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常
q.get([block[, timeout]])  # 向队列中取出并返回消息,然后将其从列队中移除,当队列空时会阻塞
    可选参数:
        block: 默认为True表示阻塞,设置为False则非阻塞
        timeout: 在block为True时设置超时时间,单位是秒, 例如 q.get(True, 3)
            如果block值为True
                没有设置timeout,消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止
                设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出"Queue.Empty"异常
            如果block值为False:
                消息列队如果为空,则会立刻抛出"Queue.Empty"异常
q.put_nowait(item)  # 用法相当于 q.put(item, False)
q.get_nowait()  # 用法相当于 q.get(False)
q.full()  # 判断队列是否为满,满则返回True
q.empty()  # 判断队列是否为空,空则返回True
q.qsize()  # 得到当前队列中消息的个数
q.close()  # 关闭队列

    3.进程队列

from multiprocessing import Process
from multiprocessing import Queue

import time
import random


# 写数据进程执行的代码:
def write(q):
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())


# 读数据进程执行的代码:
def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print('Get %s from queue.' % value)
            time.sleep(random.random())
        else:
            break


if __name__ == '__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 等待pw结束:
    pw.join()
    # 启动子进程pr,读取:
    pr.start()
    pr.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    print("")
    print("所有数据都写入并且读完")
"""执行结果
    Put A to queue...
    Put B to queue...
    Put C to queue...
    Get A from queue.
    Get B from queue.
    Get C from queue.
"""

    4.进程池队列

# 进程池中使用队列,修改import中的Queue为Manager
from multiprocessing import Manager
from multiprocessing import Pool

import os
import time


def reader(q):
    print("reader启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    for i in range(q.qsize()):
        print("reader从Queue获取到消息:%s" % q.get(True))


def writer(q):
    print("writer启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    for i in "itcast":
        q.put(i)


if __name__ == "__main__":
    print("(%s) start" % os.getpid())
    q = Manager().Queue()  # 使用Manager中的Queue
    po = Pool()
    po.apply_async(writer, (q,))

    time.sleep(1)  # 先让上面的任务向Queue存入数据,然后再让下面的任务开始从中取数据

    po.apply_async(reader, (q,))
    po.close()
    po.join()
    print("(%s) End" % os.getpid())
"""执行结果
    (24474) start
    writer启动(24476),父进程为(24474)
    reader启动(24477),父进程为(24474)
    reader从Queue获取到消息:i
    reader从Queue获取到消息:t
    reader从Queue获取到消息:c
    reader从Queue获取到消息:a
    reader从Queue获取到消息:s
    reader从Queue获取到消息:t
    (24474) End
"""

    5.进程队列实现生产者消费者模型

# 实现方案一: 生产者生产结束的标识,放到生产者进程中
from multiprocessing import Queue
from multiprocessing import Process


# 生产者
def consumer(q, name):
    while 1:
        info = q.get()
        if info:
            print('%s 拿走了%s' % (name, info))
        else:  # 当消费者获得队列中数据时,如果获得的是None,就是获得到了生产者不再生产数据的标识
            break  # 此时消费者结束即可


# 消费者
def producer(q, product):
    for i in range(20):
        info = product + '的智能手机%s号' % str(i)
        q.put(info)
    q.put(None)  # 让生产者生产完数据后,给消费者一个不再生产数据的标识


if __name__ == '__main__':
    q = Queue(10)
    p_pro = Process(target=producer, args=(q, '中国制造'))
    p_con = Process(target=consumer, args=(q, 'Kali'))
    p_pro.start()
    p_con.start()
"""执行结果
    Kali 拿走了中国制造的手办0号
    Kali 拿走了中国制造的手办1号
    Kali 拿走了中国制造的手办2号
    Kali 拿走了中国制造的手办3号
    Kali 拿走了中国制造的手办4号
    Kali 拿走了中国制造的手办5号
    Kali 拿走了中国制造的手办6号
    Kali 拿走了中国制造的手办7号
    Kali 拿走了中国制造的手办8号
    Kali 拿走了中国制造的手办9号
    Kali 拿走了中国制造的手办10号
    Kali 拿走了中国制造的手办11号
    Kali 拿走了中国制造的手办12号
    Kali 拿走了中国制造的手办13号
    Kali 拿走了中国制造的手办14号
    Kali 拿走了中国制造的手办15号
    Kali 拿走了中国制造的手办16号
    Kali 拿走了中国制造的手办17号
    Kali 拿走了中国制造的手办18号
    Kali 拿走了中国制造的手办19号
"""
# 实现方案二: 生产者生产结束的标识,放到父进程中
from multiprocessing import Queue
from multiprocessing import Process


# 生产者
def consumer(q, name, color):
    while 1:
        info = q.get()
        if info:
            print('%s%s 拿走了%s \033[0m' % (color, name, info))
        else:  # 当消费者获得队列中数据时,如果获得的是None,就是获得到了生产者不再生产数据的标识
            break  # 此时消费者结束即可


# 消费者
def producer(q, product):
    for i in range(10):
        info = product + '的智能手机%s号' % str(i)
        q.put(info)


if __name__ == '__main__':
    q = Queue(10)
    # 多个生产者进程
    p_pro1 = Process(target=producer, args=(q, '中国制造'))
    p_pro2 = Process(target=producer, args=(q, '美国制造'))
    p_pro3 = Process(target=producer, args=(q, '日本制造'))
    # 多个消费者进程
    p_con1 = Process(target=consumer, args=(q, 'Kali', '\033[31m'))
    p_con2 = Process(target=consumer, args=(q, 'Coco', '\033[32m'))

    p_l = [p_con1, p_con2, p_pro1, p_pro2, p_pro3]
    [i.start() for i in p_l]

    # 父进程通过等待生产者进程结束后再发送结束标识
    p_pro1.join()
    p_pro2.join()
    p_pro3.join()
    q.put(None)  # 几个消费者就要接受几个结束标识
    q.put(None)
"""执行结果
    Kali 拿走了日本制造的智能手机0号 
    Coco 拿走了日本制造的智能手机1号 
    Kali 拿走了日本制造的智能手机2号 
    Coco 拿走了日本制造的智能手机3号 
    Kali 拿走了日本制造的智能手机4号 
    Coco 拿走了日本制造的智能手机5号 
    Kali 拿走了日本制造的智能手机6号 
    Coco 拿走了日本制造的智能手机7号 
    Kali 拿走了日本制造的智能手机8号 
    Coco 拿走了日本制造的智能手机9号 
    Kali 拿走了美国制造的智能手机0号 
    Coco 拿走了美国制造的智能手机1号 
    Kali 拿走了美国制造的智能手机2号 
    Coco 拿走了美国制造的智能手机3号 
    Kali 拿走了美国制造的智能手机4号 
    Coco 拿走了美国制造的智能手机5号 
    Kali 拿走了美国制造的智能手机6号 
    Coco 拿走了美国制造的智能手机7号 
    Kali 拿走了美国制造的智能手机8号 
    Coco 拿走了美国制造的智能手机9号 
    Kali 拿走了中国制造的智能手机0号 
    Coco 拿走了中国制造的智能手机1号 
    Kali 拿走了中国制造的智能手机2号 
    Coco 拿走了中国制造的智能手机3号 
    Kali 拿走了中国制造的智能手机4号 
    Coco 拿走了中国制造的智能手机5号 
    Kali 拿走了中国制造的智能手机6号 
    Coco 拿走了中国制造的智能手机7号 
    Kali 拿走了中国制造的智能手机8号 
    Coco 拿走了中国制造的智能手机9号 
"""

    6.进程队列JoinableQueue模块+守护进程实现生产者消费者模型

from multiprocessing import Process,JoinableQueue

q = JoinableQueue()

# q.join()  # 用于生产者,等待 q.task_done的返回结果,通过返回结果,生产者就能获得消费者当前消费了多少个数据
# q.task_done()  # 用于消费者,是指每消费队列中一个数据,就给join返回一个标识

# 假设生产者生产了100个数据,join就能记录下100这个数字,每次消费者消费一个数据,就必须要task_done返回一个标识
# 当生产者(join)接收到100个消费者返回来的标识的时候,生产者就能知道消费者已经把所有数据都消费完了


# 消费者
def consumer(q, name, color):
    while 1:
        info = q.get()
        print('%s %s 拿走了%s \033[0m' % (color, name, info))
        q.task_done()


# 生产者
def producer(q, product):
    for i in range(20):
        info = product + '的智能手机%s号' % str(i)
        q.put(info)
    q.join()  # 记录了生产了20个数据在队列中,此时会阻塞等待消费者消费完队列中所有数据

if __name__ == '__main__':
    q = JoinableQueue(10)
    p_pro1 = Process(target=producer, args=(q, '中国制造'))
    p_con1 = Process(target=consumer, args=(q, 'Kali', '\033[31m'))
    p_con1.daemon = True  # 把消费者进程设为守护进程
    p_con1.start()
    p_pro1.start()
    p_pro1.join()  # 主进程等待生产者进程结束
    # 程序有3个进程,主进程和生产者进程和消费者进程,当主进程执行到p_pro1.join()代码时,主进程会等待生产进程结束
    # 而生产进程中执行到q.join()会等待消费者进程把所有数据消费完,生产者进程才结束
    # 现在的状态就是主进程等待生产者进程结束,生产者进程等待消费者消费完所有数据
    # 所以,把消费者设置为守护进程,当主进程执行完,就代表生产进程已经结束,也就代表消费者进程已经把队列中数据消费完
    # 此时,主进程一旦结束,守护进程也就是消费者进程也就跟着结束,整个程序也就能正常结束了
"""执行结果
     Kali 拿走了中国制造的智能手机0号 
     Kali 拿走了中国制造的智能手机1号 
     Kali 拿走了中国制造的智能手机2号 
     Kali 拿走了中国制造的智能手机3号 
     Kali 拿走了中国制造的智能手机4号 
     Kali 拿走了中国制造的智能手机5号 
     Kali 拿走了中国制造的智能手机6号 
     Kali 拿走了中国制造的智能手机7号 
     Kali 拿走了中国制造的智能手机8号 
     Kali 拿走了中国制造的智能手机9号 
     Kali 拿走了中国制造的智能手机10号 
     Kali 拿走了中国制造的智能手机11号 
     Kali 拿走了中国制造的智能手机12号 
     Kali 拿走了中国制造的智能手机13号 
     Kali 拿走了中国制造的智能手机14号 
     Kali 拿走了中国制造的智能手机15号 
     Kali 拿走了中国制造的智能手机16号 
     Kali 拿走了中国制造的智能手机17号 
     Kali 拿走了中国制造的智能手机18号 
     Kali 拿走了中国制造的智能手机19号 
"""

4.共享内存-Value, Array

    1.概述: 在内存中开辟一段空间存储数据,对多个进程可见,每次写入共享内存的数据会覆盖之前的内容,对内存格式化较少,但存取速度快

    2.语法

from multiprocess import Value
from multiprocess import Array

obj = Value(ctype, obj)  # 开辟共享内存空间,返回一个共享内存对象
    参数:
        ctype: str要转变的c类型(对照ctype表)
        obj: 写入共享内存的初始值
obj.value  # 得到共享内存中的值
obj = Array(ctype, obj)  # 开辟共享内存空间(数组),返回一个共享内存对象
    参数:
        ctype: 要转换的类型
        obj: 存入到共享内存中的数据,obj是一个列表且列表中的数据类型必须一致,obj是正整数则表示开辟一个多大的内存空间

    3.进程间通信-Value

from multiprocessing import Process
from multiprocessing import Value
import time
import random


# 向共享内存中存取
def deposit(money):
    for i in range(100):
        time.sleep(0.01)
        money.value += random.randint(1, 100)


# 向共享内存中取钱
def withdraw(money):
    for i in range(100):
        time.sleep(0.01)
        money.value -= random.randint(1, 50)


def main():
    money = Value("i", 1000)  # "i"在ctype对照表中对应的数据类型是Python的int类型
    de = Process(target=deposit, args=(money,))
    wi = Process(target=withdraw, args=(money,))
    de.start()
    wi.start()
    de.join()
    wi.join()
    print(money.value)  # 本次执行结果money.value的值是3401


if __name__ == "__main__":
    main()

    4.进程间通信-Array

from multiprocessing import Process
from multiprocessing import Array


def func(shm):
    for i in shm:
        print(i, end=" ")  # 子进程中打印结果: 1 2 3 4 5
    shm[0] = 11


def main():
    # 开辟内存共享空间,可容纳5个整数,初始值是[1, 2, 3, 4, 5]
    shm = Array("i", [1, 2, 3, 4, 5])  # "i"在ctype对照表中对应的数据类型是Python的int类型

    # 子内存共享空间开辟一个包含5个整形的空间
    # shm = Array("i", 5)  # 没有初始值遍历shm结果为 0 0 0 0 0 此时可以灵活的修改数据

    p = Process(target=func, args=(shm,))
    p.start()
    p.join()
    print()
    for i in shm:
        print(i, end=" ")  # 主进程中打印结果: 11 2 3 4 5


if __name__ == "__main__":
    main()

5.管道消息队列共享内存对比

对比项 管道 消息队列 共享内存
开辟空间 内存 内存 内存
读写方式 双向/单向 先进先出  操作覆盖内存 
效率 一般 一般
应用  多用于亲缘进程   方便灵活广泛  较复杂
 是否需要互斥机制 

6.信号-os.kill, signal

    1.概述
        1.信号的名字,作用和处理信号都是有系统定义的,一个进程向另一个进程通过信号传递某种讯息
        2.信号的默认处理方法: 系统定义,信号给接收信号的进程带来的行为一般有 终止 暂停 忽略
        3.信号属于异步通信方式,信号的发送不会影响进程的持续执行
        4.命令行下信号操作
            kill -l: 查看信号
            kill -signame PID: 给PID的进程发送一个信号, 例如 kell -9 2332

    2.常用信号

# 默认操作: 终止的信号
SIGHUP: 该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关系
SIGINT: 该信号在用户键入INTR字符(通常是Ctrl+c)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程
SIGQUIT: 该信号和SIGINT类似,但由QUIT字符(通常是'"Ctrl+\"')来控制
SIGILL: 该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段,堆栈溢出时)发出
SIGFPE: 该信号在发送致命的算术运算错误时发出,不仅包括浮点运算错误还包括溢出和除数为0等其他所有的算数的错误
SIGKILL: 该信号用来立即结束程序的运行,并且不能被阻塞,处理和忽略
SIGALRM: 该信号当一个定时器到时的时候发出
SIGABORT: 该信号用于结束进程

# 默认操作: 暂停进程
SIGSTOP: 该信号用于暂停一个进程,并且不能被阻塞,处理和忽略
SIGTSTP: 该信号用于暂停交互进程,用户可以键入SUSP字符(通常是Ctrl+z)发出这个信号

# 默认操作: 忽略
SIGCHLD: 子进程改变状态时,父进程会收到这个信号

    3.发送信号

        语法

import signal

os.kill(pid, sig)  # 向一个进程发送一个信号
    参数:
        pid: 要发送信号的进程PID
        sig: 要发送的信号
signal.alarm(sec)  # 向自身发送一个时钟信号SIGALRM,一个进程中只能同时有一个时钟,后面的时钟时间会覆盖前面的时钟时间
    参数: sec代表时钟秒数

        示例1

import os
import signal

# 向 2332 进程发送SIGKILL信号,杀死2332进程
os.kill(2332, signal.SIGKILL)

        示例2

import time
import signal

# 3秒钟之后向自身发送SIGALRM信号,终止进程
signal.alarm(3)
time.sleep(1)
signal.alarm(5)  # 后面的时钟时间会覆盖前面的时钟时间
while True:
    time.sleep(1)
    print("等待时钟...")

    4.信号处理

        语法

import signal

signal.pause()  # 阻塞等待一个信号的发生
signal.signal(signum, handler)  # 处理一个信号
    使用说明: 是一个异步处理信号的函数,只要执行在进程中就会按照指定方法处理信号,但是不能处理 SIGSTOP 和 SIGKILL 信号
    参数:
        signum: 要处理的信号
        handler: 对该信号的处理方法
            SIG_DFL: 采用默认方法
            SIG_IGN: 忽略这个信号
            func: 回调函数,自定义处理方法
    自定义处理方法格式要求
        def func(sig, frame):  # sig: 接收到的信号;frame: 信号对象
            pass

        示例1

import time
import signal

# 5秒钟之后向自身发送SIGALRM信号,终止进程
signal.alarm(5)

# 采用默认的方法处理SIGALRM信号
signal.signal(signal.SIGALRM, signal.SIG_DFL)

# 采用忽略信号的方法处理SIGALRM信号
# signal.signal(signal.SIGALRM, signal.SIG_IGN)
# signal.signal(signal.SIGINT, signal.SIG_IGN)  # 忽略Ctrl+c终止进程,即进程运行时无法使用Ctrl+c来终止

while True:
    time.sleep(1)
    print("等待时钟...")

        示例2

import time
import signal

# 5秒钟之后向自身发送SIGALRM信号,终止进程
signal.alarm(5)


# 固定格式要求
def handler(sig, frame):
    if sig == signal.SIGALRM:
        print("收到了时钟信号: %s" % sig)
    elif sig == signal.SIGINT:
        print("收到了时钟信号: %s" % sig)


# 通过自定义方法handler处理SIGALRM信号
signal.signal(signal.SIGALRM, handler)
signal.signal(signal.SIGINT, handler)

while True:
    time.sleep(1)
    print("等待时钟...")

    5.使用信号处理僵尸进程
        import signal

        原理: 在父进程中忽略子进程的发送信号
        语法: signal.signal(signal.SIGCHLD, signal.SIG_IGN)

    6.多进程实现信号通信

import multiprocessing
from signal import *
from time import sleep
import os


# 售票员处理信号
def conductor_handler(sig, frame):
    if sig == SIGINT:
        os.kill(os.getppid(), SIGUSR1)
    elif sig == SIGQUIT:
        os.kill(os.getppid(), SIGUSR2)
    elif sig == SIGUSR1:
        print("到站了")
        os._exit(0)


# 子进程中售票员发送信号
def conductor():
    signal(SIGINT, conductor_handler)
    signal(SIGQUIT, conductor_handler)
    signal(SIGUSR1, conductor_handler)

    # 子进程中忽略父进程要处理的信号
    signal(SIGTSTP, SIG_IGN)
    while True:
        sleep(3)
        print("车内开始广播...")


# 司机处理信号
def driver_handler(sig, frame):
    if sig == SIGUSR1:
        print("老司机开车")
    elif sig == SIGUSR2:
        print("老司机以锁死车门")
    elif sig == SIGTSTP:
        os.kill(p_pid, SIGUSR1)


def main():
    p = multiprocessing.Process(target=conductor)
    p.start()
    global p_pid
    p_pid = p.pid

    # 父进程中司机发送信号
    signal(SIGUSR1, driver_handler)
    signal(SIGUSR2, driver_handler)
    signal(SIGTSTP, driver_handler)

    # 父进程忽略子进程要处理的信号
    signal(SIGINT, SIG_IGN)
    signal(SIGQUIT, SIG_IGN)

    p.join()


if __name__ == "__main__":
    main()
"""执行结果
    ~/Desktop/python3/demo $ python3 demo5.py
    车内开始广播...
    车内开始广播...
    ^C老司机开车
    ^C老司机开车
    ^C老司机开车
    车内开始广播...
    ^\老司机以锁死车门
    ^\老司机以锁死车门
    ^\老司机以锁死车门
    车内开始广播...
    车内开始广播...
    ^Z到站了
"""

7.信号量-Semaphore

    1.概述: 给定一定的信号数量,对多个进程可见并且多个进程均可操作,进程根据信号量的多少可以有不同的行为

    2.语法

from multiprocess import Semaphore

sem = Semaphore(num)  # 定义信号量并返回信号量对象,num是给定信号量的初始个数
sem.acquire()  # 将信号量减一,信号量为0时调用次方法会阻塞等待
sem.release()  # 将信号量加一

    3.示例1

import time
import multiprocessing


def func(sem):
    print("进程%s等待信号量" % multiprocessing.current_process())  # current_process获取当前进程对象
    sem.acquire()  # 信号量减1
    print("进程%s消耗信号量" % multiprocessing.current_process())
    time.sleep(2)
    print("进程%s添加信号量" % multiprocessing.current_process())
    sem.release()  # 信号量加1


def main():
    # 创建信号量初始值为3
    sem = multiprocessing.Semaphore(3)
    jobs = list()
    for i in range(4):
        p = multiprocessing.Process(target=func, args=(sem,))
        p.start()
        jobs.append(p)
    for i in jobs:
        i.join()


if __name__ == "__main__":
    main()
"""执行结果
    进程<Process(Process-1, started)>等待信号量
    进程<Process(Process-1, started)>消耗信号量
    进程<Process(Process-2, started)>等待信号量
    进程<Process(Process-2, started)>消耗信号量
    进程<Process(Process-3, started)>等待信号量
    进程<Process(Process-3, started)>消耗信号量
    进程<Process(Process-4, started)>等待信号量
    进程<Process(Process-1, started)>添加信号量
    进程<Process(Process-2, started)>添加信号量
    进程<Process(Process-4, started)>消耗信号量
    进程<Process(Process-3, started)>添加信号量
    进程<Process(Process-4, started)>添加信号量
"""

    4.示例2

from multiprocessing import Process
from multiprocessing import Semaphore
import time
import random

def func(i, sem):
    sem.acquire()
    print('第%s个人进入小黑屋,拿了钥匙锁上门' % i)
    time.sleep(random.randint(3, 5))
    print('第%s个人出去小黑屋,还了钥匙打开门' % i)
    sem.release()


if __name__ == '__main__':
    sem = Semaphore(5)  # 初始化了一把锁5把钥匙,也就是说允许5个人同时进入小黑屋
    # 之后其他人必须等待,等有人从小黑屋出来,还了钥匙,才能允许后边的人进入
    for i in range(20):
        p = Process(target=func, args=(i, sem,))
        p.start()

8.共享数据-Manager

    1.概述
        Python中提供了强大的Manager类,专门用于实现多进程之间的数据共享
        Manager类是数据不安全的,Manager类包含的常用方法和属性与Multiprocessing中其他常用类的方法属性一致
        Manager管理的共享数据类型有: Value, Array, dict, list, Lock, Semaphore等等,同时Manager还可以共享类的实例对象

    2.Manager实现dict功能

from multiprocessing import Manager, Process, Lock


def work(d, lock):
    with lock:  # 不加锁而操作共享的数据,肯定会出现数据错乱
        d['count'] -= 1


if __name__ == '__main__':
    lock = Lock()
    with Manager() as m:
        dic = m.dict({'count': 100})
        p_l = []
        for i in range(100):
            p = Process(target=work, args=(dic, lock))
            p_l.append(p)
            p.start()
        for p in p_l:
            p.join()
        print(dic)

    3.Manager实现list功能

from multiprocessing import Manager, Process


def func(num):
    num[0] -= 1
    print("子进程中num的值是%s" % num)


if __name__ == "__main__":
    m = Manager()
    num = m.list([1, 2, 3])
    p = Process(target=func, args=(num,))
    p.start()
    p.join()
    print("父进程中num的值是%s" % num)

    4.通过Manager进程间共享实例对象

from multiprocessing import Process, Value, Lock
from multiprocessing.managers import BaseManager


class Employee(object):
    def __init__(self, name, salary):
        self.name = name
        self.salary = Value('i', salary)

    def increase(self):
        self.salary.value += 100

    def getPay(self):
        return self.name + ':' + str(self.salary.value)


class MyManager(BaseManager):
    pass


def Manager2():
    m = MyManager()
    m.start()
    return m


MyManager.register('Employee', Employee)


def func1(em, lock):
    with lock:
        em.increase()


if __name__ == '__main__':
    manager = Manager2()
    em = manager.Employee('zhangsan', 1000)
    lock = Lock()
    proces = [Process(target=func1, args=(em, lock)) for i in range(10)]
    for p in proces:
        p.start()
    for p in proces:
        p.join()
    print(em.getPay())
posted @ 2020-08-23 22:31  唐雪成  阅读(236)  评论(0编辑  收藏  举报