1. 守护进程(daemon)
什么是守护进程:一个进程可以守护另一个进程。开启守护进程是为了并发执行某个任务,但这个任务如果伴随着主进程的结束而没有存在的意义,就可以将子进程设为守护进程。
如果b是a的守护进程,a是被守护的进程,如果a挂了,则b也随之结束
比如:qq接收到一个视频文件,于是开启了一个子进程来下载,如果中途退出了,下载任务就没必要继续下去
import time from multiprocessing import Process def task(): print("妃子进宫了") time.sleep(50) print("妃子挂了") if __name__ == '__main__': print("皇帝登基了") p = Process(target=task) p.daemon = True p.start() time.sleep(3) print("皇帝驾崩了")
2. 互斥锁
2.1 什么是互斥锁
出现原因:并发将带来资源的竞争问题,当多个进程要操作同一个资源时,会导致数据错乱的问题
互斥锁:互相排斥的锁,如果这个资源被别人占用,或者说锁了,其他进程就无法使用
【注意】锁 并不是真的把资源锁起来,只是在代码层面上限制了代码不能正常进行
2.2 使用方法
import time import random from multiprocessing import Process,Lock def task1(lock): # 要开始使用了 上锁 lock.acquire() print("123") time.sleep(random.randint(1,2)) print("456") time.sleep(random.randint(1, 2)) print("789") # 用完了就解锁 lock.release() # 注意注意: 不要对同一把执行多出acquire 会锁死导致程序无法执行 一次acquire必须对应一次release def task2(lock): lock.acquire() print("qwe") time.sleep(random.randint(1, 2)) print("rty") time.sleep(random.randint(1, 2)) print("uio") lock.release() def task3(lock): lock.acquire() print("asd") time.sleep(random.randint(1, 2)) print("fgh") time.sleep(random.randint(1, 2)) print("jkl") lock.release() if __name__ == '__main__': lock = Lock() # 想要保住数据安全,必须保住所有进程使用同一把锁,把这一行提到if之外,虽然每次导入 # 都会重新导入并执行,但是子进程一开始就确定了lock的值,所以放外面也行 p1 = Process(target=task1,args=(lock,)) p2 = Process(target=task2,args=(lock,)) p3 = Process(target=task3,args=(lock,)) p1.start() p2.start() p3.start() from multiprocessing import Process,Lock def task1(lock): lock.acquire() print("qwertyui") lock.release() def task2(lock): lock.acquire() print("asddfghj") lock.release() def task3(lock): lock.acquire() print("zxcvbnm") lock.release() if __name__ == '__main__': lock = Lock() p1 = Process(target=task1, args=(lock,)) p2 = Process(target=task2, args=(lock,)) p3 = Process(target=task3, args=(lock,)) p1.start() p2.start() p3.start()
2.3 互斥锁与join的区别
大前提:两者的本质原理都一样,都是将并发编程穿行,从而保证有序
区别:
1. join是固定了执行顺序,会造成父进程等待子进程,锁依然是公平竞争,谁先抢到谁先执行,不影响父进程做其他事
2. join是把进程的任务全部串行,锁可以锁住任意代码,一行也可以,可以自己调整粒度。
粒度越大,效率越低,粒度越小,效率越高
3. 共享内存方式
3.1 IPC(进程间通信Manager)
进程之间相互隔离,当一个进程想要把数据给另一个进程,就要考虑IPC
进程间通信的方式:
1. 管道:只能单向通讯,数据都是二进制
2. 文件:在硬盘上创建共享文件,
缺点:速度快
优点:数据量几乎没有限制
3. socket:编程复杂度较高
4. 共享内存:必须要操作系统来分配
优点:速度快
缺点:数据量不能太大
import random import time from multiprocessing import Process,Lock,Manager def task(data, lock): lock.acquire() # print(data) num = data["num"] time.sleep(0.1) data["num"] = num - 1 lock.release() if __name__ == '__main__': lock = Lock() m = Manager() data = m.dict({"num": 10}) for i in range(10): p = Process(target=task, args=(data, lock)) p.start() time.sleep(2) print(data)
3.2 Queue队列
堆栈:先存储的后取出, 就像衣柜 桶装薯片 先进后出
扩展:函数嵌套调用时 执行顺序是先进后出 也称之为函数栈
调用 函数时 函数入栈 函数结束就出栈
from multiprocessing import Queue q = Queue(3) # 建队列 不指定maxsize 则没有数量限制 q.put(1) q.put(2) q.put(3) print(q.get()) # 1 q.put(4) # 如果容量已经满了,在调用put时将进入阻塞状态 直到有人从队列中拿走数据有空位置 才会继续执行 print(q.get()) print(q.get()) print(q.get()) print(q.get()) # 如果队列已经空了,在调用get时将进入阻塞状态 直到有人从存储了新的数据到队列中 才会继续 #block 表示是否阻塞 默认是阻塞的 # 当设置为False 并且队列为空时 抛出异常 q.get(block=True,timeout=2) # block 表示是否阻塞 默认是阻塞的 # 当设置为False 并且队列满了时 抛出异常 # q.put("123",block=False,) # timeout 表示阻塞的超时时间 ,超过时间还是没有值或还是没位置则抛出异常 仅在block为True有效
4. 生产者消费者模型
4.1 消费者生产者模型是什么
模型:就是解决某个问题套路
产生数据的一方称之为生产者
处理数据的一方称之为消费者
例如: 爬虫 生活中到处都是这种模型
原本,双方是耦合 在一起,消费着必须等待生产者生成完毕在开始处理, 反过来
将双方分开来.一专门负责生成,一方专门负责处理
这样一来数据就不能直接交互了 双方需要一个共同的容器
生产者完成后放入容器,消费者从容器中取出数据
这样就解决了双发能力不平衡的问题,做的快的一方可以继续做,不需要等待另一方
import time import random from multiprocessing import Process,Queue def task1(q): for i in range(10): time.sleep(random.randint(0, 2)) print("正在烧%s盘菜" % i) rose = "%s盘菜" % i q.put(rose) def task2(q): for i in range(10): rose = q.get() print(rose,"正在消费") time.sleep(random.randint(0, 2)) print(rose,"消费完成") if __name__ == '__main__': q = Queue() p1 = Process(target=task1, args=(q,)) p2 = Process(target=task2, args=(q,)) p1.start() p2.start()