操作系统信号量和管程

1 背景

同步互斥回顾:

并发问题: 竞争条件(竞态条件)

  • 多程序并发存在大量问题

同步

  • 多线程共享公共数据的协调执行
  • 包括互斥与条件同步
  • 互斥: 在同一时间只有一个线程可以执行临界区

确保线程同步

  • 需要高层次的编程抽象(如: 锁)
  • 从底层硬件支持编译

2 信号量

信号量是抽象数据类型

  • 信号量用一个整形(sem)表示, 具有两个原子操作
  • P(): sem减1, 如果sem < 0, 等待, 否则继续
  • V(): sem加1, 如果sem <= 0, 唤醒一个等待的P

3 信号量使用

3.1 信号量的一些属性

  • 信号量是整数(有符号)
  • 信号量是被保护的变量
  • 初始化完成后, 唯一改变一个信号量的值的办法是通过P()和V()
  • 操作必须是原子
  • P()能够阻塞, V()不会阻塞
  • 我们限定信号量是"公平的"
  • 没有线程被阻塞在P()仍然阻塞如果V()被无限频繁调用(在同一个信号量)
  • 在实践中, FIFO(先阻塞先被唤醒)经常被使用(如果只能唤醒一个进程)(忙等的锁没有这种先等先唤醒机制)

 3.2 信号量的实现简介

信号量实现一般有两种:

  • 二进制信号量: 可以是0或1
  • 一般/计数信号量: 可取任何非负值
  • 两者互相表现(给定一个可以实现另一个)

信号量可以用在两个方面:

  • 互斥
  • 条件同步(调度约束 -- 一个线程等待另一个线程的事情发生)

用二值信号量实现互斥功能:

mutex = new Semaphore(1);          //初始化一个值为1的信号量作为一个锁mutex->P();                        //信号量减1
//执行临界区代码, 此时如果有其他线程要进入临界区, 也需要mutex->P(), 也就会让信号量的值为-1,
//也就会被阻塞
mutex->V();                        //信号量加1

用二值信号量实现同步:

condition = new Semaphore(0);// Thread A
condition->P()                 //A线程等待condition->V()// Thread B
condition->V()                 //B线程唤醒, 也就是同步

用计数信号量还可以实现生产者 - 消费者问题:

有界缓冲区(buffer)的生产者 - 消费者问题, 有以下性质:

  • 一个或多个生产者产生数据将数据放在一个缓冲区(buffer)里
  • 单个消费者每次从缓冲区取出数据
  • 在任何一个时间只有一个生产者或消费者可以访问该buffer

这个问题用互斥(锁机制)是不够的.

而且具有正确性要求:

  • 在任何一个时间只能有一个线程操作buffer(互斥)
  • 当buffer为空, 消费者必须等待生产者(调度/同步约束)
  • 当缓存区满, 生产者必须等待消费者(调度同步约束)

根据以上要求和性质:

如果每个约束用一个单独的信号量, 有以下信号量设计:

  • 二进制信号量互斥
  • 一般信号量fullBuffers
  • 一般信号量emptyBuffers 

代码设计:

class BoundedBuffer{mutex = new Semaphore(1);fullBuffers = new Semaphore(0);emptyBuffers = new Semaphore(n);
}BoundedBuffer::Deposit(c) {emptyBuffers->P();         //需保证buffer还有空闲, 如果无空闲, 则等待mutex->P();                //要保证对buffer的操作的互斥性Add c to the buffer;mutex->V();fullBuffers->V();          //有数据进入
}BoundedBuffer::Remove(c) {fullBuffers->P();          //需保证buffer有数据, 如果无数据, 则等待mutex->P();                //要保证对buffer的操作的互斥性Remove c from buffer;mutex->V();emptyBuffers->V();         //取出数据
}

4 信号量实现

针对信号量的P()和V()操作的实现, 需要使用硬件原语:

  • 禁用中断
  • 原子指令(Test - And - Set)

具体实现:

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);}
}

实现中需要注意的细节:

  • 信号量的双用途
  • 互斥和条件同步
  • 但等待条件是独立的互斥
  • 读/开发代码比较困难
  •  程序员必须非常精通信号量
  • 容易出错
  • 使用的信号量已经被另一个线程占用
  • 忘记释放信号量 
  • 不能够处理死锁问题

5 管程

管程的目的:

分离互斥和条件同步的关注

什么是管程:

  • 一个锁: 指定临界区
  • 0或者多个条件变量: 等待/通知信号量用于管理并发访问的共享数据

一般方法:

  • 收集在对象/模块中的相关共享数据
  • 定义方法来访问共享数据

简单步骤:

  1. 获取进入管程的锁从而进入管程
  2. 管程中对共享资源进行操作
  3. 当需要等待时候, 信号量调用wait自身睡眠并把进入管程的锁进行释放
  4. 当资源得到满足, 信号量会调用signal从而唤醒正在这个信号量睡眠的线程

条件变量的实现:

class Condition{int numWaiting = 0;WaitingQueue q;
}Condition::Waiting(lock){numWaiting ++;Add this thread to q;release(lock);schedule();                    //need mutex, 选择下一个线程去执行require(lock)
}Condition::Signal(){if (numWaiting > 0) {Remove a thread t from q;wakeup(t);                      //need mutex, 把该线程置为ready状态numWaiting --;}
}

管程实现生产者 - 消费者问题:

  • 需要维持每个条件队列
  • 线程等待的条件等待signal()
class BoundedBuffer{Lock lock;int count = 0;Condition notFull, notEmpty;
}BoundedBffer::Deposit(c){lock->Acquire();while(count == n)notFull.Wait(&lock);Add c to the buffer;count ++;notEmpty.Signal();lock->Release();
}BoundedBuffer::Remove(c){lock->Acquire();while(count == 0)notEmpty.Wait(&lock);Remove c from buffer;count --;notFull.Signal();lock->Release();
}

6 经典同步问题

6.1 读者 - 写者问题

问题动机:

共享数据的访问

两种类型的使用者:

  • 读者: 不需要修改数据
  • 写者: 读取和修改数据

问题的约束:

  • 允许同一时间有多个读者, 但在任何时候只有一个写者
  • 当没有写者时读者才能访问数据
  • 当没有读者和写者时写者才能访问数据
  • 在任何时候只能有一个线程可以操作共享变量

设计思路:

  • 需要有以下共享数据:
  • 数据集, 也就是要进行读或者写的数据
  • 信号量CountMutex初始化为1
  • 信号量WriteMutex初始化为1
  • 整数Rcount初始化为0(正在执行读操作的数量)

 6.1.1 读者优先

基于读者优先策略的方法: 只要有一个读者处于活动状态, 后来的读者都会被接纳. 如果读者源源不断出现的话, 那么写者就始终处于阻塞状态.

  • 写操作: Writer
sem_wait(WriteMutex);        //相当于WriteMutex->P(), 保证只有一个线程在执行写操作, 以及在写操作过程中没有读操作write;sem_post(WriteMutex);        //相当于WriteMutex->V()
  • 读操作: Reader
sem_wait(CountMutex);            //保证对Rcount操作的互斥性
if(Rcount == 0)sem_wait(WriteMutex);        //说明此时没有读操作, 是有可能有写操作的, 所以需要等待写操作结束
++ Rcount;
sem_post(CountMutex);read;sem_wait(CountMutex);
-- Rcount;if (Rcount == 0)sem_post(WriteMutex);sem_post(CountMutex);

6.1.2 写者优先

基于写者优先策略的方法: 一旦写者就绪, 那么写者会尽可能快地执行写操作. 如果写者源源不断地出现的话, 那么读者就始终处于阻塞状态.

需要的变量:

  • AR = 0                        //active readers正在执行读操作的线程个数
  • AW = 0                        //active writers正在执行写操作的线程个数
  • WR = 0                        //waiting readers正在等待读操作的线程个数
  • WW = 0                        //waiting writers正在等待写操作的线程个数
  • Condition okToRead
  • Condition okToWrite
  • Lock lock

读操作:

Read(){//wait until no writersstartRead();read database;//check out - wake up waiting writers;doneRead();
}startRead(){lock.Acquire();while ((AW + WW) > 0) {                   //如果有写者, 写者优先WR ++;okToRead.wait(&lock);WR --;}AR ++;lock.release();
}doneRead(){lock.Acquire();AR --;if (WW > 0 && AR == 0) {                  //需要唤醒等待的写者okToWrite.signal();}lock.Release();
}

写操作:

Write() {//Wait until no readers/writersstartWrite();write database;//check out - wake up waiting readers/writers;doneWrite();
}startWrite(){lock.Acquire();while((AR + AW) > 0) {          //需要等待正在写或者读的操作结束后WW ++;okToWrite.wait(&lock);WW --;}lock.Release();
}doneWrite() {lock.Acquire();AW --;if (WW > 0) {okToWrite.signal();} else if (WR > 0) {okTORead.broadcast();}lock.Acquire();
}
  • 6.2 哲学家就餐问题

问题描述(1965年由Di jkstra首先踢出并解决):

5个哲学家围绕一张圆桌而坐, 桌子上放着5支叉子, 每两个哲学家之间放一支; 哲学家的动作包括思考和进餐, 进餐时需要同时拿起他左边和右边的两支叉子, 思考时则同时将两支叉子放回原处. 如何保证哲学家们的动作有序进行? 如: 不会出现有人永远拿不到叉子.

共享数据:

  • bowl of rice(data set)
  • Semaphore fork[5] initialized to 1

思路:

  • 必须有数据结构, 来描述每个哲学家的当前状态
#define N           5                //哲学家个数
#define LEFT        i                //第i个哲学家的左邻居
#define RIGHT       (i + 1) % N      //第i个哲学家的右邻居
#define THINKING    0                //思考状态
#define HUNGRY      1                //饥饿状态
#define EATING      2                //进餐状态
int state[N];                        //记录每个人的状态
  • 该状态是一个临界资源, 每个哲学家对它的访问应该互斥地进行 -- 进程互斥
semaphore mutex;                     //互斥信号量, 初值1
  • 一个哲学家吃饱后, 可能要唤醒它的左邻右舍, 两者之间存在着同步关系 -- 进程同步
semaphore s{N};                      //同步信号量, 初值0
  • 函数philosopher定义(哲学家流程定义)
void philosopher(int i){        //i的取值: 0到N - 1, 代表着第几个哲学家while(true){                //封闭式循环think();take_forks(i);          //拿到两把叉子或被阻塞eat();put_forks(i);           //把两把叉子放回原处}
}
  • 函数take_forks的定义
void take_forks(int i){                //i的取值: 0到N - 1P(mutex);                          //进入临界区state[i] = HUNGRY;test_take_left_right_forks(i);     //试图拿两把叉子V(mutex);                          //退出临界区P(s[i]);                           //没有叉子便被阻塞
}void test_take_left_right_forks(int i) {if (state[i]     == HUNGRY &&state[LEFT]  != EATING &&state[RIGHT] != EATING) {state[i] = EATING;V(s[i]);                       //通知第i个人可以拿起左右叉子吃饭了}
}
  • 函数put_forks定义
//功能: 把两把叉子放回原处, 并在需要的时候,
//去唤醒左邻右舍
void put_forks(int i){P(mutex);state[i] = THINKING;                //交出两把叉子//提醒左右邻居其相邻状态有改变, 让其从阻塞态恢复, 重新执行循环检查test_take_left_right_forks(LEFT);test_take_left_right_forks(RIGHT);V(mutex);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/505398.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python里元组和列表的共同点和不同点_Python元组与列表的相同点与区别

列表和元组都属于有序序列&#xff0c;支持使用双向索引访问其中的元素、使用内置函数len()统计元素个数、使用运算符in测试是否包含某个元素、使用count()方法统计指定元素的出现次数和index()方法获取指定元素的索引。虽然有着一定的相似之处&#xff0c;但列表和元组在本质上…

操作系统中死锁避免算法 --- 银行家算法

1. 背景 在银行系统中, 客户完成项目需要申请贷款的数量是有限的, 每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量, 在满足所有贷款要求并完成项目时, 客户应及时归还. 银行家在客户申请的贷款数量不超过自己拥有的最大值时, 都应尽量满足客户的需要. 在这样的描…

python字符串对齐_Python - 字符串对齐

字符串对齐 本文地址: http://blog.csdn.net/caroline_wendy/article/details/20463231 Python中, 字符串对齐, 使用ljust(), 左对齐;rjust(), 右对齐; center(), 中间对齐; 也可以修改第三个参数, 修改填充数据, 默认使用空格; 代码如下: # -*- coding: utf-8 -*- # #File: Te…

操作系统中的死锁问题

1 死锁问题概述 一组阻塞的进程持有一种资源等待获取另一个进程所占有的一个资源.例子: 系统有两个磁带驱动器, P1和P2各有一个, 都需要另外一个. 2 系统模型 资源类型: , , ..., 包括CPU cycles, memory space, I/O devices 每个资源类型有实例. 每个进程使用资源过程如下…

chrome控制台如何把vw显示成px_你可能不知道的chrome调试技巧

本文是对常用的chrome调试技巧进行总结整理&#xff0c;如果你没有深入了解过chrome调试工具&#xff0c;此处总有你不知道的惊喜&#xff01;从 Chrome 说起对于大部分人来说&#xff0c;Chrome 可能只是个浏览器&#xff0c;但是对于开发人员来说&#xff0c;它更是一个强大无…

操作系统进程间通信 --- IPC

1. 概述 进程间通信的原因: 进程之间要保持独立, 也需要通信, 保证相对独立性的同时还需要去确保进程间的沟通. 1.1 通信模型 IPC facility提供2个操作: send(message) -- 消息大小固定或者可变receive(message) 如果P和Q想通信, 需要: 在它们之间建立通信链路通过 send/…

gns3中两个路由器分别连接主机然后分析ip数据转发报文arp协议_ARP协议在同网段及跨网段下的工作原理...

前言&#xff1a;ARP协议是在链路层通讯中&#xff0c;已知目标IP地址,但是&#xff0c;仅有IP 地址是不够的&#xff0c;因为IP数据报必须封装成帧才能通过数据链路进行发送&#xff0c;而数据帧必须要有目的MAC地址&#xff0c;每一个网络设备在数据封装前都需要获取下一跳的…

操作系统文件系统

1 基本概念 1.1 文件系统和文件 1.1.1 简述 文件系统: 一种用于持久性存储的系统抽象 在存储器上: 组织, 控制, 导航, 访问和检索数据大多数计算机系统包含文件系统个人电脑, 服务器, 笔记本电脑ipod, Tivo/机顶盒, 手机/掌上电脑google可能是由一个文件系统构成的 文件: 文…

c++代码整洁之道pdf_别再问如何用python提取PDF内容了

作者&#xff1a;陈熹 来源&#xff1a;早起Python大家好&#xff0c;在之前的办公自动化系列文章中我们已经详细介绍了如何使用python批量处理PDF文件&#xff0c;包括合并、拆分、水印、加密等操作。今天我们再次回到PDF&#xff0c;详细讲解如何使用python从PDF提取指定的信…

计算机操作系统学习

1 概述 操作系统职能完成对硬件的管理和控制 1.1 操作系统需要关注的 计算机硬件有CPU, 内存, 磁盘, 声卡, 网卡等等, 所以操作系统关注CPU进程线程的调度, 内存管理(物理内存, 虚拟内存), 文件系统管理, 中断处理, IO设备驱动等等. 1.2 操作系统特征 1.2.1 并发和并行 并…

查看文章影响因子的插件_Scholarscope--在新版PubMed中实现基于影响因子的文献筛选...

小编之前介绍过如何在Pubmed上直接显示杂志影响因子的方法&#xff0c;这个方法主要是依托Scholarscope插件&#xff0c;其实除了显示影响因子&#xff0c;这个插件还可以帮助大家根据影响因子筛选文献哦&#xff0c;操作也很简单&#xff0c;只要生成自定义过滤器即可&#xf…

leetcode32 --- longestValidParentheses

1 题目 给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 2 解法 2.1 动态规划方法 维护一个字符串长度的数组cur_max_len, 第i个元素代表以当前(或者)结束的最长有效括号的长度. 这样就会利用动态规划递推…

armitage识别不了漏洞_Shiro RememberMe 漏洞检测的探索之路

前言Shiro 是 Apache 旗下的一个用于权限管理的开源框架&#xff0c;提供开箱即用的身份验证、授权、密码套件和会话管理等功能。该框架在 2016 年报出了一个著名的漏洞——Shiro-550&#xff0c;即 RememberMe 反序列化漏洞。4年过去了&#xff0c;该漏洞不但没有沉没在漏洞的…

css响应式布局_用 CSS Grid 布局制作一个响应式柱状图

最新一段时间比较喜欢玩弄图表&#xff0c;出于好奇&#xff0c;我想找出比较好的用 CSS 制作图表的方案。开始学习网上开源图表库&#xff0c;它对我学习新的和不熟悉的前端技术很有帮助&#xff0c;比如这个&#xff1a;CSS Grid。今天和大家分享我学到的新知识&#xff1a;如…

leetcode33 --- search

1 题目 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], ..., nums[n-1], nums[0], num…

leetcode39 --- combinationSum

1 题目 给定一个无重复元素的数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。 说明&#xff1a; 所有数字&#xff08;包括 target&#xff09;都是正整数。 解集不能包…

vs code 插件_[VSCode插件开发] 由浅入深,带你了解如何打造百万级产品

去年&#xff0c;笔者有幸在微软技术暨生态大会上做了个演讲&#xff0c;主题是“从零开始开发一款属于你的 Visual Studio Code 插件”。演讲内容主要覆盖了VS Code插件开发的四个方面&#xff1a;设计、实现、推广和维护。作为一个开发者&#xff0c;我们往往会把大多数的时间…

leetcode45 --- jump

1 题目 给定一个非负整数数组&#xff0c;你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 假设你总是可以到达数组的最后一个位置。 2 解法 2.1 从终点遍历的方法(时间复杂度)…

python怎么查看网页编码格式_怎么用python爬取网页文字?

用Python进行爬取网页文字的代码&#xff1a;#!/usr/bin/python# -*- coding: UTF-8 -*-import requestsimport re# 下载一个网页url htt用python进行爬取网页文字的代码&#xff1a;#!/usr/bin/python# -*- coding: UTF-8 -*-import requestsimport re# 下载一个网页url htt…

leetcode51 --- solveNQueens

1 题目 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案&#xff0c;该方案中 Q 和 . 分别代…