4.23
昨日回顾
-
操作系统发展史
-
多道技术
并发:看起来像同时运行的
并行:真正意义上的同时运行
- 多道技术核心:单核实现并发效果
- 空间和时间上的复用
- 空间上:多个程序公用一套计算机硬件
- 时间上:切换+保存状态
- 切换分为两种
- 当一个程序遇到IO操作,操作系统立刻剥夺该程序的cpu执行权限
- 当一个程序长时间占用cpu,也会剥夺该程序的cpu执行权限
-
进程
-
程序与进程的区别
-
进程的调度算法
- 先来先服务调度算法
- 短作业优先调度算法
- 时间片轮转发+多级反馈队列
-
进程运行的三状态图
- 就绪态:一切程序必须先经过就绪态才能加入运行态
- 运行态
- 阻塞态
理想情况是程序一直处于就绪态和运行态之间
-
-
同步与异步
任务的提交方式
- 同步:任务提交之后,原地等待返回结果,期间不做任何事情
- 异步:任务提交之后,不等待结果,结果由回调机制做处理
-
阻塞非阻塞
程序的运行状态
上面两种概念通常会组合出现,但是最常用的就是 异步 + 非阻塞
-
开启进程的两种方式
from mutiprocessing import Process # 1. 类实例化产生对象 # 2. 类的继承,run方法 # 在Windows里开启进程的代码一定要在main代码块内 # 创建一个进程就是在内存中申请一块内存空间将需要的代码丢进去
-
join方法
主进程等待子进程代码运行完毕后再往下执行代码
-
进程间数据是相互隔离
-
人工智能相关参考网站
http://www.turingapi.com # 图灵机器人 科大讯飞 http://ai.baidu.com/creation/main/createlab
今日内容概要
- 进程对象及其他方法
- 僵尸进程与孤儿进程
- 守护进程
- 互斥锁
- 队列介绍
- 进程间通信IPC机制
- 生产者消费者模型
- 线程相关
进程对象及其他方法
一台计算机上运行着很多进程,计算机会给每一个运行的进程分配一个 PID号 ,是每一个进程的唯一标识
如何查看:
-
windows:cmd -->tasklist
tasklist|findstr PID
-
mac:终端-->ps aux
-
current_process().pid
from multiprocessing import Process,current_process
import time,os
def task():
print('%s is running'%current_process().pid)
time.sleep(3)
if __name__ == '__main__':
p = Proccess(target=task)
p.start()
p.terminate() # 杀死当前进程,需要一些时间,is_alive不会立刻看到False
# time.sleep(0.1)
print(p.is_alive()) # 判断当前进程是否存活
'''
一般情况下会将储存布尔值的变量名和返回的结果是布尔值的方法名都起成is_开头
'''
# 得到进程task的pid
-
os模块
os.getpid()
:得到当前进程PIDos.getppid()
:得到父进程的PID号,谁运行的,父pid就是谁
僵尸进程与孤儿进程
-
僵尸进程:
没死透的进程,当开设了子进程之后,该进程死后不会立刻释放占用的进程号,因为要让父进程能够查看他开设的子进程的基本信息,于是进入僵尸进程状态
-
孤儿进程:
子进程存活,父进程死亡,操作系统会开设一个儿童福利院,专门管理孤儿进程,回收相关资源
守护进程
守护进程
在start开启进程上方,指定进程p为守护进程 p.daemon = True
- 其一:守护进程会在主进程代码执行结束后就终止
- 其二:守护进程内无法再开启子进程,否则抛出异常:
AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的
from multiprocessing import Process
def task(name):
print(f'{name} alive')
time,sleep(1)
print(f'{name} dead')
if __name__ == '__main__':
p = Process(target=task,args=('egon',))
p.daemon = True # 将进程p设置成守护进程
# 这句话一定放在start上面
p.start()
print('main')
互斥锁
模拟抢票,来讲互斥锁
from multiprocessing import Process
import json,time,random
# 查票
def search(i):
# 同级目录下写一个json文件data
with open('data', 'r', encoding='utf-8') as f:
dic = json.load(f)
print('%s用户查询余票%s' % (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(0, 2))
# 判断当前是否有票
if dic.get('ticket_num') > 0:
# 修改数据库,票数-1
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, 11):
p = Process(target=run, args=(i, mutex))
p.start()
多个进程操作同一份数据的时候,会出现数据错乱的问题,针对上述问题,加锁处理:将并发变成串行,牺牲效率,保证了数据安全
在主进程中生成一把锁,让所有的子进程抢,谁先抢到,谁先操作数据。一个进程抢到了,进程结束释放之后,别的进程接着抢
注意:
- 锁不要轻易使用,容易造成死锁现象,写代码不会用到,都是内部封装好的
- 锁只在处理数据部分加,来保证数据安全
行锁,表锁
进程间通信
队列Queue模块
队列,先进先出:管道 + 锁
堆栈,先进后出
import queue
# 创建一个队列,括号内可以传数字,表示生成队列最大可以存放的数据量max_size
q = queue.Queue(5)
# 往队列中存数据:put
q.put(100)
q.put(200)
q.put(300)
# 当队列数据放满了,继续put,程序会阻塞,直到有位置让出来,多的数据入队
# 在队列中取数据:get
val = q.get() # 100
# 队列中如果已经没有数据,get方法会原地阻塞
val6 = q.get_nowait() # 没有数据直接报错
val7 = q.get(timeout = 3) # 等待时间3秒,超过3秒没有取出数据,报错
q.full() # 返回判断队列是否满了
q.empty() # 返回判断当前判断是否为空
# 当多进程操作一个队列的时候,结果会不准确
IPC机制
借助队列实现进程间通信
import queue
from multiprocessing import Process
# 1. 主进程跟子进程借助队列通信
def producer(g):
q.put('1')
print('producer')
# 2. 子进程跟子进程借助队列通信
def consumer(q):
print(q.get())
if __name__ == '__main__':
q = Quene()
p = Process(target=producer,args=(q,))
p1 = Process(target=consumer,args=(q,))
p.start()
p1.start()
# print(q.get())
生产者消费者模型
生产者:生产/制造东西的
消费者:消费/处理东西的
该模型除了上述两个角色,还有一个媒介进行连接,生产者和消费者直接不直接交互,而是通过媒介
from multiprocessing import Process, Queue
import time, random
# 1. 生产者制造数据
def producer(name, food, q):
for i in range(6):
data = f'{name},{food},{i}'
time.sleep(random.randint(1, 3))
print(data)
q.put(data)
# 2. 消费者接收数据
def consumer(name, q):
while True:
food = q.get()
if not food:
break
time.sleep(random.randint(1, 2))
print(f'{name}吃了{food}')
if __name__ == '__main__':
q = Queue()
# 生产者
p1 = Process(target=producer, args=('aaa', '包子', q))
p2 = Process(target=producer, args=('bbb', '煲汤', q))
# 消费者
c1 = Process(target=consumer, args=('deimos', q))
p1.start()
p2.start()
c1.start()
# 等待生产者生产完所有的数据,往队列添加指定的符号
p1.join()
p2.join()
q.put(None)
q.put(None) # 两个None,一个消费真拿一个,拿到就break了
# 有几个消费者就放几个None
q = JoinableQueue()
q.task_done()
# 每当往队列中存入数据的时候,内部有一个计数器+1,每当调用q.task_done的时候,内部计数器-1
q.join() # 在计数器为0的时候才会往后运行
# join执行完毕,说明消费者消费完毕,就没有必要再存在了,所以应该把消费者设置成守护进程
线程理论
三个问题
-
什么是:
进程表示的是资源单位,真正干活的是线程,执行单位 将操作系统比喻成一个工程,进程相当于车间,线程相当于车间里的流水线 进程和线程都是虚拟单位,职位为了更加方便地描述问题
-
为何要有:
开设进程 1. 申请内存空间 耗资源 2. 拷贝代码 耗资源 开线程 3. 一个进程内可以开设多个线程,无需再次申请内存空间,即拷贝代码的操作 开设线程的开销远远小于开设进程的开销 同一个进程下的多个进程,数据共享 4. 进程是资源分配的最小单位,线程是CPU调度的最小单位. 每一个进程中至少有一个线程。