一、 守护进程
主进程创建守护进程:
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则会抛出异常:AssertionError:daemonic processes are not allowed to have children
import os
import time
import multiprocessing import Process
class Myprocess(Process):
def __init__(self,person):
super().__init__()
self.person = person
def run(self):
print(os.getpid(),self.name)
print(‘%s正在和女主播聊天’%self.person)
time.sleep
if __name__==’__main__’:
p = Myprocess(‘太白‘)
p.daemon = True
p.start()
print(‘主进程结束’)
import time
from multiprocessing import Process
def func1():
time.sleep(2)
print(‘xxxxx’)
if __name__==’__main__’:
p = Process(target = func1)
p.daemon = True#守护进程一定要写在start前面
p.start()
print(‘主进程代码运行完了’)
一、 进程同步(锁)
我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制。尽管并发编程让我们能够更加充分利用IO资源,但是也给我们带来了新的问题:进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理
from multiprocessing import Process,Lock
import time,json,random
#查看剩余票数
def search():
dic = json.load(open('db'))
print('剩余票数%s'%dic['count'])
def get():
dic = json.load(open('db'))
time.sleep(0.1)#模拟网络延迟,导致所有人都拿到了这一票
if dic['count'] >0:
dic['count'] -=1
time.sleep(0.2)
json.dump(dic,open('db','w'))
print('购票成功')
def task():
search()
get()
if __name__ == '__main__':
for i in range(3):
p = Process(target=task)
p.start()
from multiprocessing import Process,Lock
import time,json,random
#查看剩余票数
def search():
dic=json.load(open('db')) #打开文件,直接load文件中的内容,拿到文件中的包含剩余票数的字典
print(' 33[43m剩余票数%s 33[0m' %dic['count'])
#抢票
def get():
dic=json.load(open('db'))
time.sleep(0.1) #模拟读数据的网络延迟,那么进程之间的切换,导致所有人拿到的字典都是{"count": 1},也就是每个人都拿到了这一票。
if dic['count'] >0:
dic['count']-=1
time.sleep(0.2) #模拟写数据的网络延迟
json.dump(dic,open('db','w'))
#最终结果导致,每个人显示都抢到了票,这就出现了问题~
print(' 33[43m购票成功 33[0m')
else:
print('sorry,没票了亲!')
def task(lock):
search()
#因为抢票的时候是发生数据变化的时候,所有我们将锁加加到这里
lock.acquire()
get()
lock.release()
if __name__ == '__main__':
lock = Lock() #创建一个锁
for i in range(3): #模拟并发100个客户端抢票
p=Process(target=task,args=(lock,)) #将锁作为参数传给task函数
p.start()
#加锁可以保证多个进程修改同一数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度时慢了,但是牺牲了速度却保证了数据安全
虽然可以用文件共享数据实现进程间的通信:
1, 效率低(共享数据基于文件,而文件时硬盘上的数据)
2, 需要自己加锁处理
因此我们最好寻找一种解决方案能够兼顾1,效率高(多个进程共享一块内存的数据)2,帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来
我们应该尽量避免使用共享数据,尽可能使用消息传递,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可拓展性
IPC通信机制:IPC是inter-process-Communication的缩写,含义为进程通信或者跨进程通信,是指两个进程之间数据交换的过程。IPC不是某个系统所独有的,任何一个操作系统都需要有相应的IPC机制
比如windows上可以通过剪贴板,管道和邮槽等来进行进程间通信,而linux上可以通过命名共享内容,信号量来进行进程间通信。Android它也有自己的进程通信方式,Android构建在Linux上,继承了linux的通信方式。
队列
进程彼此之间相互隔离,要实现进程间通信(IPC),multiprocessing模块支持两种方式:队列和管道,这两种方式都是使用消息传递的。队列就像一个特殊的列表,但是可以设置固定长度,并且从前面插入数据,从后面取出数据,先进先出
Queue(【maxsize】)创建共享的进程队列
参数:maxsize是队列允许的最大项数。如果省略此参数,则无大小限制
底层队列使用管道和锁实现
Queue 的方法介绍:
q = Queue([maxsize])
创建共享的进程队列。Maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。底层队列使用管道和锁定实现。另外,还需要运行支持线程以便队列中的数据传输到底层管道中
Queue的实例q具有以下方法:
q.get([block[,timeout]])
返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。Timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。
q.get_nowait()
同q.get(False)方法
q.put(item[,block[,timeout]])
将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。Timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
q.qsize()
返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常
q.empty()
如果调用此方法时q为空,返回true。如果其他进程或线程正在往队列中添加项目,结果都是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入了新项目
import os
import time
import multiprocessing
# 向queue中输入数据的函数
def inputQ(queue):
info = str(os.getpid()) + '(put):' + str(time.asctime())
queue.put(info)
# 向queue中输出数据的函数
def outputQ(queue):
info = queue.get()
print ('%s%s 33[32m%s 33[0m'%(str(os.getpid()), '(get):',info))
# Main
if __name__ == '__main__':
#windows下,如果开启的进程比较多的话,程序会崩溃,为了防止这个问题,使用freeze_support()方法来解决。知道就行啦
multiprocessing.freeze_support()
record1 = [] # store input processes
record2 = [] # store output processes
queue = multiprocessing.Queue(3)
# 输入进程
for i in range(10):
process = multiprocessing.Process(target=inputQ,args=(queue,))
process.start()
record1.append(process)
# 输出进程
for i in range(10):
process = multiprocessing.Process(target=outputQ,args=(queue,))
process.start()
record2.append(process)
for p in record1:
p.join()
for p in record2:
p.join()
队列是进程安全的:同一时间只能一个进程拿到队列中的一个数据,你拿到了一个数据,这个数据别人就拿不到了。
下面我们来看一个叫做生产者消费者模型的东西:
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力,并且我可以根据生产速度和消费速度来均衡一下多少个生产者可以为多少个消费者提供足够的服务,就可以开多进程等等,而这些进程都是到阻塞队列或者说是缓冲区中去获取或者添加数据。
from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q):
while True:
res=q.get()
# time.sleep(random.randint(1,3))
time.sleep(random.random())
print(' 33[45m%s 吃 %s 33[0m' %(os.getpid(),res))
q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走并执行完了
def producer(name,q):
for i in range(10):
# time.sleep(random.randint(1,3))
time.sleep(random.random())
res='%s%s' %(name,i)
q.put(res)
print(' 33[44m%s 生产了 %s 33[0m' %(os.getpid(),res))
print('%s生产结束'%name)
q.join() #生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理。
print('%s生产结束~~~~~~'%name)
if __name__ == '__main__':
q=JoinableQueue()
#生产者们:即厨师们
p1=Process(target=producer,args=('包子',q))
p2=Process(target=producer,args=('骨头',q))
p3=Process(target=producer,args=('泔水',q))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
c1.daemon=True #如果不加守护,那么主进程结束不了,但是加了守护之后,必须确保生产者的内容生产完并且被处理完了,所有必须还要在主进程给生产者设置join,才能确保生产者生产的任务被执行完了,并且能够确保守护进程在所有任务执行完成之后才随着主进程的结束而结束。
c2.daemon=True
#开始
p_l=[p1,p2,p3,c1,c2]
for p in p_l:
p.start()
p1.join() #我要确保你的生产者进程结束了,生产者进程的结束标志着你生产的所有的人任务都已经被处理完了
p2.join()
p3.join()
print('主')
# 主进程等--->p1,p2,p3等---->c1,c2
# p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据
# 因而c1,c2也没有存在的价值了,不需要继续阻塞在进程中影响主进程了。应该随着主进程的结束而结束,所以设置成守护进程就可以了。