39---并发编程之进程2
一 进程对象及其他方法
一台计算机上面运行着很多进程,计算是如何区分并管理这些进程服务端的呢:
计算机会给每一个运行的进程分配一个PID号
Windows电脑进入cmd输入tasklist即可查看所有进程的进程号
输入tasklist|findir pid号可查看对应的进程
mac进入终端输入ps aux
- 查看进程号
# 查看当前进程的进程号---方法一---current_process().pid
from multiprocessing import Process,current_process
import time
import os
def task():
print(f'{current_process().pid} is run')
time.sleep(15)
if __name__ == '__main__':
p = Process(target=task)
p.start()
print('主',current_process().pid)
# 查看当前进程的进程号---方法一二--getpid()
from multiprocessing import Process,current_process
import time
import os
def task():
# 查看当前进程的进程号
print(f'{os.getpid()} is run')
# 查看当前进程的主进程号
print(f'{os.getppid()}')
time.sleep(15)
if __name__ == '__main__':
p = Process(target=task)
p.start()
print('主',os.getpid())
# 当前主进程的主进程号
print('猪猪',os.getppid())
- 终止进程
from multiprocessing import Process,current_process
import time
import os
def task():
# 查看当前进程的进程号
print(f'{os.getpid()} is run')
# 查看当前进程的主进程号
print(f'{os.getppid()}')
time.sleep(15)
if __name__ == '__main__':
p = Process(target=task)
p.start()
p.terminate() # 杀死当前进程
# 这句话是告诉操作系统帮你杀死当前进程
# 需要一定时间,而代码的运行速度极快
print(p.is_alive()) # 判断当前进程是否存活
'''
一般情况下我们会将存储布尔值的变量名和返回
的结果是布尔值的方法名,都起成is_开头
'''
print('主',os.getpid())
# 当前主进程的主进程号
print('猪猪',os.getppid())
二 僵尸进程与孤儿进程(了解)
- 僵尸进程
僵尸:死了但是没有死透
当你开设了子进程之后该进程死后不会立刻释放占用的进程号
因为我要让父进程能够查看到它开设的子进程的一些基本信息---占用的PID号,运行时间
所有的进程都会步入僵尸进程
可能存在下属情况:父进程不死并且无限制的创建子进程并且子进程也不结束,就是有害的
父进程回收子进程占用的PID号:
1 父进程等待子进程运行结束
2 父进程调用join方法
- 孤儿进程
子进程存活,父进程意外死亡
操作系统会开设一个儿童福利院专门管理孤儿进程回收相关资源
三 守护进程
- 介绍
你死我死,你活我活
- 代码实现
from multiprocessing import Process
import time
def task(name):
print(f'{name} 太监总管正在或者')
time.sleep(3)
print(f'{name}已经死亡')
if __name__ == '__main__':
p = Process(target=task,kwargs={'name':'egon'})
# 这句话一定要放在start上面,否则会报错
p.daemon = True # 将进程p设置成守护进程
p.start()
print('秦始皇寿终正寝!')
四 互斥锁
针对多个进程操作同一份数据的时候,会出现数据错乱的问题
针对上述问题解决方式就是加锁处理:
****将并发变成串行,牺牲效率但是保证了数据的安全****
from multiprocessing import Process,Lock
import json
import time
import random
# 查票
def search(i):
# 文件操作读取票数
with open('data','r',encoding='utf-8') as f:
dic = json.load(f)
print(f'{i}查询余票{dic.get("ticket_num")}')
# 买票 先查再买
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'] -= 1
# 写入数据库
with open('data', 'w', encoding='utf-8') as f:
json.dump(dic,f)
print(f'{i}买票成功')
else:
print(f'{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 锁只在处理数据的部分加来保证数据的安全(只在争抢数据的环节加锁处理即可)
"""
- 使用面向对象整合上述代码
from multiprocessing import Process,Lock
import json
import random
import time
class People:
def __init__(self,name):
self.name = name
def query(self):
with open('data','r',encoding='utf-8') as f:
dic = json.load(f)
num = dic.get('ticket_num')
print(f'{self.name} 查询到余票是{num}')
return dic
def buy(self):
dic = self.query()
time.sleep(random.randint(1,3))
if dic.get('ticket_num') > 0:
dic['ticket_num'] -= 1
with open('data','w',encoding='utf-8') as f:
json.dump(dic,f)
print(f'{self.name}订票成功')
else:
print(f'{self.name}订票失败')
def run(self,mutex):
self.query()
mutex.acquire()
self.buy()
mutex.release()
if __name__ == '__main__':
mutex = Lock()
for i in range(10):
p = Process(target=People(i).run,args=(mutex,))
p.start()
### 五 队列介绍
- 队列Queue模块
```markdown
队列:管道+锁
队列:先进先出
堆栈:先进后出
from multiprocessing import Queue
# 创建一个队列
q = Queue(5) # 括号内可以传数字 标示生成的队列最大可以同时存放的数据量
q.put(111)
q.put(222)
q.put(333)
# print(q.full()) # 判断当前队列是否满了
# print(q.empty()) # 判断当前队列是否空了
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_nowait() # 没有数据直接报错queue.Empty
# v6 = q.get(timeout=3) # 没有数据之后原地等待三秒之后再报错 queue.Empty
try:
v6 = q.get(timeout=3)
print(v6)
except Exception as e:
print('一滴都没有了!')
# # v6 = q.get() # 队列中如果已经没有数据的话 get方法会原地阻塞
# print(v1, v2, v3, v4, v5, v6)
"""
q.full()
q.empty()
q.get_nowait()
在多进程的情况下是不精确
"""
六 进程间通信IPC机制
- IPC机制
借助队列完成进程与进程之间的通信
from multiprocessing import Queue,Process
'''
研究思路
1 主进程跟子进程借助于队列通信
2 子进程跟子进程借助于队列通信
'''
def run(q):
q.put('25服务')
print('hello py')
def consumer(q):
print(q.get())
if __name__ == '__main__':
q = Queue()
p = Process(target=run,args=(q,))
p1 = Process(target=consumer,args=(q,))
p.start()
p1.start()
七 生产者消费者模型
生产者:生产/制造数据
消费者:消费/处理数据
该模型除了上述两个之外还需要一个介质:
生活中的例子:做包子的包子做好后放在蒸笼(介质)里面,买包子的从蒸笼中拿包子
生产者和消费者不是直接做交互的,而是借助于媒介
生产者(做包子的)+消息队列(蒸笼)+消费者(吃包子的)
from multiprocessing import Process,Queue,JoinableQueue
import time
import random
def produser(name,food,q):
for i in range(5):
data = f'{name} 生产了 {food}{i}个'
# 模拟延迟
time.sleep(random.randint(1,3))
print(data)
# 将数据放入队列中
q.put(data)
def consumer(name,q):
# 消费者胃口很大,光盘行动,生产的事物全部吃掉
while True:
food = q.get() # 没有数据就会卡住
# 判断当前是否有结束的表示
# if food is None:
# break
time.sleep(random.randint(1,3))
print(f'{name} 吃了 {food}')
q.task_done() # 告诉队列你已经从队列中取出一个数据并处理完毕了
if __name__ == '__main__':
# 先做蒸笼
# q = Queue()
q = JoinableQueue()
p1 = Process(target=produser,args=('egon','包子',q))
p2 = Process(target=produser,args=('tank','煲汤',q))
c1 = Process(target=consumer,args=('春哥',q))
c2 = Process(target=consumer,args=('新哥',q))
p1.start()
p2.start()
# 将消费者是设置成守护进程
c1.daemon = True
c2.daemon =True
c1.start()
c2.start()
p1.join()
p2.join()
# 等待生产者生产完毕之后,往队列中添加特定的结束符号
# 有几个消费者,所以需要几个None
# q.put(None) # None在生产者生产数据的末尾
# q.put(None) # None在生产者生产数据的末尾
q.join() # 等待队列中所有数据被取完在执行往下的代码
'''
JoinableQueue每当往该队列中存入数据的时候,内部会有一个计数器+1
每当调用task_done的时候,计数器-1
q.join()当计数为0的时候,才往后运行
只要q.join()执行完毕,说明消费者已经处理完数据了,消费者就没有
存在的必要了---守护进程
'''
八 线程相关知识点
- 什么是线程
进程:资源单位
线程:执行单位
将操作系统比喻成一个大的工厂
那么进程就相当于工厂中的车间
而线程就是车间里面的流水线
每一个进程肯定自带一个线程
再次总结:
进程:资源单位,起一个进程仅仅只是在内存空间中开辟一块独立的内存车间
线程:执行单位,真正被CPU执行的其实是进程中的线程,线程指的就是代码的执行过程,执行代码中所需使用到的资源都找所需要的进程索要
进程和线程都是虚拟单位,只是为了更加方便的描述问题
- 为何要有线程
开设进程
1 申请内存空间,耗资源
2 ‘拷贝代码’ 耗资源
开线程
一个进程内可以开设多个线程,可以在一个进程内可以开设多个线程无需再次申请内存空间
总结:开设线程的开销要远远小于线程的开销
同一个进程下的多个线程数据是共享的
举例说明:
开发一款文本编辑器
获取用户输入
实时输出到屏幕
自动保存到硬盘
针对上述三个功能,开始进程合适还是线程合适
开三个线程处理上述三个功能更加合适
- 如何使用
loading.....