python多任务编程思想(三)

本文继续python多任务编程思想(一)和 python多任务编程思想(二)讨论python多进程话题,展开python多进程编程中的最后一个知识点----->python进程间通信的方法。

       进程间由于空间独立,资源无法互相直接获取,此时在不同的进程间进行数据传递就需要专门的通信方法。进程间通信的方法包含以下6种,本文将依次进行介绍:

  •     管道
  •     消息队列
  •     共享内存
  •     信号
  •     信号量
  •     套接字

一.  管道通信 Pipe

    在内存中开辟一段内存空间,形成管道结构。管道对多个进程可见,进程可以对管道进行读写操作
        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()

 

你可能感兴趣的:(Python处理并发)