1.守护进程
什么是守护进程?
进程是一个正在运行的程序
守护进程也是一个普通进程,意思是一个进程可以守护另一个进程,比如如果b是a的守护进程,a是被守护的进程,如果a进程结束,b进程也会随之结束。
使用场景:
父进程交给了子进程一个任务,子进程在执行过程中并没有执行完毕,但是父进程结束了,那么子进程就没又继续执行的意义了
使用方法:在start 前面加 deamon = Ture
案例:
from multiprocessing import Process import time def task(): print('子进程开启了') time.sleep(5) print('你跟着去吧') if __name__ == '__main__': print('父进程开始了') p = Process(target=task) # 使用daemon方法来守护父进程,如果父进程提前结束了 # 那么子进程也会一起结束 p.daemon = True p.start() time.sleep(2) print('父进程结束了')
2.互斥锁
什么是互斥锁?
互斥锁,意思是互相排斥的锁,在程序中的意思是。如果这个资源已经被锁定了,那么其他的进程则无法使用该资源
PS: 锁,并不是真的把资源锁起来了,只是在代码层面限制你的代码不能执行
为什么需要互斥锁?
并发带来资源的竞争问题:
当多个进程同时要操作同一个资源时,将会导致数据错乱的问题,那么解决这样问题的方案有两种
1. 是用 join ,等子进程结束后再执行其他进程,这样做的效果确实能实现资源竞争的问题,但是却降低了效率,如果这样操作就没有必要开启子进程了,而且本来多个进程之间是公平竞争的,而join方法,就必须按照join的执行顺序来进行,所以这是不合理的
2. 给公共资源加锁
每个进程间是公平的,都是同时竞争,但是如果有一个进程抢到了资源,就会把资源锁住,那么其他的程序就只能等哪个进程释放锁之后才能使用。
锁与join的区别
1. join是固定了执行顺序,不仅会造成父进程等待子进程结束后才能继续执行,而且浪费资源
而锁是公平竞争,谁先抢到谁先执行,父进程依然可以继续执行,不受干扰
2.最主要i的区别
join是把进程的任务全部串行
锁可以锁任意代码,可以自主的调整锁定的粒度
使用方法:
在你需要执行的代码前加 acpuire 方法,代码的后面加 release
案例:
''' 并发将带来资源的竞争问题 当多个进程同时要操作同一个资源时,将会导致数据错乱的问题 ''' def task1(lock): # acquire 将进程上锁(并不是上锁),是让其他进程必须等抢到系统资源 # 的进程执行完毕后才能执行 lock.acquire() print('哒哒哒哒哒哒冒蓝光的加特林') print('biubiubiubiu冒红光的M416') print('砰砰砰砰砰砰冒黄光的手榴弹') # 当进程执行完后要释放锁,让其他进程使用 lock.release() def task2(lock): lock.acquire() print('冒蓝光的加特林') print('冒红光的M416') print('黄光的手榴弹') lock.release() def task3(lock): lock.acquire() print('加特林') print('M416') print('手榴弹') lock.release() if __name__ == '__main__': # 需要先实例化得到一把锁 # 需要注意的是,不要对同一把执行两次acquire,会锁死 # 导致程序无法运行,一此acquire必须对应一次release 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()
3.IPC
IPC = 进程间通讯 (通讯指的是互相交换数据)
进程之间内存是相互隔离的,当一个进程想要把数据给另外一个进程的时候,就需要考虑IPC
进程之间传输数据的方式:
1.管道:只能单向通讯,数据都是二进制(subprocess)
2.文件:在银盘上创建共享文件
缺点: 速度慢
优点: 数据量没有限制
3.socket
编程难度复杂
4. 共享内存
优点: 速度块
缺点: 数据量不能太大
共享内存的方式
Manager类
Manager提供很多数据结构 list dict 等等
Manager所创建出来的数据结构,具备进程间共享的特点
案例:
from multiprocessing import Process,Manager,Lock def task(data, lock): lock.acquire() num = data['num'] data['num'] = num - 1 lock.release() if __name__ == '__main__': # Manager 开启一个共享字典,这个字典子进程与父进程公同拥有 m = Manager() data = m.dict({'num': 10}) lock = Lock() for i in range(10): p = Process(target=task, args=(data, lock)) p.start() p.join() print(data)
PS: 需要强调的是Manager创建的一些数据结构是不带锁的,可能会出现问题
4.Queue队列 *****(重点)
Queue队列帮我们处理了锁的问题,重点
队列是一种特殊的数据结构,先存储的先取出,先进先出
相反的是堆区,先存储的后取出,先进后出
PS:函数嵌套调用时,执行顺序是先进后出的,所以也称之为函数栈
调用函数时,函数入栈,函数结束就出栈
案例:
from multiprocessing import Queue # 创建队列,不指定maxsize 则没有数量限制 q = Queue(3) # 存储元素 q.put('abc') q.put('123') q.put('哈哈哈') print(q.get()) print(q.get()) print(q.get()) # 如果容量已经满了,在调用put时将会进入阻塞状态 # 直到有人从队列中拿走数据有空位置才会继续执行 # 如果队列已经空了,在调用get时将进入阻塞状态, # 直到有人存储了新的数据到队列中,才会继续 # block 表示是否需要进行阻塞,默认是阻塞的,当设置为False,并且队列为空时,则抛出异常 # timeout 表示阻塞的超时时间,超过时间还是没有值或还是没位置则抛出异常,仅在block为True有效 # 此处如果没有值,则会阻塞两秒,如果还没有值,则报异常 # q.get(block=True,timeout=2) # 此处存值时,如果容器满了,则不会阻塞,直接报异常 # q.put('123', block=False)
5.生产消费着模型
生产者:产生数据的一方
消费者:处理数据的一方
生产者与消费者中存在的问题:
生产者和消费者处理速度不平衡,一方快一方慢,导致一方需要等待另一方
双方是耦合在一起的,消费者必须等待生产者生成完毕后才能开始处理,
但是如果消费者消费速度太慢,生产者必须等待其处理完毕才能开始生成下一个数据
解决途径:
将双方分开,生产者专门生产,消费者专门消费,两者不需要等待某一方结束后才能执行
但是这样一来就需要一个共同的容器,生产者完成后放入容器,消费者从容器中取出数据,这样就解决了双方能力不平衡的问题,完成时间快的一方继续完成,无需等待另一方
案例:
from multiprocessing import Process, Queue, Lock import time, random, os # 消费者 def consumer(q): # 假如消费者点了10盘菜 for i in range(10): # 因为吃饭需要时间,所以使用tiem模拟了时间 time.sleep(random.randint(0, 2)) # 通过从Queue容器获取生产者的数据 rose = q.get() print('%s已经吃完了%s' % (os.getpid(), rose)) # 生产者 def producer(q): # 生产者需要烧制10盘菜 for i in range(10): # 因为制作菜需要时间,所以也使用了tiem 模拟了时间 time.sleep(random.randint(0, 2)) print('第%s盘菜烧好了' % i) rose = ('第%s盘菜' % i) # 使用put方法往Queue中添加数据 q.put(rose) if __name__ == '__main__': # 创造一个Queue来存储数据 q = Queue() c = Process(target=consumer,args=(q,)) p = Process(target=producer,args=(q,)) c.start() p.start()