进程知识点补充
进程间数据隔离
不同进程之间的数据都是默认互不干涉的,可以看成每个进程都在内存中有一个自己房间,彼此不干扰,但可以通过一些技术打破。
举例
from multiprocessing import Process
money = 999
def task():
global money # 局部修改全局不可变类型
money = 666
if __name__ == '__main__':
p = Process(target=task)
p.start()
p.join() # 确保子进程代码运行结束再打印money
print(money)
"""
执行结果:
999
"""
进程对象属性和方法
获取进程号
在windows系统中,我们可以通过在cmd中输入tasklist命令查看所有进程。
代码中也有方法可以查看执行文件当前的进程号和父进程号。
current_process().pid可以获取当前进程号
from multiprocessing import Process, current_process
def task():
print('子进程号:', current_process().pid)
if __name__ == '__main__':
p = Process(target=task)
p.start()
print('主进程号:', current_process().pid)
"""
执行结果:
主进程号: 8896
子进程号: 2312
"""
os模块中的getpid()可以获取当前进程号,getppid()可以获取当前进程的父进程号。
from multiprocessing import Process
import os
def task():
print('子进程号:', os.getpid())
print('子进程的父进程号:', os.getppid())
if __name__ == '__main__':
p = Process(target=task)
p.start()
print('主进程号:', os.getpid())
"""
执行结果:
主进程号: 8248
子进程号: 7996
子进程的父进程号: 8248
"""
判断进程是否存活与杀死子进程
进程对象提供了查看进程是否存活的方法:is_alive()方法;还有杀死子进程的方法:terminate()方法
from multiprocessing import Process
import time
def task():
print('子进程执行中')
time.sleep(2)
print('子进程结束')
if __name__ == '__main__':
p = Process(target=task)
p.start()
# 查看进程是否存活
print(p.is_alive())
# 先让子进程运行一段
time.sleep(1)
# 杀死子进程
p.terminate()
# 杀死子进程需要一点时间,所以短暂休眠一下
time.sleep(0.1)
# 再次查看进程是否存活
print(p.is_alive())
"""
执行结果:
True
子进程执行中
False
"""
僵尸进程与孤儿进程
僵尸进程
所有的子进程在运行结束之后都会变成僵尸进程(死了但没死透),还保留着pid和一些运行过程的中的记录便于主进程查看(短时间保存),这些信息会被主进程回收(这时候才彻底死了)。
子进程会被主进程回收的情况:
- 主进程正常结束
- 调用join方法
孤儿进程
通俗易懂,子进程存活着,父进程意外死亡,这个时候子进程会被操作系统自动接管(相当于孤儿院)。
守护进程
守护进程即该进程的死活全部参考守护的对象,对象结束了,那么该进程也结束了,把进程对象中的daemon值变为True就说明该进程是守护进程。
举例
from multiprocessing import Process
import time
def task():
print('子进程正在执行')
time.sleep(2)
print('子进程结束了')
if __name__ == '__main__':
p = Process(target=task)
# 必须写在start前面
p.daemon = True # 将子进程设置为守护进程:主进程结束 子进程立刻结束
p.start()
print('主进程正在执行')
time.sleep(1)
print('主进程结束了')
执行结果:
主进程正在执行
子进程正在执行
主进程结束了
把子进程设置为守护进程后,主进程结束了,那么子进程也立马结束。
互斥锁
引入:每逢节假日抢票,手机上明明显示还有余票,但是点击购买的时候却提示已经没有票了,之后回到查询页面发现确实显示没有票了,这是因为在你点击购买的时候票被别人买走了。
明明多人一起买票,却不会出现1张票可以被多人买到,这就是因为互斥锁的存在。
互斥锁:防止多个进程操作同一份数据的时候会造成数据的错乱的情况,原理就是将并发变成串行,牺牲了效率但是保证的数据的安全
比如下面这个买票系统是没有使用互斥锁的情况:
import json
from multiprocessing import Process
import time
import random
# 查票
def search(name):
with open(r'ticket_data.json', 'r', encoding='utf8') as f:
data = json.load(f)
print(f'用户{name}查询当前余票:%s' % data.get('ticket_num'))
# 买票
def buy(name):
# 买票是需要再次查票的,因为期间其他人可能已经把票买走了
with open(r'ticket_data.json', 'r', encoding='utf8') as f:
data = json.load(f)
# 模拟购买的时间
time.sleep(random.randint(1, 3))
# 判断是否还有余票
if data.get('ticket_num') > 0:
data['ticket_num'] -= 1
with open(r'ticket_data.json', 'w', encoding='utf8') as f:
json.dump(data, f)
print(f'用户{name}抢票成功')
else:
print(f'用户{name}抢票失败,没有余票了')
def run(name):
search(name)
buy(name)
if __name__ == '__main__':
data = {'ticket_num': 1}
with open(r'ticket_data.json', 'w', encoding='utf8') as f:
json.dump(data, f)
# 模拟多人同时抢票
for i in range(1, 5):
p = Process(target=run, args=('用户:%s' % i,))
p.start()
执行结果:
用户用户:2查询当前余票:1
用户用户:3查询当前余票:1
用户用户:1查询当前余票:1
用户用户:4查询当前余票:1
用户用户:2抢票成功
用户用户:4抢票成功
用户用户:3抢票成功
用户用户:1抢票成功
明明只有一张票,但所有人都抢到票了,这就是因为多个进程同时操作同一份数据会出现的问题,这个问题可以添加互斥锁来解决。
添加互斥锁代码
from multiprocessing import Process, Lock
mutex = Lock()
mutex.acquire() # 抢锁
mutex.release() # 放锁
放入到模拟抢票的代码中
import json
from multiprocessing import Process, Lock
import time
import random
# 查票
def search(name):
with open(r'ticket_data.json', 'r', encoding='utf8') as f:
data = json.load(f)
print(f'用户{name}查询当前余票:%s' % data.get('ticket_num'))
# 买票
def buy(name):
# 买票是需要再次查票的,因为期间其他人可能已经把票买走了
with open(r'ticket_data.json', 'r', encoding='utf8') as f:
data = json.load(f)
# 模拟购买的时间
time.sleep(random.randint(1, 3))
# 判断是否还有余票
if data.get('ticket_num') > 0:
data['ticket_num'] -= 1
with open(r'ticket_data.json', 'w', encoding='utf8') as f:
json.dump(data, f)
print(f'用户{name}抢票成功')
else:
print(f'用户{name}抢票失败,没有余票了')
# 传入锁的对象
def run(name, mutex):
search(name)
mutex.acquire() # 抢锁
buy(name)
mutex.release() # 放锁
if __name__ == '__main__':
data = {'ticket_num': 1}
with open(r'ticket_data.json', 'w', encoding='utf8') as f:
json.dump(data, f)
# 创建互斥锁
mutex = Lock()
# 模拟多人同时抢票
for i in range(1, 5):
# 传入锁的对象
p = Process(target=run, args=('用户:%s' % i, mutex))
p.start()
执行结果:
用户用户:2查询当前余票:1
用户用户:4查询当前余票:1
用户用户:1查询当前余票:1
用户用户:3查询当前余票:1
用户用户:2抢票成功
用户用户:4抢票失败,没有余票了
用户用户:1抢票失败,没有余票了
用户用户:3抢票失败,没有余票了
互斥锁并不能轻易使用,容易造成死锁现象,互斥锁只在处理数据的部分加锁,不能什么地方都加,不然会严重影响程序的效率。
死锁现象:有两个互斥锁,释放锁的代码分别在另一个进程内,比如进程A的解锁需要进程B先解锁中,进程B的解锁需要进程A先解锁。
PS:锁有很多种,除了互斥锁,还有行锁、表锁、乐观锁、悲观锁等。
IPC机制(进程间通信)
IPC机制指的是两个进程之间进行数据通信的过程,可以是主进程与子进程数据交互,也可以是两个子进程数据交互,但本质都是一样的:不同内存空间中的进程数据交互。
我们可以用Queue队列模块实现IPC机制。
from multiprocessing import Process, Queue
def producer(q):
for i in range(3):
q.put(i)
print('子进程producer向队列中添加值:', i)
def consumer(q):
for i in range(3):
print('子进程consumer从队列中取值:', q.get())
if __name__ == '__main__':
q = Queue(1)
# 创建往队列中添加值的进程,并把队列对象当参数传入
p = Process(target=producer, args=(q,))
# 创建从队列中取值的进程,并把队列对象当参数传入
p1 = Process(target=consumer, args=(q,))
p.start()
p1.start()
执行结果:
子进程producer向队列中添加值: 0
子进程consumer从队列中取值: 0
子进程producer向队列中添加值: 1
子进程consumer从队列中取值: 1
子进程producer向队列中添加值: 2
子进程consumer从队列中取值: 2
生产者消费者模型
生产者即负责生产/制作数据,消费者即负责消费/处理数据。
比如刚刚用Queue队列模块实现IPC机制中,producer方法即是生产者(往队列里添加值),consumer方法即是消费者(从队列中取值)。
遇到该模型需要考虑的问题其实就是供需平衡的问题,生产力与消费力要均衡,比如下面的代码中就是消费力等于生产力。
from multiprocessing import Process, Queue
import time
import random
def producer(name, food, q):
for i in range(3):
data = f'{name}生产了第{i}个{food}'
print(data)
# 模拟生产过程
time.sleep(random.randint(1, 3))
q.put(food)
def consumer(name, q):
# 写个死循环,确保队列数据全部取出
while True:
food = q.get()
# 判断是否把队列数据消费完了
if food is None: break
time.sleep(random.random())
print(f'{name}吃了一个{food}')
if __name__ == '__main__':
q = Queue()
# 生产者一
p1 = Process(target=producer, args=('jason', '汉堡', q))
# 生产者二
p2 = Process(target=producer, args=('tom', '炸鸡', q))
# 消费者一
c1 = Process(target=consumer, args=('tony', q))
# 消费者二
c2 = Process(target=consumer, args=('david', q))
p1.start()
p2.start()
c1.start()
c2.start()
p1.join()
p2.join()
# None用于判断是否生产结束,有两个消费者所以要添加两个值
q.put(None)
q.put(None)
拓展:使用None用于判断是否生产结束具有不确定性,我们不知道生产者有几个时就无法使用了,所以我们这里可以使用JoinableQueue()创建队列,并修改部分代码
from multiprocessing import Process, JoinableQueue
import time
import random
def producer(name, food, q):
for i in range(3):
data = f'{name}生产了第{i}个{food}'
print(data)
# 模拟生产过程
time.sleep(random.randint(1, 3))
q.put(food)
def consumer(name, q):
# 写个死循环,确保队列数据全部取出
while True:
food = q.get()
# 判断是否把队列数据消费完了
if food is None: break
time.sleep(random.random())
print(f'{name}吃了一个{food}')
q.task_done() # 每次取数据都给队列反馈,让队列自己知道是否还有数据
if __name__ == '__main__':
# 使用JoinableQueue()创建队列
q = JoinableQueue()
# 生产者一
p1 = Process(target=producer, args=('jason', '汉堡', q))
# 生产者二
p2 = Process(target=producer, args=('tom', '炸鸡', q))
# 消费者一
c1 = Process(target=consumer, args=('tony', q))
# 消费者二
c2 = Process(target=consumer, args=('david', q))
p1.start()
p2.start()
# 给消费者添加守护进程,如果q.join()执行完了,那么消费者也该结束了
c1.daemon = True
c2.daemon = True
c1.start()
c2.start()
p1.join()
p2.join()
# 让队列全部数据取出才会往下执行
q.join()