目录
第五十三篇 并发编程之多进程续
一、守护进程
1.守护进程的特性
1.守护进程会在主进程代码执行结束后就终止(而守护进程结束不会影响主进程的执行)
2.守护进程内无法再启动子进程,否则会抛出异常:AssertionError: daemonic processes are not allowed to have children
from multiprocessing import Process
import time
import random
class MyProcess(Process):
def __init__(self, name):
self.name = name
super().__init__()
def run(self):
print(self.name,'start')
time.sleep(random.randrange(1,4))
print(self.name,'end')
if __name__ == '__main__':
p = MyProcess('king')
p.daemon = True
print('主进程 start')
p.start()
time.sleep(3)
print('主进程 end')
二、进程安全问题
1.进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的
2.但是由于共享会带来资源的竞争,竞争的结果就是错乱,所以就出现了进程安全的问题,也就是合理有序的共享有限资源的问题
3.解决方法:
1.直接使用join函数:所有代码全都串行(还不如不开进程),多个进程之间原本公平进制,join则会强行规定执行顺序
2.互斥锁:原理就是将要操作公共资源的代码锁起来,以保证同一时间只能有一个进程在执行这部分代码
三、互斥锁
1.同步的概念
同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。"同"字从字面上容易理解为一起动作,其实不是,"同"字应是指协同、协助、互相配合。如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B执行,再将结果给A;A再继续操作
2.锁的原理
1.将要操作公共资源的代码锁起来, 以保证同一时间只能有一个进程在执行这部分代码
2.互相排斥的锁
3.优点:可以仅将部分代码串行
4.注意:必须保证锁只有一把
3.锁的应用
1.加锁解决了安全问题,带来了效率减低的问题
2.锁其实只是给执行代码加了限制,本质是一个标志(True或False)
3.如何使得即保证安全又提高效率:
- 锁的粒度(粒度指的是被锁住的代码的多少),粒度越大锁住的就越多,效率也就越低
from multiprocessing import Process, Lock
import random
import time
def task1(mutex):
for i in range(10000):
print(1)
mutex.acquire()
time.sleep(random.random()) # 假如随机睡眠,是为了表现两个进程可能同时运行的情况
print('...task1 start...')
time.sleep(random.random())
print('...task1 continue...')
time.sleep(random.random())
print('...task1 end...')
mutex.release()
def task2(mutex):
for i in range(10000):
print(2)
mutex.acquire()
time.sleep(random.random())
print('*** task2 start...')
time.sleep(random.random())
print('*** task2 continue...')
time.sleep(random.random())
print('*** task2 end...')
mutex.release()
if __name__ == '__main__':
mutex = Lock()
p1 = Process(target=task1, args=(mutex,))
p2 = Process(target=task2, args=(mutex,))
p1.start()
p2.start()
5.抢票
def show():
with open('db.json', 'r') as fr:
data = json.laod(fr)
print(data['count'])
def buy():
with open('db.json', 'r') as fr:
data = json.load(fr)
if data['count'] > 0:
data['count'] -= 1
with open('db.json', 'w') as fw:
json.dump(data, fw)
print('ok')
def task(mutex):
show()
mutex.acquire()
buy()
mutex.release()
if __name__ == '__main__':
mutex = Lock()
for i in range(2):
p = Process(target=task, args=(mutex,))
p.start()
6.总结
- 1. 通过multiprocessing导入Lock类
- 2.进程调用锁的acquire()方法,锁就会进入“locked”状态
- 3.调用release()方法,锁进入“unlocked”状态
IPC
manager和队列都是基于共享内存而实现的
四、Manager的使用
作用:创建进程间同步的容器
缺点:没有处理安全问题,并不常用
from multiprocessing import Process,Manager,Lock
import time
def task(data,lock):
lock.acquire()
num = data[0]
time.sleep(0.2)
data[0] = num - 1 # data改变,syncdict的值也会改变
lock.release()
print("子 over")
if __name__ == '__main__':
d = [100]
m = Manager() # 创建一个管理器
syncdict = m.dict(d) # 让管理器创建一个进程同步的字典
# syncdict = m.list(d) # sync 同步
# 创建一个锁
lock = Lock()
ps = []
for i in range(10):
#
p = Process(target=task,args=(syncdict,lock))
p.start()
ps.append(p)
for p in ps: p.join()
print(d) # 不变
print(syncdict) # 改变,和子进程中的同步
五、进程队列
1.Queue 翻译为队列 是一种特殊的容器
2.特殊之处在于存取顺序为先进先出
3.作用:可以帮我们完成进程间通讯
from multiprocessing import Queue
q = Queue(2) # 创建队列 并且同时只能存储2个元素
q.put(1)
q.put(2)
# q.put(3,block=True,timeout=3) # 默认是阻塞的 当容器中没有位置了就阻塞 直到有人从里面取走元素为止
print(q.get())
print(q.get())
print(q.get(block=True,timeout=3))# 默认是阻塞的 当容器中没有位置了就阻塞 直到有人存入元素为止
# block有方块的意思,也有阻塞的意思
六、函数调用栈
1.也是一种特殊的容器
2.存取顺序为先进后出
3.调用函数时,称之为函数入栈(也叫压栈)
4.函数执行结束时,称之为函数出栈
七、处理万能异常
import traceback
try:
raise Exception('1/0')
except Exception as e:
traceback.print_exc() # 可以追踪到具体的出错代码
print(e)
print('可以执行...') # 捕捉了异常之后,后面的代码也可以正常执行
八、生产者消费模型
模型就是套路、模板(解决某种固定问题的固定套路)