2.1 操作系统发展史
https://www.cnblogs.com/xiaoyuanqujing/articles/11715635.html
2.2 多道技术(了解)
https://www.cnblogs.com/xiaoyuanqujing/articles/11715646.html
进程调度方法
想要多个进程交替运行, 操作系统布线对这些进程进行调度, 这个调度也不是随机进行
的, 而是要遵循一定的法则, 由此就有了进程的调度算法
1 先来先服务
2 短作业优先
3 时间片轮转
4 多级反馈队列(后续补充)
- 先来先服务算法
-先来先服务(FCFS) 调度算法是一种简单的调度算法, 该算法即用于作业调度,
也用于进程调度 FCFS算法比较有利于长期作业进程, 而不利于短期作业的进程.
由此可知, 本身算法适合于CPU繁忙作业 - 短作业优先调度算法
短作业(进程) 优先调度算法(SJ/PF) 是指短作业或端进程优先调度的算法, 该算法即
用于作业调度, 也可以用于进程调度. 但其对长期作业无利, 不能保证紧迫性(进程)
被及时处理, 作业的长短只是被估算而已 - 时间片轮转发
时间片轮转发(Round Robin , RR) 法的基本思路是让每个进程的就行队列中的
等待时间与享受服务的时间成比例, 在时间片轮转法中, 需要将CPU的处理
时间分成固定大小的时间片, 例如,几十毫秒至几百毫秒。如果一个进程在被调度选中
之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU
而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的
第一个进程。
并行与并发
-
并行: 并行指两个程序同时执行, 比如赛跑, 两个人都在同时跑
-
并发: 资源有限的情况下, 两者交替轮流使用CPU资源, 比如一段路同时只能走一个人
A走一段, 让B走一段, 交替走完, 目的是提高效率 -
区别: 并行是同时运行, 只有具备多个CPU才能实现并行并发是伪并行, 即看起来是同时运行, 单个CPU+多道技术就可以实现并发(并行也属于并发)
2.4 同步异步 阻塞非阻塞
- 程序执行中的三状态:
- 就绪态(Ready)
当程序已经分配到除CPU以外的所有必要资源, 只要获得CPU即可执行, 这时的进程状态称之为就绪态 - 执行/运行态(Running)
当程序已获得CPU,其程序正在处理器上执行, 此时的进程状态称之为执行/运行态 - 阻塞态(Blocked)
正在执行的进程, 由于等待某个时间发生无法执行时, 便会放弃处理器而处于阻塞状态
引起进程阻塞的时间可能有多种, 例如;等待I/O操作, 缓冲区条件不满足, 等待接收下一级引号等
- 同步与异步
同步:一个任务的完成需要依赖另外一个任务时, 只有等待依赖的任务完成后, 也就是只有上一个任务执行完成才能执行下一个任务, 如果上一个任务遇到i/o操作也不会跳到一个程序执行 - 异步: 不需要等待彼此, 只是通知一个个任务开始执行, 等到结束后回调得到结果即可
一次可运行多个子进程 - flask,django3.0以前,都是同步框架
- tornado,sanic,fastAPI 异步框架
2.5 创建进程的两种方式(重点)
- 函数方式
from multiprocessing import Process
import time
def task(n):
print('我是子进程')
time.sleep(n)
print('子进程结束')
if __name__ == '__main__':
# args=(), kwargs={}
# t=Process(task,args=(1,))
t = Process(target=task, kwargs={'n': 1})
t.start() # 通知操作系统,开启进程,执行task函数
print('主')
- 类方法
from multiprocessing import Process
import time
class Task(Process):
def __init__(self, n):
super().__init__()
self.n = n
def run(self):
print('我是子进程')
time.sleep(self.n)
print('子进程结束')
if __name__ == '__main__':
t = Task(1)
# t.run(1) # 不是调用t.run(),而是调用t.start()
t.start()
print('主')
2.6 join方法
join 等待当前子进程全部执行完毕之后 , 主进程再执行 (用来同步子父进程的)
join的使用:等待子进程执行完成
from multiprocessing import Process
import time
def task(n):
print('我是子进程')
time.sleep(n)
print('子进程结束')
if __name__ == '__main__':
ctime = time.time()
t = Process(target=task, kwargs={'n': 1})
t2 = Process(target=task, kwargs={'n': 2})
t.start()
t2.start()
t.join() # 等待t子进程执行完成
t2.join() # 等待t2子进程执行完成
print('主')
ctime2 = time.time()
print(ctime2 - ctime)
2.7 进程间数据相互隔离
- 物理隔离
进程隔离是为了保护操作系统中进程互不干扰而设计的一组不同硬件和软件的技术
这个技术是为了避免进程A写入进程B的情况发生。进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的进程隔离的安全性通过禁止进程间内存的访问可以方便实现
进程之间数据隔离
from multiprocessing import Process
import time
age = 18
def task(n):
global age # 局部修改全局
age = 99
print('我是子进程')
time.sleep(n)
print('子进程结束')
print(age)
if __name__ == '__main__':
t = Process(target=task, kwargs={'n': 1})
t.start()
t.join()
# 等待t子进程执行完成
print('主')
print(age)
# 数据没有变,主进程中打印age和子进程的age没有半毛钱关系,数据是隔离的
3.2 僵尸进程与孤儿进程
- 僵尸进程:进程结束了,资源还没来得及回收
- 孤儿进程:主进程挂了,子进程还没结束,它就会被专门的进程接管
后期补充
3.3 进程对象及其他方法
- windows:tasklist |findstr 进程id号
- mac,Linux:ps aux | grep 进程id号
- 进程对象:t=Process(target=task, )或者是在进程内部:current_process()
- t.pid或者current_process().pid 获取进程id号
- os.getpid() 同上,获取进程id号
- os.getppid() 获取父进程id号,子进程中获取父进程id,等于父进程的id号
- t.is_alive()或者current_process().is_alive() 查看进程是否存活
- t.terminate() 关闭进程,在主进程关闭
3.4 守护进程
- 守护进程会在主进程代码执行结束后,所有子进程都会死翘翘
- 守护进程内无法开启子进程
多个进程运行时独立的, 空间是不共享的
from multiprocessing import Process,current_process
import time
import os
def task():
print(os.getpid())
print('子进程')
time.sleep(200)
print('子进程结束')
if __name__ == '__main__':
t = Process(target=task, )
# 守护进程:主进程一旦结束,子进程也结束
t.daemon=True # 一定要加在启动之前
t.start()
time.sleep(1)
print('主进程结束')
3.5 互斥锁
- 进程之间数据是不共享的, 但是共享同一套文件系统, 所有的子进程都来访问同一个文件系统
, 或者打印同一个终端, 那么这样就会带来错乱, 要对这个进行处理, 如下
# 同时只有一个人能拿到,必须释放,其他人才能再次获取到
from multiprocessing import Process, Lock
import json
import time
import random
def search():
# 查票的函数
# 打开文件,读出ticket_count
with open('ticket', 'r', encoding='utf-8') as f:
dic = json.load(f)
print('余票还有:', dic.get('ticket_count'))
def buy():
with open('ticket', 'r', encoding='utf-8') as f:
dic = json.load(f)
time.sleep(random.randint(1, 3)) # 模拟一下网络延迟
if dic.get('ticket_count') > 0:
# 能够买票
dic['ticket_count'] -= 1
# 保存到文件中去
with open('ticket', 'w', encoding='utf-8') as f:
json.dump(dic, f)
print('买票成功')
else:
# 买票失败
print('买票失败')
# 写一个函数,先查票,再买票
def task(mutex):
search()
# 买票过程要加锁
# 买前加锁
# mutex.~~acquire()~~
# buy() # 10个进程变成了串行执行
# # 买后释放锁
# mutex.release()
with mutex:
buy()
if __name__ == '__main__':
# 锁的创建,在哪?主进程创建锁
mutex = Lock() # 创建一把锁
# 模拟十个人买票(开10个进程)
for i in range(10):
t = Process(target=task, args=(mutex,))
t.start()
3.6 进程之间通信 队列介绍
-
我们知道进程之间数据是相互隔离的,要想实现进程间的通信(IPC机制),就必须借助于一些技术才可以,比如multiprocess模块中的:队列和管道,这两种方式都是可以实现进程间数据传输的,由于队列是管道+锁的方式实现,所以我们着重研究队列即可
-
创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现进程之间的数据传递
-
总结:队列支持多个人从队列的一端放入数据,同样支持多个人从队列的另一端取数据
需要使用到Queue模块
from multiprocessing import Queue
# 实例化得到要给对象
q=Queue(5) # 默认很大,可以放很多,写了个5,只能放5个
# 往管道中放值
q.put(1)
q.put('lqz')
q.put(18)
q.put(19)
# q.put(20)
# q.put(21)
# q.put_nowait(100)
# 从管道中取值
# print(q.get())
# print(q.get())
# print(q.get())
# print(q.get(timeout=100)) # 等0.1s还没有值,就结束
# print(q.get_nowait()) # 不等了,有就是有,没有就没有
print(q.empty()) # 看一下队列是不是空的
print(q.full()) # 看一下队列是不是满的
# 总结:
'''
q=Queue(队列大小)
# 放值
q.put(asdf)
q.put_nowait(asdf) # 队列满了,放不进去就不放了,报错
# 取值
q.get() # 从队列头部取出一个值
q.get_nowait() # 从队列头部取值,没有就抛错
# 队列是否为空,是否满
print(q.empty()) # 看一下队列是不是空的
print(q.full()) # 看一下队列是不是满的
3.7 IPC机制(进程间通信)代码实现
- 官方解释: 进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。另外,系统空间是“公共场所”,各进程均可以访问,所以内核也可以提供这样的条件。此外,还有双方都可以访问的外设。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。
# Inter-Process Communication,进程间通信
# 代码实现
from multiprocessing import Process, current_process, Queue
import time
import os
def task1(q):
print('我是task1进程,我的id号是:%s'%os.getpid())
q.put('lqz is handsome')
def task2(q):
# res=q.get()
# print('我是task2进程,我的id号是:%s'%os.getpid(),res)
print('我是task2进程,我的id号是:%s'%os.getpid())
if __name__ == '__main__':
q = Queue(5)
t1 = Process(target=task1, args=(q,))
t1.start()
t2 = Process(target=task2, args=(q,))
t2.start()
print(q.get())
消费者模式
- 理论:
- 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。
- 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取
- 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
from multiprocessing import Process, Lock,Queue
import time, random, os, json, socket
def consumer(q):
while True:
res = q.get()
time.sleep(random.randint(1,3))
print("%s 吃 %s" % (os.getpid(),res))
try:
q.get_nowait()
except Exception:
pass
def product(q):
for i in range(1,11):
time.sleep(random.randint(1,3))
res = "包子 %s" % i
q.put(res)
print("%s 生产了 %s" % (os.getpid(),res))
if __name__ == '__main__':
q = Queue()
p1 = Process(target=product,args=(q,))
p2 = Process(target=consumer,args=(q,))
p1.start()
p2.start()
print("host")