【操作系统学习】(二)同步互斥
来源:互联网 发布:java math 绝对值 编辑:程序博客网 时间:2024/05/17 02:29
临界区
描述
- 临界区
- 指进程中访问临界资源的一段需要互斥访问的代码
- 进入区
- 检查可否进入临界区的一段代码
- 如可进入,设置相应“正在访问临界区”的标志
- 退出区
- 清除“正在访问临界区”的标志
- 剩余区
- 代码中的剩余代码
访问规则
- 空闲则入
- 没有进程在临界区时,任何进程可进入
- 忙则等待
- 如果有进程在临界区时,任何进程都不可进入临界区
- 有限等待
- 等待进入临界区的进程不能无限的被动等待
- 让权等待(可选)
- 不能进入临界区的进程,应该放弃CPU的使用
同步实现方法
1、禁用硬件中断
- 没有中断,也没有上下文的切换,没有并发执行
- 硬件将中断处理延迟到中断被启用之后
- 使用计算机体系结构的指令集提供的方法实现
缺点
- 禁用中断之后,进程无法被停止
- 可能导致饥饿
- 整个系统都会为此停下来
- 临界区可能很长
- 无法确定响应中断所需的时间
2、软件方法
共享变量
- 线程之间可以通过一些共有的变量去同步它们的行为
PS:以下为代码描述
do{ enter section //进入区 critical section exit section //退出区 reminder section}while(1);
//第一种做法
int turn = 0;turn == i ; //表示允许进入临界区的线程的代号//线程的代码do{ while(turn!=i); critical section turn =j; reminder section;}while(1);
缺点
- 满足“忙则等待”,但不满足“空闲则入”
//第二种做法
int falg[2];flag[0]=flag[1]=0;flag[i] == 1 ; //表示线程i是否在临界区//线程的代码do{ while(flag[j]==1); flag[i]=1; critical section flag[i]=0; reminder section;}while(1);
缺点
- 满足“空闲则入”,不满足“忙则等待”
//第三种做法
int falg[2];flag[0]=flag[1]=0;flag[i] == 1 ; //表示线程i想要进入在临界区//线程的代码do{ flag[i] = 1 ; while(flag[j] == 1); critical section flag[i] = 0; reminder section;}while(1);
缺点
- 满足“忙则等待”,但不满足“空闲可入”
//Peterson算法
int turn;//表示该谁进入临界区boolean flag[];//表示进程是否准备好进入临界区do{//进入区代码flag[i] = true;turn = j;while(falg[j] && turn == j); criticla section//退出区代码flag[i] = false; remainder section}while(1);
//Dekkers算法
int turn;//表示该谁进入临界区boolean flag[];//表示进程是否准备好进入临界区do{//进入区代码flag[i] = true;while(flag[j] == true ){ if(turn != i ){ flag[i] = false; while(tuen != i){} flag[i] = true; }}criticla section//退出区代码flag[i] = false;turn = j; remainder section}while(1);
3、基于软件的解决方法的分析
- 复杂
- 需要两个进程间的共享数据项
- 需要程序编写者极高的素养
- 需要忙等待
- 浪费CPU时间
更高级的抽象方法
描述
- 硬件提供了一些同步原语
- 中断禁用,原子操作指令等
- 操作系统提供了更高级的编程抽象来简化进程同步
- 例如:锁、信号量
- 用硬件原语来构建
锁(lock)
- 锁是一种抽象的数据结构
- 一个二进制变量(锁定/解锁)
- Lock::Acquire
- 锁被释放前一直等待,然后得到锁
- Lock::Release
- 释放锁,唤醒任何等待的线程
- 在进入区得到锁,在退出区释放锁
原子操作指令
- 现代CPU体系都提供一些特殊的原子操作指令,保证在原子操作时不会被中断
- 测试和置位操作TS(Test-and-Set)(三个操作为一整个不会被中断的原子操作)
- 从内存中读取值
- 测试该值是否为1
- 内存单元值设置为1
- 交换指令
- 交换内存中的两个值
使用TS指令实现自旋锁(spinlock)
class Lock{ int value = 0; };Lock::Acquire() { while(test-and_set(value)) ;//等待}Lock::Release() { value = 0;}
- 缺点: 线程在等待的时候也会消耗CPU资源
无忙等待锁(基于上面的自旋锁的忙等待进行优化)
- 在无法进入临界区的时候,将自己的CPU使用权交出
class Lock{ int value = 0; waitQueue q;};Lock::Acquire() { while(test-and_set(value)){ add this TCB to waitQueue q;//将需要进行CPU使用的进程或线程放入等待队列 schedule(); }}Lock::Release() { value = 0; remove one thread t from waitQueue q;//将等待队列中的线程取出 wakeup(t);//唤醒等待线程队列中的线程}
- 优点
- 适用于单处理器或者共享主存的多处理器中任意数量的进程同步
- 简单且容易证明
- 支持多临界区
- 缺点
- 忙等待锁会占用CPU时间
- 可能导致饥饿
- 进程离开临界区的时候有多个进程等待的情况
- 可能会出现死锁
- 如下为一种情况:
- 拥有临界区的低优先级进程没有获得CPU资源(没法执行完自己的工作)
- 请求访问临界区的高优先级进程获得处理器资源并等待临界区(如果是执行忙等待)
- 导致两者都没法执行自己的任务,造成死锁
- 如下为一种情况:
同步方法总结
锁是一种高级的同步抽象方法
- 互斥可以使用锁来支持
- 需要硬件的支持才能使用
常用的三种同步实现方法
- 禁用中断(仅限于单处理器)
- 软件方法(复杂)
- 原子指令操作(单处理器、多处理器均可实现)
信号量
简介
- 信号量是操作系统提供的一种协调
- 软件同步是平等线程中的一种同步协商机制
- OS是管理者,地位高于进程
- 由操作系统来规定谁使用临界区资源
- 用信号量表示资源的数量
- 有Dijkstra在60年代提出
- 早期操作系统的主要同步机制
描述
- 信号量是一种抽象的数据类型
- 由一个整型变量(sem)和两个原子操作组成
- P(Prolaag(荷兰语:尝试减少))
- sem减一
- 如果sem<0,进入等待,否则继续
- V(Verhoog(荷兰语:增加))
- sem加一
- 如果sem<=0,唤醒一个处于等待状态的进程
- 信号量是被保护的整型变量
- 初始化完成后,只能通过P()和V()操作修改
- 由操作系统保证,PV操作是原子操作
- P()可能被阻塞,V()不会被阻塞
- 通常假定信号量是“公平的”
- 线程不会无限期的被阻塞在P()操作
- 假定信号量等待是按照先进先出排队(自旋锁无法实现先进先出)
信号量的实现
class Semaphore{ int sem; waitQueue q;};Semaphore::P(){ sem--; if(sem < 0){ add this thread t to q; block(p);//阻塞P }}Semaphore::V(){ sem++; if(sem<=0){ remove a thread t from q; wakeup (t); }}
信号量的分类
- 可分为两种信号量
- 二进制信号量:资源数量为0或1
- 资源信号量:资源可为任何非负数
- 两者等价
- 基于一个可以实现另一个
- 信号量的使用
- 互斥访问
- 临界区的互斥访问控制
- 条件同步
- 线程间的事件等待
- 互斥访问
用信号量实现临界区的互斥访问
mutex = new Semaphore(1);//设置资源信号量,初始值为1mutex->P();Criticla Section;mutex->V();
- 必须成对使用P()和V()
- P()保证互斥访问临界资源
- V()保证在使用后释放临界资源
PV 解决生产者消费者问题
生产者 –> 缓冲区 –> 消费者
- 问题分析
- 任何时刻只能有一个线程操作缓冲区(互斥访问)
- 缓存区空时,消费者必须等待生产者(条件同步)
- 缓冲区满时,生产者必须等待消费者(条件同步)
- 用信号量描述每个约束
- 二进制信号量 mutex
- 资源信号量 fullBuffers
- 资源信号量 emptyBuffers
//信号量class BoundedBuffer{ mutex = new Semaphore(1); fullBuffers = new Semaphore(0); emptyBuffers = new Semaphore(n);//资源};//生产者BoudedBuffer::Deposit(c){ emptyBuffers->p(); mutex->P(); Add c to the buffer; mutex->V(); fullBuffers->V();}//消费者BoudedBuffer::Deposit(c){ fullBuffers->p(); mutex->P(); Remove c from the buffer; mutex->V(); emotyBuffers->V();}
缺点和困难
- 读写代码很难
- 程序员需要能运用信号量机制
- 容易出错
- 使用的信号量已经被另一个线程占用
- 忘记释放信号量
- 不能处理死锁问题
管程
描述
- 管程是一种用于多线程互斥访问共享资源的程序结构
- 采用面向对象方法,简化了线程间的同步控制
- 任一时刻最多只有一个线程执行管程代码
- 正在管程中的线程可临时放弃管程的互斥访问,并等待时间出现时回复
- 管程的使用
- 在对象/模块中,收集相关共享数据
- 定义访问共享数据的方法
组成
- 一个锁
- 控制管程代码的互斥访问
- 0或者多个条件变量
- 管理共享数据的并发访问
条件变量
- 条件变量是管程内的等待机制
- 进入管程的线程因为资源被占用而进入等待状态
- 每个条件变量表示一种等待原因,对应一个等待队列
- wait()操作
- 将自己阻塞在等待队列中
- 唤醒一个等待着管程释放的互斥访问
- signal()操作
- 将等待队列中的一个线程唤醒
- 如果等待队列为空,则等同空操作
条件变量实现
class Condition{ int numWaiting = 0; WaitQueue q;};Condition::Wait(lock){ numWaiting++; //等待数加一 add this thread t to q;//放入等待队列 release(lock);//释放管程使用权 schedule();//执行调度 require();//完成调度后,请求管程的访问权}Condition::Signal(){ if(numWaiting > 0){ remove a thread t from q; wakeup(t); numWaiting--; }}
用管程解决生产者- 消费者问题
代码实现:
class BoundedBuffer{...Lock lock;int count=0;Condition notFull,notEmpty;};//生产者BoudedBuffer::Deposit(c){ lock->Acquire(); while(count -- ){ notFull.Wait(&lock); } Add c to the buffer; count++; notEmpty.Signal(); lock->Release();}//消费者BoudedBuffer::Deposit(c){ lock->Acquire(); while(count -- ){ notEmpty.Wait(&lock); } Remove c from the buffer; count -- ; notFull.Signal(); lock->Release();}
管程条件变量的释放处理方式
根据方式不同可分为:
- Hansen管程
- Hoare管程
阅读全文
0 0
- 【操作系统学习】(二)同步互斥
- c# 多线程学习笔记(二)互斥,同步
- 操作系统实验--同步互斥
- 【操作系统】互斥与同步
- Windows多线程学习(二)多线程互斥同步 关键段解决互斥问题
- 操作系统学习笔记——进程互斥与同步
- 操作系统学习笔记(8) 互斥和同步的实现算法
- 操作系统学习笔记(9) 互斥和同步的信号量算法
- 操作系统学习笔记(10) 互斥和同步的经典问题
- 操作系统学习笔记(11) 互斥和同步的解决方案-消息传递
- 操作系统学习笔记(13) 互斥与同步的经典问题 -哲学家进餐问题
- 操作系统精髓与设计原理学习笔记五:并发性(互斥和同步)
- 互斥与同步(二)
- 操作系统概论(3) --进程的同步与互斥
- 操作系统 进程/线程 同步与互斥
- 操作系统的互斥与同步
- 操作系统读书笔记-互斥和同步
- 操作系统之同步与互斥
- CSS组件-搜索框
- 前中序重建二叉树python实现
- java之面向对象2
- Java注解(Annotation)详解(二)——自定义注解
- [博弈]Atcoder AGC002 E. Candy Piles
- 【操作系统学习】(二)同步互斥
- 【嵌入式学习历程12】Linux文件编程
- Android自定义adapter
- 雾计算与边缘计算的区别
- Qt 全局热键的实现
- Leetcode 80.
- Codeforces 429B B. Working out
- jersey上传图片到另外的服务器
- 简单分析zookeeper的工作原理