看了林海峰老师的视频总结:
基于上一篇文章讲解了进程的概念和实现方法,接下看为大家介绍有关进程更详细的知识点及方法
下面链接为上一篇文章的链接
晚上劳累来看看python多进程吧(详细)~_m0_51734025的博客-CSDN博客
一、 进程对象即及其它方法
首先大家要知道进程间数据是相互隔离的。
'''
# 方法介绍
1 p.start():启动进程,并调用该子进程中的p.run()
2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
3 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
4 p.is_alive():判断当前进程是否存活,如果p仍然运行,返回True
5 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
# 属性介绍
1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2 p.name:进程的名称
3 p.pid:进程的pid
4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
'''
二、简单补充知识之僵尸进程与孤儿进程
1、僵尸进程:
死了但是没有死透。
例如当你开设了子进程后,该进程死后不会立刻释放占用的进程号,因为我要让父进程能够查看到它开设的子进程的一些基本信息,如占用pid,运行时间.....。所有的进程都会进入僵尸进程。当父进程等待子进程运行结束或父进程调用join方法或子进程正常死亡会回收子进程占用的pid号
2、孤儿进程:
子进程存活,父进程意外死亡。操作系统会开设一个“儿童福利院”专门管理孤儿进程回收相关资源
# 小案例
from multiprocessing import Process,current_process
import time
import os
def tast():
print('%s is running'%current_process().pid) # 查看当前进程的进程号pid(具有唯一标识)
print('%s is running'%os.getpid())
print('子进程的主进程号:%s'%os.getppid())
time.sleep(2)
if __name__ == '__main__':
p=Process(target=tast)
p.start()
print('主进程',current_process().pid)
p.terminate() # 杀死当前进程(告诉操作系统去执行,但是需要一定的时间,而代码的运行速度极快,不会立马杀死)
print(p.is_alive())# =>True
time.sleep(0.1)
print(p.is_alive())# 判断当前进程是否存活 =>False
print('主进程',os.getpid())
print('父进程',os.getppid()) #获取父进程pid号
'''
运行结果:
主进程 1736
True
False
主进程 1736
父进程 16348
'''
三、守护进程(可以理解为同时死亡同时存活)
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
# 小案例
from multiprocessing import Process
import time
def task(name):
print('%s 正在活着'%name)
time.sleep(2)
print('%s 正在死亡'%name)
if __name__ == '__main__':
p=Process(target=task,args=('egon',))
# 也可写成
# p=Process(target=task,kwargs={
# 'name':'egon'
# })# 类似=>task(name='egon')
p.daemon = True # 将进程p设置为守护进程(一定要放在start方法上面才有效,否则会直接报错)
p.start()
print('主进程正在死亡')
'''
运行结果:
主进程正在死亡
'''
# 若不将进程p设置为守护进程
from multiprocessing import Process
import time
def task(name):
print('%s 正在活着'%name)
time.sleep(2)
print('%s 正在死亡'%name)
if __name__ == '__main__':
p=Process(target=task,args=('egon',))
p.start()
print('主进程正在死亡')
'''
运行结果:
主进程正在死亡
egon 正在活着
egon 正在死亡
'''
四、互斥锁:
多个进程操作同一份数据的时候,会出现数据错乱的问题
针对上述问题,解决方式就是加锁处理:将并发变成串行,牺牲效率但是保证了数据的安全
# 模拟抢票
from multiprocessing import Process,Lock
import json
import time
import random
# 查票
def search(i):
# 文件操作读取票数
with open('data.json', 'r', encoding='utf-8')as f:
dic = json.load(f)
print('用户%s查询余票:%s' % (i, dic.get('ticket_num')))
# 买票(先查票,再买票)
def buy(i):
# 先查票
with open('data.json', 'r', encoding='utf-8')as f:
dic = json.load(f)
# 模拟网络延迟
time.sleep(random.randint(1, 3))
# 判断当前是否有票
if dic.get('ticket_num') > 0:
# 修改数据库,买票
dic['ticket_num'] -= 1
# 写入数据库
with open('data.json', 'w', encoding='utf-8')as f:
json.dump(dic, f)
print('用户%s买票成功' % i)
else:
print('用户%s买票失败' % i)
# 整合两个函数
def run(i,mutex):
search(i)
# 给买票环节加锁处理
# 抢锁
mutex.acquire()
buy(i)
# 释放锁
mutex.release()
if __name__ == '__main__':
# 在主进程中生成一把锁,让所有的子进程抢,谁先抢到谁先买票
mutex=Lock()
for i in range(1, 10):
p = Process(target=run, args=(i,mutex))
p.start()
'''
注意:
1、锁不用轻易的使用,容易造成死锁现象(我们写代码一般不会用到,都是内部封装好了的)
2、锁只在处理数据的部分加来保证数据安全(只在争抢数据的环节加锁处理即可)
'''
# data.json文件内容如下:
{"ticket_num": 0}
'''
运行结果:
用户2查询余票:1
用户1查询余票:1
用户4查询余票:1
用户5查询余票:1
用户3查询余票:1
用户6查询余票:1
用户8查询余票:1
用户9查询余票:1
用户7查询余票:1
用户2买票成功
用户1买票失败
用户4买票失败
用户5买票失败
用户3买票失败
用户6买票失败
用户8买票失败
用户9买票失败
用户7买票失败
'''
五、队列介绍:
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
队列=管道+锁
# 队列基本方法介绍
1 q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
2 q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
3 q.get_nowait():同q.get(False)
4 q.put_nowait():同q.put(False)
5 q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
6 q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
7 q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
# 创建一个队列
import queue
q = queue.Queue(4) # 括号内可以传数字,表示生成的队列最大可以同时存放的数据量
# 队列中存数据
q.put(11)
q.put(22)
q.put(33)
q.put(44)
# q.put(55) # 当队列数据放满了之后,如果还有数据要放,程序会阻塞,直到有位置出来(不会报错)
# 取数据
v1 = q.get()
v2 = q.get()
print(q.full()) # 判断当前队列是否满了
v3 = q.get()
v4 = q.get()
print(q.full())
# v5 = q.get() # 队列中如果已经没有数据的话,get方法就会原地阻塞
# v5=q.get_nowait() #没有数据会直接报错queue.Empty
print(q.empty()) # 判断当前队列是否空了
# v5 = q.get(timeout=4) # 没有数据后原地等待4秒之后报错
print(v1, v2, v3, v4)
'''
运行结果:
False
False
True
11 22 33 44
'''
try:
v5=q.get(timeout=2)
print(v5)
except Exception as s:
print('已没有数据')
'''
运行结果:
已没有数据
'''
'''
q.empty()
q.full()
q.get_nowait()
在多进程的情况下是不精确的
'''
六、IPC机制
IPC机制:进程与进程间通过队列通信
研究思路:
1、主进程跟子进程借助与队列通信
2、子进程跟子进程借助与队列通信
from multiprocessing import Process, Queue
def prodecer(q):
q.put('你好')
print('hello ~')
def consumer(q):
print(q.get())
# 子进程与子进程借助队列通信
if __name__ == '__main__':
q = Queue()
p = Process(target=prodecer, args=(q,))
p1=Process(target=consumer,args=(q,))
p.start()
p1.start()
'''
运行结果:
hello ~
你好
'''
# 主进程与子进程通过队列通信
if __name__ == '__main__':
q = Queue()
p = Process(target=prodecer, args=(q,))
p.start()
print(q.get())
'''
运行结果:
hello ~
你好
'''
七、生产者与消费者模型
1、 生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
2、为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
3、 什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
'''
程序中有两类角色
生产者- 负责生产数据
消费者- 负责处理数据
引入生产者消费者模型为了解决的问题是:
平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度
如何实现:
生产者<-->队列<——>消费者
'''
# 案例:食物生成者与消费者
# 方法一
from multiprocessing import Process,Queue
import time
import random
def prodecer(name,food,q):
for i in range(5):
data='%s 生产了 %s %s个'%(name,food,i)
# 模拟延迟
time.sleep(random.randint(1,5))
data_send='%s 的 %s %s个'%(name,food,i)
print(data)
# 将数据放入队列中
q.put(data_send)
def consumer(name,q):
# 假设消费者胃口很大,光盘行动
while True:
food=q.get()# 当数据取完时会停留在那,因此不是死循环
# 判断是否有结束的标识
if food is None:
break
time.sleep(random.randint(1,5))
print('%s 吃了 %s'%(name,food))
if __name__ == '__main__':
q=Queue()
p1=Process(target=prodecer,args=('大厨 egon','包子',q))
p2=Process(target=prodecer,args=('养身大师 json','泔水',q))
c1=Process(target=consumer,args=('jack',q))
c2=Process(target=consumer,args=('tom',q))
p1.start()
p2.start()
c1.start()
c2.start()
p1.join()
p2.join()
# 等待生成者生成完毕后,往队列中添加特定的结束符号
q.put(None)# 肯定在所有生产者生成的数据的末尾(注意这里有两个消费者)
q.put(None)
'''
运行结果:
大厨 egon 生产了 包子 0个
养身大师 json 生产了 泔水 0个
tom 吃了 养身大师 json 的 泔水 0个
jack 吃了 大厨 egon 的 包子 0个
大厨 egon 生产了 包子 1个
养身大师 json 生产了 泔水 1个
大厨 egon 生产了 包子 2个
养身大师 json 生产了 泔水 2个
tom 吃了 大厨 egon 的 包子 1个大厨 egon 生产了 包子 3个
jack 吃了 养身大师 json 的 泔水 1个
jack 吃了 养身大师 json 的 泔水 2个
大厨 egon 生产了 包子 4个
养身大师 json 生产了 泔水 3个tom 吃了 大厨 egon 的 包子 2个
jack 吃了 大厨 egon 的 包子 3个
养身大师 json 生产了 泔水 4个
jack 吃了 养身大师 json 的 泔水 3个
jack 吃了 养身大师 json 的 泔水 4个
tom 吃了 大厨 egon 的 包子 4个
Process finished with exit code 0
'''
# 精简方法二(引入JoinableQueue模块)
from multiprocessing import Process,Queue,JoinableQueue
import time
import random
def prodecer(name,food,q):
for i in range(5):
data='%s 生产了 %s %s个'%(name,food,i)
# 模拟延迟
time.sleep(random.randint(1,5))
print(data)
# 将数据放入队列中
q.put(data)
def consumer(name,q):
# 假设消费者胃口很大,光盘行动
while True:
food=q.get()# 当数据取完时会停留在那,因此不是死循环
time.sleep(random.randint(1,5))
print('%s 吃了 %s'%(name,food))
q.task_done()# 告诉队列你已经从里面取出来了一个数据并且处理完毕
if __name__ == '__main__':
q=JoinableQueue()# 内部存在一个计数器,每往队列里加数据-》计数+1,每执行一次q.task_done()-》计数-1
p1=Process(target=prodecer,args=('大厨 egon','包子',q))
p2=Process(target=prodecer,args=('养身大师 json','泔水',q))
c1=Process(target=consumer,args=('jack',q))
c2=Process(target=consumer,args=('tom',q))
p1.start()
p2.start()
# 将消费者设置成守护进程
c1.daemon=True
c2.daemon=True
c1.start()
c2.start()
p1.join()
p2.join()
q.join() #等待队列中所有的数据被取完再执行往下执行代码-》当计数为0时才往后运行
#只要q.join执行完毕,说明消费者已经处理完数据了,消费者就没有存在的必要了
'''
运行结果:
养身大师 json 生产了 泔水 0个大厨 egon 生产了 包子 0个
大厨 egon 生产了 包子 1个
养身大师 json 生产了 泔水 1个tom 吃了 大厨 egon 的 包子 0个
jack 吃了 养身大师 json 的 泔水 0个
养身大师 json 生产了 泔水 2个
大厨 egon 生产了 包子 2个
jack 吃了 养身大师 json 的 泔水 1个
tom 吃了 大厨 egon 的 包子 1个养身大师 json 生产了 泔水 3个
jack 吃了 养身大师 json 的 泔水 2个
大厨 egon 生产了 包子 3个
养身大师 json 生产了 泔水 4个tom 吃了 大厨 egon 的 包子 2个
大厨 egon 生产了 包子 4个
jack 吃了 养身大师 json 的 泔水 3个
jack 吃了 养身大师 json 的 泔水 4个
tom 吃了 大厨 egon 的 包子 3个
jack 吃了 大厨 egon 的 包子 4个
Process finished with exit code 0
'''