python并发编程之守护进程、互斥锁以及生产者和消费者模型
一、守护进程
主进程创建守护进程
守护进程其实就是'子进程'
一、守护进程内无法在开启子进程,否则会报错
二、进程之间代码是相互独立的,主进程代码运行完毕,守护进程也会随机结束 守护进程简单实例:
from multiprocessing import Process
import time
def task(name): #此时的task为守护进程
print('%s is running' % name) #该行并不会被打印,因为主进程结束,守护进程会随之结束
time.sleep(3) if __name__ == '__main__':
obj = Process(target=task, args=('egon',))
obj.daemon=True #一定要在obj.start()开启之前,将obj设置为守护进程,禁止obj创建子进程,否则就会报错
obj.start() # 发送信号给操作系统开启一个进程(守护进程)
print('主') #并且主进程结束,守护进程也会随之结束
为什么要用守护进程:
两方面理解:
一、进程:为了让父进程的任务能够并发的执行,需要将该任务放到子进程中去
二、守护:是因为子进程中执行的任务,在父进程运行完就没有存在的意义了,就设置成守护进程,会在父进程结束后,守护进程也随之结束
由于计算机的性能的原因,进程打印出的结果可能会有不同的结果:
from multiprocessing import Process
import time
def foo(): #主进程结束,守护进程就会结束
print(123)
time.sleep(1)
print("end123") def bar(): #正常的子进程,父进程会等正常的子进程结束才会结束(父进程可以看到子进程的pid)
print(456)
time.sleep(3)
print("end456") if __name__ == '__main__': p1=Process(target=foo) #把p1做成守护进程
p2=Process(target=bar) #正常的子进程 p1.daemon=True #一定要在开启子进程之前,否则会报错,守护进程会伴随主进程的代码的运行完毕而死掉
p1.start()
p2.start()
print("main-------") #主进程结束,守护进程也随之结束 #正常机器
'''
main-------
456
end456
''' #快一点机器
'''
main------- #申请一个内存空间,然后调用操作系统,将其打印到终端
123 #机器性能好的话,可能在打印的时候,p1进程已经起来了,即向操作系统发送请求后,很快就做出回应开启了子进程
456
end456
''' # 机器更快
'''
123 #当机器的性能在好一点,有可能会出现,p1向操作系统发起开启子进程的请求,迅速做出回应,然后开启子进程,那么123就会优先main先被打印出来
main-------
456
end456
'''
了解知识点
二、互斥锁
进程之间数据不共享,但是共享同一套文件系统,所以访问同一套文件,或同一个打印终端是没问题的
然而共享带来的是竞争,竞争带来的就是错乱无序,所以我们要加锁进行处理
多个进程共享同一个打印终端
'''能不自己处理锁,就尽量不用锁,'''
#进程间通信,一个进程修改后,其他进程能看到修改后的结果
'''互斥所就是把并发变成串行,保证了数据安全,但是牺牲了效率'''
'''锁同一时间锁只能被一个子进程抢,用完了再释放掉,才能被其他进程抢到再用'''
"""互斥锁可以将要执行代码的(部分共享的数据)变成串行,而join是将要执行代码所有部分变成串行""" # 互斥锁:
#强调:必须是lock.acquire()一次,然后 lock.release()释放一次,才能继续lock.acquire(),不能连续的lock.acquire() # 互斥锁vs join的区别一:
# 大前提:二者的原理都是一样,都是将并发变成串行,从而保证有序
# 区别:join是按照人为指定的顺序执行,而互斥锁是所以进程平等地竞争,谁先抢到谁执行 # 没加锁之前,打印会变的错乱
# 并发运行,效率高,但竞争同一打印终端,带来了打印错乱
from multiprocessing import Process
import time,random def task1(): #为每一个进程加上一把锁,这样就会有和join一样的效果,变成了串行
print('task1:名字是egon')
time.sleep(random.randint(1,3))
print('task1:性别是male')
time.sleep(random.randint(1,3))
print('task1:年龄是18') def task2():
print('task2:名字是alex')
time.sleep(random.randint(1,3))
print('task2:性别是male')
time.sleep(random.randint(1,3))
print('task2:年龄是78') def task3():
print('task3:名字是lxx')
time.sleep(random.randint(1,3))
print('task3:性别是female')
time.sleep(random.randint(1,3))
print('task3:年龄是30') if __name__ == '__main__':
p1=Process(target=task1)
p2=Process(target=task2)
p3=Process(target=task3) p1.start()
p2.start()
p3.start() '''
运行结果:
task1:名字是egon
task3:名字是lxx
task2:名字是alex
task1:性别是male
task3:性别是female
task2:性别是male
task1:年龄是18
task3:年龄是30
task2:年龄是78
'''
并发运行,提升了效率,但是共享同一个打印终端,造成打印错乱
from multiprocessing import Process,Lock
import time,random
mutex=Lock()
# 为每一个进程加锁,就可以让其变为串行,牺牲了效率,但保证了数据安全
def task1(lock): #为每一个进程加上一把锁,这样就会有和join一样的效果,变成了串行
lock.acquire() #
print('task1:名字是egon')
time.sleep(random.randint(1,3))
print('task1:性别是male')
time.sleep(random.randint(1,3))
print('task1:年龄是18')
lock.release() def task2(lock):
lock.acquire()
print('task2:名字是alex')
time.sleep(random.randint(1,3))
print('task2:性别是male')
time.sleep(random.randint(1,3))
print('task2:年龄是78')
lock.release() def task3(lock):
lock.acquire()
print('task3:名字是lxx')
time.sleep(random.randint(1,3))
print('task3:性别是female')
time.sleep(random.randint(1,3))
print('task3:年龄是30')
lock.release() if __name__ == '__main__':
p1=Process(target=task1,args=(mutex,))
p2=Process(target=task2,args=(mutex,))
p3=Process(target=task3,args=(mutex,)) # p1.start()
# p1.join()
# p2.start()
# p2.join()
# p3.start()
# p3.join() p1.start()
p2.start()
p3.start()
加锁:牺牲了效率,但是避免了竞争,打印到同一个终端不会错乱
多个进程共享同一份文件--------模拟抢票
通过文件:db.json来模拟数据库
文件内容:
{"count":1}
# 共享一份数据
# 文件db.json内容:
# {'count':1}
import json
import time
import random
import os
from multiprocessing import Process
'''大家抢的是同一份数据,先有查票,然后购票,先在客户端减1,在发送到服务端减1'''
# 查票应该并发
def search():
time.sleep(random.randint(1,3)) #模拟网络延时
with open('db.json','r',encoding='utf-8') as f:
dic=json.load(f)
print('%s 剩余票数:%s' %(os.getpid(),dic['count'])) #用pi进行标识 # 购票应该一个一个来,才不至于购票时所有人都减1
def get():
with open('db.json','r',encoding='utf-8') as f: #之前查看的可能不准确,所以再打开一次
dic=json.load(f)
time.sleep(random.randint(1, 3))
if dic['count'] > 0:
dic['count']-=1 #在客户端的内存里减1,但要把他刷新到服务端的文件中去
time.sleep(random.randint(1,3)) #往文件中刷也要有一个网络延迟
with open('db.json','w',encoding='utf-8') as f:
json.dump(dic,f)
print('%s 购票成功' %os.getpid()) def task():
search() #先查票,
get() #在购票 if __name__ == '__main__':
for i in range(10): #网络延迟已经够把10个进程都创建完
p=Process(target=task)
p.start()
# p.join() #加上join后变为串行,但是此时的问题是大家要排队来查票,保证了数据安全,但是效率变低了,因为查票和购票都变成了串行 # 打印结果:
'''
12888 剩余票数:1
10352 剩余票数:1
10440 剩余票数:1
228 剩余票数:1
10736 剩余票数:1
7032 剩余票数:1
8580 剩余票数:1
2712 剩余票数:1
4248 剩余票数:1
2332 剩余票数:1
12888 购票成功
10736 购票成功
228 购票成功
10352 购票成功
10440 购票成功
4248 购票成功
8580 购票成功
2332 购票成功
7032 购票成功
2712 购票成功 '''
并发运行效率高,但是共享同一份文件,写入数据库是错乱
"""
抢票的正确思路是:
查看票数应该是并发进行,提升效率
购票时应加锁,让其串行,虽然降低了效率,但是保证了数据安全即修改时不会导致错乱
"""
import json
import time
import random
import os
from multiprocessing import Process,Lock
'''大家抢的是同一份数据,先有查票,然后购票,先在客户端减1,在发送到服务端减1'''
mutex=Lock()
# 互斥锁vs join的区别一:
# 互斥锁可以让一部分代码(只修改共享数据的代码)变成串行,而join只能将代码整体串行
#查票
def search():
time.sleep(random.randint(1,3)) #模拟网络延时
with open('db.json','r',encoding='utf-8') as f:
dic=json.load(f)
print('%s 剩余票数:%s' %(os.getpid(),dic['count'])) #用pi进行标识
#购票
def get():
with open('db.json','r',encoding='utf-8') as f: #之前查看的可能不准确,所以再打开一次
dic=json.load(f)
time.sleep(random.randint(1, 3))
if dic['count'] > 0:
dic['count']-=1 #在客户端的内存里减1,但要把他刷新到服务端的文件中去
time.sleep(random.randint(1,3)) #往文件中刷也要有一个网络延迟
with open('db.json','w',encoding='utf-8') as f:
json.dump(dic,f)
print('%s 购票成功' %os.getpid()) def task(lock):
search() #先查票,(是一个单独的行为,等你查完票在取购票的时候,可能票已经被抢完了) # 购票变成串行,对get即购票进行加锁处理
lock.acquire() #mutex=Lock().acquire()---------互斥所不能连续的acquire,必须释放掉才能在acquire,(有种独享的感觉)
get() #在购票
lock.release() #mutex=Lock().release()--------(释放掉锁,这把锁可以在被其他人抢到) if __name__ == '__main__':
for i in range(10): #网络延迟已经够把10个进程都创建完
p=Process(target=task,args=(mutex,))
p.start()
# p.join()
加锁:购票行为由并发变为串行,降低了效率,但是保证了数据安全
总结:
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理 #因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
三、IPC通信机制
进程间的通信就是通过队列或管道来实现的,而队列本质就是管道+锁,所以在这我们只研究队列
进程之间通信必须找到一种介质,该介质必须满足
1、是所有进程共享的
2、必须是内存空间
附加:帮我们自动处理好锁的问题
"""
对列:是管道+锁实现的(管道本身就是一个共享的数据,但是管道没有加锁的处理,)----为了实现进程间通信的
1、队列是共享的空间
2、占用的是内存空间
3、自动帮我们处理好锁定问题
"""
from multiprocessing import Queue
'''参数是3表明队列最多只能放三次数据。我们通过put放数据,通过get取数据'''
q=Queue(3) #队列占用的是内存空间。-1代表队列没有限制,但是不能放大数据,应该往队列中放数据量比较小的消息数据
q.put('first') #firs是放的对象
q.put({'second':None})
q.put('三')
#
# # q.put(4) #阻塞,------放满了就不能再放了,再放就会出现阻塞状态,这是因为有自动加锁的处理机制导致阻塞的,要想在放需取出数据才可继续再放
print(q.get()) #取数据,先进先出,先放进去的先被取出来
print(q.get())
print(q.get())
print(q.get()) #第四次在取会处于阻塞状态,这是因为队列自带锁处理机制,要想再取数据,需要向队列中放数据,否则一直处于阻塞状态
队列主要方法:
q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。 q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常. q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False) q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
# 先进先出
from multiprocessing import Queue
q=Queue(5)
q.put('first',block=False) #等价于:q.put_nowait('first)---------block=False,加超时时间没有意义,以为满了或者去空就会报错,此时和超时时间已经没有关系,无论超时时间等于多少,都会立刻报错,没有等的过程
q.put('second',block=False)
q.put('third',block=False)
q.put('fourth',block=False) #不加锁,第四次队列放满了,不会被阻塞,而是直接抛出异常 q.put('first',block=True) #默认锁,满了就不让往里面放了
q.put('second',block=True) #超时时间只在block=True才有意义
q.put('third',block=True)
q.put('fourth',block=True,timeout=3) #加锁,timeout超时时间,第四次放已经满了,在放3秒后将抛出异常,如果没有放满,3秒后不会抛出异常 print(q.get(block=False)) #等价于:q.get_nowait()
print(q.get(block=False))
print(q.get(block=False))
print(q.get(block=False)) #不加锁,第四次队列取空了,不会被阻塞,而是直接抛出异常 print(q.get(block=True))
print(q.get(block=True))
print(q.get(block=True)) #超时时间只在block=True才有意义
print(q.get(block=True,timeout=2)) #加锁,第四次取已经去空,2秒后将抛出异常,如果没有取空,2秒后不会抛出异常
队列其他方法应用
四、生产者和消费者模型
# # 终极版:
import time
import random
from multiprocessing import Process,JoinableQueue #(队列空了就就执行join操作) def consumer(name,q): #
while True: #源源不断的取是一个循环的过程
res=q.get()
time.sleep(random.randint(1,3)) #模拟经过一段时间处理完数据
print('\033[46m消费者===》%s 吃了 %s\033[0m' %(name,res))
q.task_done() def producer(name,q,food):
for i in range(5):
time.sleep(random.randint(1,2)) #模拟生产者生产数据的时间
res='%s%s' %(food,i)
q.put(res) #数据生产好了,不断的往队列里面放,供消费者消费
print('\033[45m生产者者===》%s 生产了 %s\033[0m' %(name,res)) if __name__ == '__main__':
#1、共享的盆
q=JoinableQueue() #中间的介质,不知道放多少个,所以不用写限制 '''生产者和消费者都可以有多个'''
#2、生产者们
p1=Process(target=producer,args=('egon',q,'包子'))
p2=Process(target=producer,args=('刘清政',q,'泔水'))
p3=Process(target=producer,args=('杨军',q,'米饭')) #3、消费者们,消费者还没结束,,设置为守护进程
c1=Process(target=consumer,args=('alex',q))
c2=Process(target=consumer,args=('梁书东',q))
c1.daemon=True #一定要在进程开启之前设置
c2.daemon=True p1.start()
p2.start()
p3.start()
c1.start()
c2.start() p1.join()
p2.join()
p3.join() q.join() #等待队列被取干净
#主进程代码运行完毕-----队列中的数据被取干净----消费者没有存在的意义了----设置为守护进程
print('主')
python并发编程之守护进程、互斥锁以及生产者和消费者模型的更多相关文章
- python并发编程之多进程1--(互斥锁与进程间的通信)
一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理. 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行 ...
- python并发编程之多进程1互斥锁与进程间的通信
一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理. 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行 ...
- 并发编程~~~多线程~~~守护线程, 互斥锁, 死锁现象与递归锁, 信号量 (Semaphore), GIL全局解释器锁
一 守护线程 from threading import Thread import time def foo(): print(123) time.sleep(1) print('end123') ...
- 9 并发编程-(线程)-守护线程&互斥锁
一 .守护线程 无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁 需要强调的是:运行完毕并非终止运行 1.对主进程来说,运行完毕指的是主进程代码运行完毕 2.对主线程来说,运行完毕 ...
- python 并发编程 多进程 守护进程
一 守护进程 主进程创建子进程目的是:主进程有一个任务需要并发执行,那开启子进程帮我并发执行任务 主进程创建子进程,然后将该进程设置成守护自己的进程 关于守护进程需要强调两点: 其一:守护进程会在主进 ...
- 4 并发编程-(进程)-守护进程&互斥锁
一.守护进程 主进程创建子进程,然后将该进程设置成守护自己的进程,守护进程就好比崇祯皇帝身边的老太监,崇祯皇帝已死老太监就跟着殉葬了. 关于守护进程需要强调两点: 其一:守护进程会在主进程代码执行结束 ...
- python并发编程之多进程1-----------互斥锁与进程间的通信
一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理. 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行 ...
- 守护进程,互斥锁,IPC,队列,生产者与消费者模型
小知识点:在子进程中不能使用input输入! 一.守护进程 守护进程表示一个进程b 守护另一个进程a 当被守护的进程结束后,那么守护进程b也跟着结束了 应用场景:之所以开子进程,是为了帮助主进程完成某 ...
- python 之 并发编程(守护进程、互斥锁、IPC通信机制)
9.5 守护进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就立即终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic process ...
随机推荐
- 2018年阿里巴巴关于java重要开源项目汇总
1.分布式应用服务开发的一站式解决方案 Spring Cloud Alibaba Spring Cloud Alibaba 致力于提供分布式应用服务开发的一站式解决方案.此项目包含开发分布式应用服务的 ...
- 为什么php+apache本地站点访问超级慢
/etc/hosts中必然有一行为127.0.0.1,作用是什么呢? 特点: (1)127.0.0.1不光是unix系统,linux也好,windows也好,都会有这个循回地址的.(2)在IP地址的规 ...
- 面向对象程序设计_Task5_Calculator1.5.0
The 3rd part of the Calculator program _ FILE I/O 题目链接:第五次作业(计算器第三步) github链接:Calculator_1.5.0 第五次作业 ...
- Avito Code Challenge 2018
第一次打CF,很菜,A了三道水题,第四题好像是是数位DP,直接放弃了.rateing从初始的1500变成了1499,还是绿名,这就很尴尬.之后觉得后面的题目也没有想象的那么难(看通过人数)过两天吧剩下 ...
- SQL语句.md
数据库操作 create mysql> create database study_2; Query OK, 1 row affected (0.00 sec) mysql> show c ...
- hdu 4803 Poor Warehouse Keeper(贪心+数学)
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/u011328934/article/details/26005267 题目链接:hdu 4803 P ...
- 1051. [HAOI2006]受欢迎的牛【强连通分量】
Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎. 这 种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也 ...
- 【LGP4886 】快递员
题目 好秒啊,真是一道神仙的点分治 于是我们来一个暴力的\(O(nlog^2n)\)的暴力统计吧 考虑计算每一个点作为快递中心时的答案 我们考虑在这个点成为分治重心时计算这个贡献 把这个贡献分成两部分 ...
- 《Java程序设计》第12周课堂实践总结
<Java程序设计>第12周课堂实践总结 实践一 教材代码检查-p98 要求 修改教材P98 Score2.java, 让执行结果数组填充是自己的学号: 提交在IDEA或命令行中运行结查截 ...
- 20145203 盖泽双《Java程序设计》第一周的学习总结
20145203 盖泽双<Java程序设计>第一周学习总结 教材学习内容总结 第一章 1.Java是一门完全面向对象,安全可靠,与平台无关的编程语言. 2.Java现由Java SE.Ja ...