同步互斥的实现

一、临界区

1.定义:临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程或是进程必须等待,有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用。

2.临界区中存在的属性:

  • 互斥:同一时间临界区中最多存在一个线程;
  • Progress:如果一个线程想要进入临界区,那么它最终会成功(如果无限等待,处于饥饿状态,不妥);
  • 有限等待:如果一个线程i处于入口区,那么在i的请求被接受之前,其他线程进入临界区的时间是有限制的;
  • 无忙等待(可选):如果一个进程在等待进入临界区,那么它可以进入之前会被挂起。

二、管理临界区的方法

1. 禁用硬件中断

采用引荐中断需要考虑时钟中断:时钟中断是控制进程调度的手段之一

  • 没有中断,没有上下文切换,因此没有并发。硬件将中断处理延迟到中断被启用之后;大多数现代计算机体系结构都提供指令来完成。
  • 进入临界区,则禁用中断。
  • 离开临界区,则开启中断。

但是,存在如下问题:

  • 一旦中断被禁用,线程就无法被停止;整个系统都会为你停下;可能导致其他线程处于饥饿状态。
  • 如临界区可以任意长,则无法限制响应中断所需的时间

2. 基于软件的解决方法

例子:假设有两个线程,T0和T1。Ti的通常结构为:

1 do{
2      enter section            //进入区域
3      critical section          //临界区
4      exit section              //离开区域
5      reminder section      //提醒区域
6 }while(1);

线程可能共享一些共有的变量来同步他们的行为。下面设计一种方法,能在有限时间内实现退出/进入页区。

算法前置知识与考虑

  • 共享变量,先初始化
  • int turn = 0 ;
  • turn == i    //表示Ti进入临界区
  • 对于Thread Ti ,代码表示如下:
do{
      while(turn != i );    //如果turn不是i,死循环;直到turn是i,跳出循环
      critical section        //执行临界区代码
      turn = j;              //turn赋为j,退出循环
      reminder section
}while(1);

上述代码满足互斥,即不可能两个线程同时进入临界区。但不满足process,比如T1执行完进入临界区代码后,不再进入临界区程序,转去执行其他任务。而T2执行完临界区代码后,想再次进入临界区,而发现自己在退出临界区时,把turn赋值为了1,因此再执行进入临界区代码时,由于turn=1而不是2,会执行死循环而不能进入临界区。此方法的特点就是必须T1和T2交替执行来改变turn值,才能满足process。

因此再考虑其他方法:

对于有线程0、线程1的情况:

  • int flag[2]; flag[0] = flag[1] = 0
  • flag[i] = 1    //如果等于1,则线程Ti进入临界区
  • 对于Thread Ti,代码如下:
1 do{
2       while (flag[j] == 1);    //如果另一个进程想进来,此进程先谦让一下,自己先循环着
3       flag[i] = 1;    //如果别的进程未准备,则自己赋成1,表示自己要进入临界区
4       critical section
5       flag[i] = 0;
6       reminder section
7 }while(1);

该方法没有实现互斥,如T1执行完前两条代码后,上下文切换到T2,T2执行完前两条代码后 flag[1] == flag[2] ==0 ,所以两个线程都能进入临界区,不满足互斥。

考虑将flag[i] = 1前置,代码如下

1 do{
2        flag[i] = 1;
3        while (flag[j] == 1);
4        critical section
5        flag[i] = 0;
6        reminder section
7 }while(1);

此方法满足互斥,但可能出现死锁,
如T1执行完前两条代码后,上下文切换到T2,T2执行完前两条代码后 flag[1] == flag[2] ==1,两个进程都会进入死循环,所以两个线程都不能进入临界区。

正确的解决办法(Peterson算法)

满足进程Pi和Pj之间互斥的经典的基于软件的解决方法(1981年),Use two shared data items(用上了turn和flag)。

int turn; // 指示该谁进入临界区
boolean flag[]; // 指示进程是否准备好进入临界区

Code for ENTER_CRITICAL_SECTION

1 flag[i] = TRUE;
2 turn = j;
3 while(flag[j] && turn == j);

Code for EXIT_CRITICAL_SECTION

flag[i] = FALSE;

对于进程Pi的算法:

1 do {
2     flag[i] = TRUE;
3     turn = j;
4     while (flag[j] && turn == j);
5     CRITICAL SECTION
6     flag[i] = FALSE;
7     REMAINDER SECTION
8 } while (TRUE);

上述算法能够满足互斥、前进、有限等待三种特性。可以用反证法来证明。

更为复杂的dekker算法
dekker算法的实现如下。

flag[0] := false flag[1] := false := 0 // or 1
do {
    flag[i] = TRUE;
    while flag[j] == true {
      if turn != i {
        flag[i] := false
        while turn != i {}
        flag[i] := TRUE
}
}
CRITICAL SECTION
turn := j
flag[i] = FALSE;
REMAINDER SECTION
} while (TRUE);

针对多进程的Eisenberg and McGuire’s Algorithm

基本思路:对于i进程,如果前面有进程,那么i进程就等待;对于i后面的进程,则等待i。这整体是一种循环。

针对多进程的Bakery算法
N个进程的临界区:

  • 进入临界区之前,进程接受一个数字;
  • 得到的数字最小的进入临界区;
  • 如果进程Pi和Pj收到相同的数字,那么如果i小于j,Pi先进入临界区,否则Pj先进入临界区;
  • 编号方案总是按照枚举的增加顺序生成数字。

总结

  • Dekker算法(1965):第一个针对双线程例子的正确解决方案;
  • Bakery算法(Lamport 1979):针对n线程的临界区问题解决方案。
  • 算法是复杂的:需要两个进程间的共享数据项;
  • 需要忙等待(死循环):浪费CPU时间;
  • 没有硬件保证的情况下无真正的软件解决方案:Peterson算法需要原子的LOAD和STORE指令。

方法3:更高级的抽象

原文地址:https://www.cnblogs.com/cjsword/p/12194448.html

时间: 2024-07-31 02:35:38

同步互斥的实现的相关文章

Linux中四种进程或线程同步互斥控制方法

原文地址:http://blog.itpub.net/10697500/viewspace-612045/ 一.Linux中 四种进程或线程同步互斥的控制方法: 1.临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问. 2.互斥量:为协调共同对一个共享资源的单独访问而设计的. 3.信号量:为控制一个具有有限数量用户资源而设计. 4.事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始. 二.临界区(Critical Section) 保证在某一时刻只有一个线程

入门级的按键驱动——按键驱动笔记之poll机制-异步通知-同步互斥阻塞-定时器防抖

文章对应视频的第12课,第5.6.7.8节. 在这之前还有查询方式的驱动编写,中断方式的驱动编写,这篇文章中暂时没有这些类容.但这篇文章是以这些为基础写的,前面的内容有空补上. 按键驱动——按下按键,打印键值: 目录 概要 poll机制 异步通知 同步互斥阻塞 定时器防抖 概要: 查询方式: 12-3 缺点:占用CPU99%的资源.中断方式:12-4 缺点:调用read函数后如果没有按键按下,该函数永远不会结束,一直在等待按键按下. 优点:使用到了休眠机制,占用cpu资源极少.poll机制: 1

C++中四种进程或线程同步互斥的控制方法

现在流行的进程线程同步互斥的控制机制,其实是由最原始最基本的4种方法实现的.由这4种方法组合优化就有了.Net和Java下灵活多变的,编程简便的线程进程控制手段. 这4种方法具体定义如下 在<操作系统教程>ISBN 7-5053-6193-7 一书中能够找到更加周详的解释 1临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问. 2互斥量:为协调一起对一个共享资源的单独访问而设计的. 3信号量:为控制一个具备有限数量用户资源而设计. 4事 件:用来通知线程有一些事件已

【软考】PV操作同步互斥

进程 在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源),一个进程能有多个线程. 临界资源 指一次只能有一个进程在占用的资源.如现实中的衣服.一件衣服只能一个人在穿.比如一个硬盘.有两个进程对同一块区域进行写操作.数据不就一锅粥了么= = 临界区 在一个进程占有临界资源的时候.别的进程不能占有.这是互斥.从进程占有资源到资源被释放.这一段代码就叫临界区. 临界区原则(有空即进-无空则等-有限等待-让权等待) 个人造词= =

linux系统编程:线程同步-互斥量(mutex)

线程同步-互斥量(mutex) 线程同步 多个线程同时访问共享数据时可能会冲突,于是需要实现线程同步. 一个线程冲突的示例 #include <stdio.h> #include <unistd.h> #include <pthread.h> #define Loop 1000000 //全局资然 int counter = 0; void *fun(void *argv) { int i; for (i = 0; i < Loop; i++) { counter

(转)经典线程同步 互斥量Mutex

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> 前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用互斥量Mutex来解决这个问题. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互斥量Mutex主要将用到四个函数.下面是这些函数

多线程同步互斥实例——使用synchronized实现线程通信和互斥

线程互斥概念 线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性.但互斥无法限制访问者对资源的访问顺序,即访问是无序的. 实现线程同步互斥的四种方式 临界区(Critical Section):适合一个进程内的多线程访问公共区域或代码段时使用 互斥量 (Mutex):适合不同进程内多线程访问公共区域或代码段时使用,与临界区相似. 事件(Event):通过线程间触发事件实现同步互斥 信号量(Semaphore):与临界区和互斥量不同,可以实现多个线程同时访问公共区域数据,原理

转--- 秒杀多线程第七篇 经典线程同步 互斥量Mutex

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> 前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用互斥量Mutex来解决这个问题. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互斥量Mutex主要将用到四个函数.下面是这些函数

秒杀多线程第七篇 经典线程同步 互斥量Mutex

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> 前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用互斥量Mutex来解决这个问题. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互斥量Mutex主要将用到四个函数.下面是这些函数

Python并发编程—同步互斥

同步互斥 线程间通信方法 1.通信方法:线程间使用全局变量进行通信 2.共享资源争夺 共享资源:多个进程或者线程都可以操作的资源称为共享资源.对共享资源的操作代码段称为临界区. 影响 : 对共享资源的无序操作可能会带来数据的混乱,或者操作错误.此时往往需要同步互斥机制协调操作顺序. 3.同步互斥机制 同步 : 同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作. 互斥 : 互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程线程就无法