计算机操作系统同步互斥
1 背景
在计算机系统里面, 多道程序设计是现代操作系统的重要特征, 且并行起到了很大的作用, 所以操作系统抽象出来了线程/进程的概念用来支持多道程序设计, 同时, 各个进程之间需要进行交互, CPU也需要进行调度来支持多进程. 多进程会涉及到共享资源访问的问题, 如果操作系统调度不当, 就可能出现饥饿, 死锁等问题. 以下是独立进程/线程与合作进程/线程(进程/线程间会有交互, 并共享资源等)的对比:
独立的进程/线程:
- 不和其他进程/线程共享资源或状态
- 确定性: 输入状态决定结果
- 可重现: 能够重现起始条件, I/O
- 调度顺序不重要
合作进程/线程:
- 在多个线程中共享状态
- 不确定性
- 不可重现
不确定性和不可重现意味着bug可能是间歇性发生的, 但是计算机/设备需要合作, 其优点如下:
- 共享资源
- 一台电脑, 多个用户
- 一个银行存款余额, 多台ATM
- 嵌入式系统(机器人控制: 手臂与手的协调)
- 加速
- I/O操作和计算可以重叠
- 多处理器 - 将程序分成多个部分并行执行
- 优化
- 将大程序分解成小程序(以编译为例, gcc会调用cpp, cc1, cc2, as, ld)
- 使系统易于扩展
而我们希望的多进程/线程的运行方式为:
- 无论多进程/线程的指令序列怎样交替执行, 程序都必须正常工作
- 多线程程序具有不确定性和不可重现的特点
- 不经过专门设计, 调试难度很高
- 不确定性要求并行程序的正确性
- 先思考清楚问题, 把程序的行为设计清楚
- 切忌急于着手编写代码, 碰到问题再调试
2 一些概念
竞态条件:
系统缺陷: 结果依赖于并发执行或者事件的顺序/时间
- 不确定性
- 不可重现
如何避免竞态条件? 可以让执行指令不被打断
原子操作:
原子操作是指一次不存在任何中断或者失败的执行:
- 该执行成功结束
- 或者根本没有执行
- 并且不应该发现任何部分执行的状态
实际上操作往往不是原子的
- 有些看上去是原子操作, 实际上不是
- 连 ++这样的简单语句, 实际上是由3条指令构成的
- 有时候甚至连单条机器指令都不是原子的
临界区:
临界区是指进程中的一段需要访问共享资源并且当另一个进程处于相应代码区域时便不会被执行的代码区域.
互斥:
当一个进程处于临界区并访问共享资源时, 没有其他进程会处于临界区并且访问任何相同的共享资源.
死锁:
两个或以上的进程, 在相互等待完成特定任务, 而最终没法将自身任务进行下去.
饥饿:
一个可执行的进程, 被调度器持续忽略, 以至于虽然处于可执行状态却不被执行.
3 临界区
3.1 临界区中执行的属性
临界区中执行需要有一些属性保证临界区的顺利执行:
- 互斥: 同一时间临界区中最多存在一个线程
- progress: 如果一个线程想要进入临界区, 那么它最终会成功(也就是说不会让一个想要进入临界区的线程一直等待, 总会进入的)
- 有限等待: 如果一个线程 i 处于入口区, 那么在 i 的请求被接受之前, 其他线程进入临界区的时间是有限制的(只会等待有限制的时间)
- 无忙等待(可选): 如果一个进程在等待进入临界区, 那么在它可以进入之前会被挂起(就是不要用while循环一直在检查是否能进入, 会消耗CPU资源)
3.2 三种针对临界区属性的保护方法
3.2.1 禁用硬件中断
在临界区里面进程执行是屏蔽中断的: 没有中断, 没有上下文切换, 因此没有并发.
- 硬件将中断处理延迟到中断被启用之后
- 大多数现代计算机体系结构都提供指令来完成
进入临界区
- 禁用中断
离开临界区
- 开启中断
利用中断来确保临界区顺利执行的缺点:
- 一旦中断被禁用, 线程就无法被停止
- 整个系统都会为你停下来
- 可能导致其他线程处于饥饿状态
- 要是临界区可以任意长怎么办
- 无法限制相应中断所需的时间(可能存在硬件影响)
- 在多CPU的情况下, 只禁用一个CPU的中断无法解决问题
因此需要小心使用
3.2.2 基于软件的解决方法
满足互斥, 但是有时候不满足progress.
3.2.3 更高级的抽象
更高级的抽象方法是需要一定的基础的, 这个基础就是硬件要提供一些原语支持:
- 比如像中断禁用, 原子操作指令等
- 大多数现代体系结构都这样
有了上面的基础, 操作系统就可以利用这些硬件支持来提供更高级的编程抽象来简化多进程/多线程的并行编程:
- 比如像锁, 信号量
- 这些高级的用于并行编程的抽象概念是通过上面说的硬件原语构建的
下面用锁来举例:
锁是一个抽象的数据结构, 也就是上面说的高级的用于并行编程的抽象概念, 具有以下特点:
- 一个二进制状态(锁定/解锁), 两种方法
- Lock::Acquire() --- 锁被释放前一直等待, 然后得到锁
- Lock::Release() --- 释放锁, 唤醒任何等待的进程
如果使用锁来处理临界区的话, 可以如下:
lock_next_pid->Acquire(); new_pid = next_pid++; lock_next_pid->Release();
这样就保证了临界区的顺利执行.
那么lock的两个接口是怎么实现的呢, 前面说过, 是通过现代计算机的体系结构提供的硬件支持, 大索数现代体系结构都提供特殊的原子操作指令:
- 通过特殊的内存访问电路
- 针对单处理器和多处理器
比如硬件提供了以下两种原子操作指令:
1. Test - and - Set测试和置位
- 从内存中读取值
- 测试该值是否为1(然后返回真或假)
- 该内存值置为1
其简单实现如下:
boolean TestAndSet(boolean *target) {boolean rv = *target;*target = TRUE;return rv; }
2. 交换
- 交换内存中的两个值
简单实现如下:
void Exchange(boolean *a, boolean *b) {boolean temp = *a;*a = *b;*b = temp; }
有了以上两种硬件提供的原子操作指令: TestAndSet以及Exchange之后, 我们就可以利用这两条指令来设计锁的Acquire和Release接口了:
1. 用TestAndSet实现
class Lock {int value = 0; }Lock::Acquire(){while(TestAndSet(&value)){} }Lock::Release() {value = 0; }
设计思路:
- Acquire: 初始化的时候value为0, 代表没有其他进程/线程进入临界区, 这时候Acquire里面调用TestAndSet返回值是0, 但是value被设置成了1, 这样就可以直接退出, 从而进入临界区执行, 这时候如果其他进程调用这个lock的Acquire, 这时候value是1了, TestAndSet返回值是1, value设置为1, 这样就会一直在while条件里面循环.(忙等)
- Release: 把value置为0, 这样就会让其他调用Acquire的线程中TestAndSet的返回值置为0从而退出while循环从而继续执行
方法评价:
这种方法是忙等, 会占用CPU资源. 可以使用在等待过程中挂起该线程的方式, 并唤醒其他等待进程的方式. 但是这种方式需要上下文切换, 如果预计等待时间较长的话, 可以选择这种方式.
2. 用Exchange实现
简单实现如下:
int lock = 0; //设置一个全局初始变量 //以下是线程Ti想要进入临界区的操作 int key; do {key = 1;while (key == 1)exchange(&lock, &key);//然后执行临界区代码lock = 0;//执行剩余代码 }
设计思路:
- 当第一个线程想要进入临界区之前, 先把key设置为1, 然后判断key = 1的话, 就执行交换key和lock, 这时lock被置为1, 因为是第一个线程, 所以此时key为0, 退出循环, 执行临界区代码.
- 当其他线程在第一个线程执行临界区代码过程中想要进入临界区, 执行exchange之后, 因为lock值在有线程在临界区执行时都会是1, 所以key一直都是1, 也就会一直在while中循环.
- 在临界区线程执行完毕后, 会把lock置为0, 让其他线程进入.
优缺点:
- 优点: 适用于单处理器或者共享主存的多处理器中任意数量的进程, 简单并且容易证明, 可以用于支持多临界区.
- 缺点: 忙等待消耗处理器时间, 当进程离开临界区并且多个进程在等待的时候可能导致饥饿. 死锁: 如果一个低优先级进程拥有临界区并且一个高优先级进程也需求, 那么高优先级进程会获得处理器并等待临界区, 最终结果是高优先级进程忙等占用CPU, 低优先级进程占用临界区却得不到CPU资源进而无法释放锁, 导致两个进程的临界区代码都无法正常执行.
计算机操作系统同步互斥相关推荐
- 操作系统 - 同步互斥
并发进程的正确性 独立进程 不和其他进程共享资源或状态 确定性 ==> 输入状态决定结果 可重现 ==> 能够重现起始条件 调度顺序不重要 并发进程 在多个进程间有资源共享 不确定性 不可 ...
- 计算机操作系统专题一:多道环境下进程同步与互斥制约关系的学习
1. 问题描述 设自行车生产线上有一只箱子,其中有N个位置(N≥3),每个位置可存放一个车架或一个车轮,又设有三名工人,其活动分别为: 2. 问题分析(包括涉及的知识点.制约关系分析.问题的解决思路等 ...
- 计算机操作系统——经典进程的同步问题
计算机操作系统--信号量机制与经典进程的同步问题 信号量机制 随着发展,信号量从整型信号量经记录型信号量,进而发展为"信号量集"机制. 一般来说,信号量的值与相应的资源的使用情况有 ...
- 操作系统中消费者与生产者的同步互斥问题
在操作系统中,我们有进程,进程会占用资源,有些资源是可以共享的,但有些资源是只允许一个占用,不能共享,只有当占用的线程用完释放后,下一个需要用的线程才可以申请使用,这样的资源便是临界资源.属于临界资源 ...
- 【考研】操作系统:2019年真题43(同步互斥问题)
前言 解决同步互斥问题的思路,源于对王道讲解的总结笔记 同类型题目: [考研]操作系统:2015年真题45(同步互斥问题)_住在阳光的心里的博客-CSDN博客 [考研]操作系统:2014年真题47(同 ...
- 国防科大计算机考研大纲,2022年国防科技大学F1003计算机操作系统考研大纲及参考书目...
2022年研究生入学考试自命题科目考试大纲 科目代码:F1003 科目名称:计算机操作系统 一.参考书目 1.<操作系统教程>(第5版),费祥林,高等教育出版社,2014年. 2.< ...
- 面试「计算机操作系统」知识点大集合!
作者:CyC2018 链接:https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/计算机操作系统.md 一.概述 基本特征 1. 并发 ...
- 计算机操作系统安装实验报告,计算机操作系统实验报告.doc
计算机操作系统实验报告.doc (12页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 14.9 积分 计算机操作系统实验报告实验一一.实验目的 在单处理 ...
- 简单计算机面试题库及答案_460道Java后端面试高频题答案版【模块六:计算机操作系统】...
写在前面 1. 计算机操作系统和计算机网络是每个后端开发工程师必须掌握的知识.因为你写的代码最终都是要在操作系统里跑的,弄懂操作系统的原理对你编写高质量代码.调优.排故都有很大的帮助.在这里说一下我作 ...
最新文章
- Android --- android中Invalidate和postInvalidate的区别
- Ribbon 与 Nginx 区别
- crontab命令linux,crontab命令
- JavaScript实现使用DisjointSet 检测无向循环算法(附完整源码)
- java凌晨12点_java - JAVA如果我在每天中午12点之后安排我的时间表,会发生什么? - SO中文参考 - www.soinside.com...
- mysql分析sql语句性能_sql语句执行性能分析
- tc/traffic control 网络控制工具
- 从事前端开发必须要了解的CSS原理
- Kafka万亿级消息实战解决方案干货
- 【读书笔记】—— 《从 0 到 1》
- java反射 获取方法_java反射——获取类的方法信息
- Python基础090:解决jupyter notebook无法自动跳转chrome浏览器的问题
- 即插即用!Batch Transformer
- [置顶] 以盛唐气象,浇胸中块垒:唐诗与宋词学习笔记汇总目录
- 幼麟棋牌技术分享系列:H5棋牌游戏加载速度优化
- 基于FPGA的数字识别实现
- Java实现XLS和XLSX之间的相互转换
- WebDAV之葫芦儿·派盘+Orgzly
- 获取linux命令硬盘信息,Linux 下使用命令获取硬盘信息
- 线性代数库 Armadillo 学习笔记
热门文章
- 视图插入数据_用EXCEL作数据分析--招聘信息
- )类 新建javafx程序时_第三章 第一个OpenCV的JavaFX应用程序.md
- OpenCV学习笔记(七):形态学morpholgy(1):腐蚀/膨胀:enrode(),dilate()
- C++中的STL--结构概览
- CornerNet: Detecting Objects as Paired Keypoints
- Android实现支持缩放平移图片
- Python 并行分布式框架 Celery
- Java并发编程实战~软件事务内存
- Inside Class Loaders
- 1030 Travel Plan(甲级)