并发02--进程02:进程对象、僵尸进程、守护进程、队列
1.进程对象及其他方法
pycharm激活码网站:http://idea.medeming.com/jets
0.系统查看进程id
# 计算机管理进程
一台计算机上面运行着很多进程,那么计算机是如何区分并管理这些进程服务端的呢?
:计算机会给每个运行的进程分配一个PID号
# 如何查看
Windows电脑
cmd-->tasklist
tasklist |findstr PID 查看具体某PID的进程
Mac电脑
终端--ps aux
ps aux|grep PID 查看具体某PID的进程
# 进程其他方法:
import os
from multiprocessing import Process, current_process
current_process().pid # 查看当前进程的进程号
os.getpid() # 查看当前进程的进程号
os.getppid() # 查看当前进程的父进程号
p = Process(target=task)
p.terminate() # 终止当前 p进程
p.is_alive() # 判断当前 p进程是否存活
# 是让操作系统去杀死当前进程,但需要执行时间,而代码的运行速度极快,所以需要稍加一点点时间
time.sleep(0.1)
print(p.is_alive())
# 例:
from multiprocessing import Process, current_process
import time
import os
def task():
# 查看当前进程的进程号
# print('子进程号是:{} is running'.format(current_process().pid))
print('子进程号是:{} is running'.format(os.getpid()))
# 查看当前进程的父进程号
print('该子进程的父进程号是:{} '.format(os.getppid()))
time.sleep(3)
if __name__ == '__main__':
p = Process(target=task)
p.start()
print('主进程号是:', current_process().pid)
print('主进程的父进程号是(当前pycharm进程号):', os.getppid())
1.僵尸进程与孤儿进程(了解)
# 僵尸进程
死了但是没有死透
当你开设了子进程之后,子进程终止后不会立即释放占用的进程号,会保留一些子进程的状态信息
如占用的PID号,运行时间等 (因为要使得其父进程能够查看到其子进程的一些基本信息)
如果保留的信息不释放,其进程号就会一直被占用,这种进程称之为僵尸进程。
所有进程都会步入僵尸进程状态
特殊情况下:父进程不终止且无限制的创建子进程,并且子进程也不结束。此情况下,僵尸进程是有害的。
# 解决僵尸进程的方法:
父进程回收子进程占用的状态信息:
1.父进程等待子进程运行结束后,再结束父进程。
因为其父进程结束了,不会存在还需要访问其子进程的状态信息了,子进程占用的信息就会自动释放
2.父进程调用join方法,等待子进程结束,再继续执行后面代码。
# 孤儿进程
子进程存活,父进程意外死亡
操作系统会开设一个"儿童福利院" 专门管理孤儿进程,回收相关资源。
2.守护进程
# 守护进程会随着主进程的结束而结束。
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常
from multiprocessing import Process
import time
def task(name):
print('{} 总管正在活着'.format(name))
time.sleep(3)
print('{} 总管正在死亡'.format(name))
if __name__ == '__main__':
p = Process(target=task, args=('egon',))
# p = Process(target=task, kwargs={'name': 'egon'}) 也可以以字典传参的形式
# 将进程p设置成守护进程
p.daemon = True
p.start()
print("皇帝寿终正寝")
3.互斥锁(了解)
多个进程操作同一份数据的时候,会出现数据错乱的问题
针对上诉问题,解决方式就是加锁处理:将并发变成串行,牺牲效率,但是保证了数据的安全性。
# 互斥锁:
就是将每次只能一个去操作数据的步骤,放进保险柜,锁起来。谁拿到钥匙,谁就可以来操作数据。
from multiprocessing import Process, Lock
import random
import json
import time
# 查票,以jason 文件模拟数据库
def search(i):
# 文件操作读取票数
with open('data', 'r', encoding='utf-8') as f:
dic = json.load(f)
print("用户{}查询余票{}".format(i, dic.get('ticket_num')))
# 字典取值不要用[]的形式 推荐使用get,你写的代码打死都不能报错!!!
# 买票:1.先查 2.再买
def buy(i):
# 先查票
with open('data', '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'] = 0
with open('data', 'w', encoding='utf-8') as f:
json.dump(dic, f)
print("用户{}买票成功".format(i))
else:
print("用户{}买票失败".format(i))
# 整合上面两个函数
def run(i, mutex):
search(i)
# 给买票环节加锁处理 (对于那些需要每次只允许一个操作数据,将其放在 acquire 与 release 之间)
# 取得锁
mutex.acquire()
buy(i)
# 释放锁
mutex.release()
if __name__ == '__main__':
# 在主进程中生成一把锁,让所有的子进程抢,谁先抢到谁先买票
mutex = Lock()
for i in range(1, 11):
p = Process(target=run, args=(i, mutex))
p.start()
拓展:行锁、表锁
注意:
1.锁不要轻易的使用,容易造成死锁现象(我们写代码一般不会用到,都是内部封装好的)
2.锁只在处理数据的部分加(放在 .acquire() 与 .release() 之间),来保证数据安全
4.进程间通信
IPC(Inter-Process Communication)
IPC机制:实现进程之间通讯
管道:pipe 基于共享的内存空间
队列:pipe+锁的概念--->Queue
# 本地测试时,才可能会用到Queue,实际生产都是用别人封装好的功能非常强大,例如Redis,kafka,RQ
5.队列 queue
"""
队列:先进先出
堆栈:先进后出
"""
"""存取数据,存是为了更好的取。核心:千方百计的存,简单快捷的取"""
import queue
# 生成一个队列实例
q = queue.Queue(maxsize=5) # 括号内可以传数字,标识生成的队列最大可以同时存放的数据量
# 往队列中存数据
q.put(111)
q.put(222)
q.put(333)
# print(q.full()) # 判断当前队列是否满了
q.put(444)
q.put(555)
# print(q.full()) # 判断当前队列是否满了
# q.put(666) # 当队列数据放满了之后,如果还有数据要放,程序会阻塞,直到有位置让出来,再继续放入。不会报错
# 去队列取数据
v1 = q.get() # 不写在一行,是为了代码的可读性
v2 = q.get()
v3 = q.get()
v4 = q.get()
v5 = q.get()
# print(q.empty()) # 判断队列是否空了
# v6 = q.get() # 队列中如果已经没有数据的话,get方法会原地阻塞,直到队列有数据,再继续取出。不会报错
# v7 = q.get(timeout=3) # 没有数据,等待3秒,再报错:queue.Empty 队列为空
# v8 = q.get_nowait() # 没有数据直接报错:queue.Empty 队列为空
# q.join() # 等待队列中所有数据被处理完,再往下执行代码
# (实际上意味着等到队列为空,再执行别的操作)
print(v1, v2, v3, v4, v5)
"""
总结: q.full() q.empty() q.get_nowait() 在多进程时不精确的。(因为你刚判断,另一个进程可能就操作了)
"""
6.IPC机制
# IPC机制:进程间通信
from multiprocessing import Queue, Process
"""
研究思路:
1.主进程跟子进程借助于队列通信
2.子进程跟子进程借助于队列通信
"""
# 例1
def producer(q):
q.put('我是23号技师,很高兴为你服务')
print('hello big baby')
if __name__ == '__main__':
q = Queue()
p = Process(target=producer, args=(q,))
p.start()
print(q.get()) # 主进程中,打印了子进程的信息...
# 例2
def producer(q):
q.put('我是23号技师,很高兴为你服务')
def consumer(q):
print(q.get()) # 子进程中,打印了另一个子进程的信息...
if __name__ == '__main__':
q = Queue()
p1 = Process(target=producer, args=(q,))
p2 = Process(target=consumer, args=(q,))
p1.start()
p2.start()
7.生产者消费者模型
生产者:生产或者制造东西
消费者:消费或者处理东西
该模型除了上述两个之外,还需要一个媒介来存储东西(是为了解决供需不平衡的问题)
例:厨师做菜,食客吃菜,盘子为媒介
生产者与消费者之间不是直接做交互的,而是借助于媒介做交互
生产者(厨师)+消息队列(盘子)+消费者(食客)
# 版本三:(最优版)
# 优化:
# 1.通过利用 JoinableQueue 类 调用 join() 和 task_done() 方法 来自动判断 队列中是否还有数据
# JoinableQueue 这个类内部,每当你往队列中存入数据时,内部有一个计数器 +1
# q.task_done() 调用时(常用在 get 取数据之后),计数器 -1
# q.join() 调用时,会等到计数器为0时,才往后运行。
# 2.将消费者进程设置成守护进程,等到主进程中执行完毕后,直接终止消费者进程。
# (因为主进程最后调用q.join,说明队列中没有数据了,且主进程后续没有执行代码,主进程结束),
from multiprocessing import JoinableQueue, Process
import time
import random
def producer(name, food, q):
for i in range(1, 4):
# 模拟生产
data = '{}做了{}{}'.format(name, food, i)
# 模拟延迟,生产时间
time.sleep(3)
print(data)
# 模拟装菜,将数据放进消息队列中
q.put(data)
def consumer(name, q):
# 一直消费,光盘行动
while True:
# 模拟消费
food = q.get()
time.sleep(random.randint(1, 3))
print('{}吃了{}'.format(name, food))
q.task_done() # 告诉当前队列 前一步的操作处理完毕(这里是取出了一个数据,且处理完毕)
if __name__ == '__main__':
q = JoinableQueue()
# # 设置生产者子进程,并开启
p1 = Process(target=producer, args=('大厨egon', '包子', q))
p2 = Process(target=producer, args=('马叉虫tank', '白菜', q))
p1.start()
p2.start()
# 设置消费者子进程
c1 = Process(target=consumer, args=('春哥', q))
c2 = Process(target=consumer, args=('熊哥', q))
# 并设置其为守护进程,开启
c1.daemon = True
c1.start()
c2.daemon = True
c2.start()
# 主进程等待生产者子进程,全部生产完
p1.join()
p2.join()
# 等待队列中所有数据被处理完,再往下执行代码
q.join()
# 只要q.join()执行完毕后,说明消费者已经处理完数据了,那么消费者的进程就没有必要存在了。
# 由此可以将 消费者的进程 设置成守护进程