进程其他知识
一、子进程回收资源的两种方式
- join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源
- 主进程"正常结束",子进程与主进程一并被回收资源
from multiprocessing import Process
import time
# 任务
def task():
print('start...')
time.sleep(2)
print('end...')
if __name__ == '__main__':
p = Process(target=task)
# 告诉操作系统帮你开启子进程
p.start()
# p.join()
time.sleep(3)
# 主进程结束
print('主进程结束...')
二、僵尸进程与孤儿进程(了解)
- 僵尸进程(有坏处)
- 在子进程没有结束时,主进程没有"正常结束",子进程PID不会被回收
- 缺点
- 操作系统中的PID号是有限的,如有子进程PID号无法正常回收,则会占用PID号
- 资源浪费
- 若PID号满了,则无法创建新的进程
- 孤儿进程(没有坏处)
- 在子进程没有结束时,主进程没有"正常结束",子进程PID不会被回收
- 操作系统优化机制(孤儿院)
- 当主进程以外终止,操作系统会检测是否有正在运行的子进程,会把他们放入孤儿院中,让操作系统帮你回收
三、守护进程
-
当主进程时,子进程也必须结束,并回收
-
通俗说法:类似于古代的君王和服侍君王的太监、妃子等,只要君王死了,其他人都得陪葬
-
主进程创建守护进程
- 其一:守护进程会在主进程代码执行结束后就终止
- 其二:守护进程内无法再开启子进程,否则抛出异常
-
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
import time
from multiprocessing import Process
def demo(name):
print(f'start...{name}')
time.sleep(100)
print(f'end...{name}')
print('子进程结束了...')
if __name__ == '__main__':
p = Process(target=demo, args=('童子军高留柱1号',))
# 守护进程必须在p.start()调用之前设置
p.daemon = True # 将子进程p设置为守护进程
p.start() # 告诉操作系统帮你开启子进程
time.sleep(1)
print('皇帝驾崩了...')
四、进程间数据隔离
进程隔离是为保护操作系统中进程互不干扰而设计的一组不同硬件和软件的技术
这个技术是为了避免进程A写入进程B的情况发生。进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就防止进程A将数据信息写入进程B
进程隔离的安全性通过禁止进程间内存的访问可以方便实现
# 代码验证进程间数据隔离
from multiprocessing import Process
n = 100
def work():
global n
n = 0
print('子进程内:',n)
if __name__ == '__main__':
p = Process(target=work)
p.start()
print('主进程内:',n)
主进程内: 100
子进程内: 0
五、进程互斥锁
-
进程同步(multiprocess.Lock)
-
锁——multiprocess.Lock
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理
- 多进程模拟抢票
# 文件的内容为:{"count":1}
# 注意一定要用双引号,不然json文件无法识别
from multiprocessing import Process,Lock
import time, json, random
def search():
dic = json.load(open('db.json'))
print(f' 33[43m剩余票数{dic["count"]} 33')
def get():
dic = json.load(open('db.json'))
time.sleep(0.1) # 模拟读数据的网络延迟
if dic['count'] > 0:
dic['count'] -= 1
time.sleep(0.2) # 模拟写数据的网络延迟
json.dump(dic, open('db.json','w'))
print(' 33[43m购票成功 33[0m')
def task():
search()
get()
if __name__ == '__main__':
for i in range(10): # 模拟并发100个客户端抢票
p = Process(target=task)
p.start()
# 引发问题:数据写入错乱
- 互斥锁保证数据安全
# 例子一
from multiprocessing import Process,Lock
import time, json, random
def search():
dic = json.load(open('db.json'))
print(f' 33[43m剩余票数{dic["count"]} 33[0m')
def get():
dic = json.load(open('db.json'))
time.sleep(random.random()) # 模拟读取数据网络延迟
if dic['count'] > 0:
dic['count'] -= 1
time.sleep(random.random()) # 模拟写数据的网络延迟
json.dump(dic, open('db.json', 'w'))
print(' 33[32m购票成功 33[0m')
else:
print(' 33[31m购票失败 33[0m')
def task(lock):
search()
lock.acquire() # 将买票这一环节由并发变成了串行,牺牲了运行效率,但保证了数据的安全性
get()
lock.release()
if __name__ == '__main__':
lock = Lock()
for i in range(10):
p = Process(target=task, args=(lock,))
p.start()
# 例子二
from multiprocessing import Process, Lock
import random, time, json
# 查看余票
def search(name):
# 1.读取db.json文件中的数据
with open('db.json', 'r', encoding='utf-8')as f:
db_dic = json.load(f)
print(f'用户{name}查看余票还剩{db_dic.get("count")}')
# 2.若有余票,购买成功,票数会减少
def buy(name):
with open('db.json', 'r', encoding='utf-8')as f:
db_dic = json.load(f)
# 进入这一步说明最先抢到票
if db_dic.get('count') > 0:
db_dic['count'] -= 1
time.sleep(random.random())
with open('db.json', 'w', encoding='utf-8')as f:
json.dump(db_dic, f)
print(f' 33[32m用户{name}抢票成功 33[0m')
else:
print(f' 33[31m用户{name}抢票失败 33[0m')
def run(name, lock):
search(name)
lock.acquire() # 加锁
buy(name)
lock.release() # 释放锁
if __name__ == '__main__':
lock = Lock()
# 开启多进程:实现并发
for line in range(10):
p_obj = Process(target=run, args=(f'sean{line}', lock))
p_obj.start()
总结:加锁可以保证多个进程修改用一块数据时,同一时间只有一个任务可以进行修改,即串行的修改,速度是慢了,但牺牲了速度却保证了数据安全。