文章目录
进程与线程(点击了解详细内容)
🔧调度
cpu调度
:
从就绪队列中挑选一个进程/线程作为CPU将要运行的下一个进程/线程(进程切换)
调度原则
:
1.调度策略:
不可抢占
可抢占
2.程序执行模型:程序在CPU突发和I/O中交替
3.比较调度算法的准则
CPU使用率:CPU处于忙状态所占时间百分比
吞吐量(带宽):在单位时间内完成的进程数量
周转时间:一个进程从初始化到结束的时间
等待时间:进程在就绪队列中的总时间
响应时间(延迟):一个请求被提交到产生第一次响应花费的时间
4.吞吐量VS延迟
5.公平的目标
每个进程占用CPU的时间和响应时间大致公平
调度算法
:
对象:通用型系统
-
先来先服务(FCFS)
-
短进程优先/短作业优先/短剩余时间优先(SPN/SJF/SRT)
SPN:不考虑抢占
SRT:考虑抢占
-
最高响应比优先(HRRN)
-
轮循(Round Robin)
-
多级反馈队列(Multilevel Feedback Queues)
-
公平共享调度(Fair Share Scheduling)
调度算法 | 优点 | 缺点 |
---|---|---|
先来先服务 | 简单 | 1.平均等待时间波动大 2.如果长任务先来,会增大平均周转时间 |
短任务优先 | 1.最小平均等待时间 2.最小平均周转时间 | 1.导致长任务饥饿 2.需要预知未来 |
最高响应比优先 | 考虑了进程的等待时间(上面的算法只考虑了进程的执行时间) | 需要预知未来 |
轮循 | 每一个进程都有时间去执行 | 存在较多的上下文切换 |
多级反馈队列 | 根据进程动态执行过程动态调整优先级 1.CPU密集型任务优先级下降很快 2.I/O密集型任务停留在高优先级 | |
公平共享调度 | 实现用户级别的公平共享 |
实时调度
:
对象:实时系统(能够在可预测的特定时限内响应事件)
-
速率单调调度(RM)
静态优先级调度
周期越短优先级越高
-
最早期限调度(EDF)
动态优先级调度
Deadline越早优先级越高
多处理器调度
:
对象:并行系统
优先级反转
:
低优先级任务影响高优先级任务
解决:优先级继承、优先级天花板
🪛进程互斥与同步:
合作的进程/线程:
- 共享资源
- 加速
- 模块化
系统缺陷:结果依赖于并发执行或者事件的顺序/时间,不确定性、不可重现
临界区:访问共享资源的一段代码
- 互斥
- 前进
- 有限等待
- 无忙等待(可选)
互斥:处于临界区的进程只能有一个
死锁:两个或以上进程,在互相等待完成特定任务,而最终无法将自身任务进行下去
饥饿:一个进程持续得不到执行
实现互斥的方法
:
-
禁用硬件中断(仅限于单处理器)
-
软件方法(复杂)
Peterson算法:针对双线程
Dekker算法:针对双线程
Bakery算法:针对n线程
-
原子操作指令(锁)
boolean TestAndSet(boolean *target){ boolean rv = *target; *target = TRUE; //修改值为真,表示锁正忙 return rv; //返回地址空间原先的值 } void Exchange(boolean *a,boolean *b){ boolean temp = *a; *a = *b; *b = temp; }
//方法一:使用TestAndSet实现锁机制 class Lock{ int value = 0; } Lock::Acquire(){ while(test-and-set(value)) //value=0 => 锁被设置为忙,并且需要等待完成; //value=1 => 不改变锁的状态,并且需要循环(自旋锁) ;//spin } Lock::Release(){ value = 0; }
//方法二:使用Exchange实现锁机制 //初始化 int lock = 0 int key; do{ key = 1; while(key == 1) exchange(lock,key); critical section lock = 0; remainder section }
实现同步互斥的方法
:
- 信号量(下文详细介绍)
- 管程(下文详细介绍)
🚩信号量
整型,可以进行P/V操作,解决同步互斥问题
P操作会产生阻塞,一旦设置的顺序有问题,会导致死锁
V操作交换顺序则无影响
使用
:
生产者和消费者问题
Class BoundedBuffer{
mutex = new Semaphore(1);
fullBuffers = new Semaphore(0);
emptyBuffers = new Semaphore(n);
}
BounderBuffer::Deposit(c){
emptyBuffers->P(); //解决同步问题
mutex->P(); //解决互斥问题
Add e to the buffer;
mutex->V();
fullBuffers->V();
}
BoundedBuffer::Remove(c){
fullBuffers->P();
mutex->P();
Remove e from buffer;
mutex->V();
emptyBuffers->V();
}
实现
:
class Semaphore{
int sem;
WaitQueue q;
}
Semaphore::P(){
sem--;
if(sem<0){
Add this thread t to q;
block(p);
}
}
Semaphore::V(){
sem++;
if(sem<=0){
Remove a thread t from q;
wakeup(t);
}
}
概念理解
互斥:只能有一个进程访问共享资源或只能有一个线程访问临界区
同步:多个进程间的协作关系。进程A的执行需要等待进程B事件执行完成
异步:进程以不可预知的速度向前推进。在等待某事件的过程中,继续做自己的事,不需要等待事件完成后工作,通过线程实现
并发:进程间走走停停运行(在一个时间点上只运行一个进程)
并行:多核处理器同时运行多个进程(在一个时间点上可以运行多个进程)
🎯管程
-
Lock 锁(实现互斥)
//等待直到锁可用,然后抢占锁 Lock::Acquire() //释放锁,唤醒等待者 Lock::Release()
-
Condition Variable 条件变量(实现同步)
//当线程的某些条件得不到满足,线程睡眠,同时释放锁(使得其他线程能够执行) Wait() //当线程的某些条件得到满足,唤醒挂在条件变量的线程继续执行 Signal()
使用
:
生产者和消费者问题
Class BoundedBuffer{
Lock lock;
int count = 0;
Condition notFull,notEmpty;
}
BoundedBuffer::Deposit(e){
lock->Acquire(); //解决互斥问题
while(count == n){ //解决同步问题,唤醒后先release再执行其他线程
notFull.Wait(&lock);
}
Add c to the buffer;
count++;
notEmpty.Signal();
lock->Release();
}
BoundedBuffer::Remove(e){
lock->Acquire();
while(count == 0 ){
notEmpty.Wait(&lock);
}
Remove c from buffer;
count--;
notFull.Signal();
lock->Release();
}
实现
:
//Lock锁的实现,在上文中已经实现了
//Conditon条件变量的实现
Class Condition{
int numWaiting = 0;
WaitQueue q;
}
Condition::Wait(lock){
numWaiting++;
Add this thread t to q;
release(lock);
schedule(); //就绪态-> 运行态
require(lock);
}
Condition::Signal(){
if(numWaiting > 0){
Remove a thread t from q;
wakeup(t); //等待态->就绪态
numWaiting--;
}
}
经典同步互斥问题(点击了解详细内容)
🔒死锁
死锁问题:
一组阻塞的进程持有一种资源等待获取另一个进程所占有的一个资源
系统模型:
如果资源分配图中不包含循环——>没有死锁
如果资源分配图中包含循环——>只有一个实例,死锁;有几个实例,可能死锁
死锁特征:
- 互斥
- 持有并等待
- 无抢占
- 循环等待(有环)
这四个特征是死锁的必要条件,不是充分条件
死锁处理方法:
-
死锁预防
破坏循环等待的特征,对所有资源类型进行排序,并要求每个进程按照资源的顺序进行申请
-
死锁避免
需要系统具有一些额外的先验信息提供,判断分配资源后是否会形成环
银行家算法:
- 总需求量(总需求量= 已分配量+未来需要量)
- 剩余空闲量
- 已分配量
- 未来需要量
-
死锁检测
定期调用检测算法来搜索wait-for Graph是否有环
-
死锁恢复
重启、杀死进程
资源抢占
📞进程间通信IPC
定义:进程间进行有效的数据交互
通信模型:
-
直接通信与间接通信
-
阻塞与非阻塞
通信链路缓冲:
- 0缓冲
- 有限缓冲
- 无限缓冲
信号
:软件中断
接收到信号后的处理方式:
- 指定信号处理函数被调用
- 依靠操作系统的默认操作
- 闭塞信号
缺点:不能传输要交换的任何数据
管道
:子进程从父进程继承文件描述符
消息队列
进程间没有父子关系
可以传递结构化数据和字节流
共享内存
优点:快速,方便地使用数据
缺点:必须同步数据访问