【操作系统】进程同步与进程互斥

简介: 【操作系统】进程同步与进程互斥

一、什么是进程同步

进程具有异步性的特征。异步性是指,各并发执行的进程以各自独立的、不可预知的速度向前推进。

读进程和写进程并发地运行,由于并发必然导致异步性,因此“数据”和“读数据”两个操作执行的先后顺序是不确定的。而实际应用中,又必须按照“写数据→读数据”的顺序来执行的,如何解决这种异步问题,就是“进程同步”所讨论的内容。

同步亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作。

二、什么是进程互斥

  • 进程的“并发”需要“共享”的支持。各个并发执行的进程不可避免的需要共享一些系统资源(比如内存,又比如打印机、摄像头这样的/O设备)
  • 互斥共享方式:
  • 系统中的某些资源,虽然可以提供给多个进程使用,但一个时间段内只允许一个进程访问该资源
  • 同时共享方式:
  • 系统中的某些资源,允许一个时间段内由多个进程“同时”对它们进行访问
  • 我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。
  • 对临界资源的访问,必须互斥地进行,互斥,亦称间接制约关系。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。
do {
  entry section;    //进入区
  critical section; //临界区
  exit section;   //退出区
  remainder section;  //剩余区
} while (true)
  • 注意:
  • 临界区是进程中访问临界资源的代码段
  • 进入区和退出区是负责实现互斥的代码段
  • 临界区也可称为“临界段”

三、 进程互斥的软件实现方法

3.1 单标志法

  • 算法思想:两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予。
int turn = 0; //turn表示当前允许进入临界区的进程号

P0进程:

while (turn != 0);  //①进入区
critical section; //②临界区
turn= 1;      //③退出区
remainder section;  //④剩余区

P1进程:

while (turn!= 1); //⑤进入区
critical section; //⑥临界区
turn= 0;      //⑦退出区
remainder section;  //⑧剩余区

turn的初值为0,即刚开始只允许0号进程进入临界区。

若P1先上处理机运行,则会一直卡在⑤。直到P1的时间片用完,发生调度,切换P0上处理机运行。

代码①不会卡住P0,P0可以正常访问临界区,在P0访问临界区期间即使切换回P1,P1依然会卡在⑤。

只有P0在退出区将turn改为1后,P1才能进入临界区。

  • 因此,该算法可以实现“同一时刻最多只允许一个进程访问临界区”
  • 只能按P0→P1→P0→P1→…这样轮流访问。这种必须“轮流访问”带来的问题是,如果此时允许进入临界区的进程是P0,而P0一直不访问临界区,那么虽然此时临界区空闲,但是并不允许P1访问。
  • 因此,单标志法存在的主要问题是:违背“空闲让进”原则

3.2 双标志先检查

  • 算法思想:设置一个布尔型数组ag,数组中各个元素用来标记各进程想进入临界区的意愿,比如“flag[0]=true”意味着0号进程P0现在想要进入临界区。每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自身对应的标志flag[i]设为true,之后开始访问临界区。
bool flag[2];   //表示进入临界区意愿的数组
flag[0] = false;
falg[1] = false;  //刚开始设置为两个进程都不想进入临界区

P0进程:

while (falg[1]);  //①
falg[0] = true;   //②
critical section; //③
flag[0] = false;  //④
remainder section;  

P1进程:

while (flag[0]);  //⑤ 如果此时P0想进入临界区,P1就一直循环等待
flag[1] = true;   //⑥ 标记为P1进程想要进入临界区
critical section; //⑦ 访问临界区
flag[1] = false;  //⑧ 访问完临界区,修改标记为P1不想使用临界区
remainder section;
  • 若按照①⑤②⑥③⑦.…的顺序执行,P0和P1将会同时访问临界区。
  • 因此,双标志先检查法的主要问题是:违反“忙则等待”原则。(原因在于,进入区的“检查”和“上锁”两个处理不是一气呵成的。“检查”后,“上锁”前可能发生进程切换。)

3.3 双标志后检查

  • 算法思想:双标志先检查法的改版。前一个算法的问题是先“检查”后“上锁”,但是这两个操作又无法一气呵成,因此导致了两个进程同时进入临界区的问题。因此,人们又想到先“上锁”后“检查”的方法,来避免上述问题。
bool flag[2];   //表示进入临界区意愿的数组
flag[0] = false;
falg[1] = false;  //刚开始设置为两个进程都不想进入临界区

P0进程:

falg[0] = true;   //①
while (falg[1]);  //②
critical section; //③
flag[0] = false;  //④
remainder section;  

P1进程:

flag[1] = true;   //⑤ 标记为P1进程想要进入临界区
while (flag[0]);  //⑥ 如果此时P0想进入临界区,P1就一直循环等待
critical section; //⑦ 访问临界区
flag[1] = false;  //⑧ 访问完临界区,修改标记为P1不想使用临界区
remainder section;
  • 若按照①⑤②⑥.的顺序执行,P0和P1将都无法进入临界区
  • 因此,双标志后检查法虽然解决了“忙则等待”的问题,但是又违背了“空闲让进”和“有限等待”原则,会因各进程都长期无法访问临界资源而产生“饥饿”现象。

3.4 Peterson 算法

  • 算法思想:结合双标志法、单标志法的思想。如果双方都争着想进入临界区,那可以让进程尝试“孔融让梨”(谦让),做一个有礼貌的进程。
bool flag[2]; //表示进入临界区意愿的数组,初始值都是false
int turn = 0; //turn 表示优先让哪个进程进入临界区

P0进程:

flag[0] = true;
turn = 1;
while (flag[1] && turn == 1);
cirtical section;
flag[0] = false;
remainder section;

P1进程:

flag[1] = true;
turn = 0;
while (flag[0] && turn == 0);
cirtical section;
flag1] = false;
remainder section;
  • Peterson算法用软件方法解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待三个原测,但是依然未遵循让权等待的原则

四、 进程互斥的硬件实现方法

4.1 中断屏蔽法

  • 利用“开/关中断指令”实现(与原语的实现思想相同,即在某进程开始访问临界区到结束访问为止都不允许被中断,也就不能发生进程切换,因此也不可能发生两个同时访问临界区的情况)
  • 优点:简单、高效
  • 缺点:不适用于多处理机;只适用于操作系统内核进程,不适用于用户进程(因为开/关中断指令只能运行在内核态,这组指令如果能让用户随意使用会很危险)

4.2 TestAndSet(TS指令/TSL指令)

  • 简称TS指令,也有地方称为TestAndSetLock指令,或TSL指令
  • TSL指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。
  • 若刚开始lock是false,则TSL返回的old值为false,while循环条件不满足,直接跳过循环,进入临界区。若刚开始lock是true,则执行TLS后old返回的值为true,while循环条件满足,会一直循环,直到当前访问临界区的进程在退出区进行“解锁”
  • 相比软件实现方法,TS指令把“上锁”和“检查”操作用硬件的方式变成了一气呵成的原子操作。
  • 优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
//布尔型共享变量lock表示当前临界区是否被加锁
//true表示已加锁,false表示未加锁
bool TestAndSet (bool *lock) {
  bool old;
  old = *lock;  //old用来存放lock原来的值
  *lock = true; //无论之前是否已加锁,都将lock设为true
  return old;   //返回lock原来的值
}
//以下是使用TSL指令实现互斥的算法逻辑
while (TestAndSet (&lock)) {  //上锁并检查
  //临界区代码段...
  lock = false; //解锁
  //剩余区代码段...
}
  • 缺点:不满足“让权等待”原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”。

4.3 Awap指令(XCHG指令)

  • 有的地方也叫Exchange指令,或简称XCHG指令。
  • Swap指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。
//Swap指令的作用是交换两个变量的值
Swap(bool *a,bool *b) {
  bool temp = *a;
  *a = *b;
  *b = temp;
}
//以下是用Swap指令实现互斥的算法逻辑
//lock表示当前临界区是否被加锁
bool old = true;
while (old == true) 
Swap(&lock,&old);
//临界代码段...
lock = false;
//剩余代码段...
  • 逻辑上来看Swap和TSL并无太大区别,都是先记录下此时临界区是否己经被上锁(记录在old变量上),再将上锁标记Iock设置为true,最后检查old,如果old为false则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区。
  • 优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
  • 缺点:不满足“让权等待”原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”。


相关文章
|
13天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
37 1
|
16天前
|
调度 开发者 Python
深入浅出操作系统:进程与线程的奥秘
在数字世界的底层,操作系统扮演着不可或缺的角色。它如同一位高效的管家,协调和控制着计算机硬件与软件资源。本文将拨开迷雾,深入探索操作系统中两个核心概念——进程与线程。我们将从它们的诞生谈起,逐步剖析它们的本质、区别以及如何影响我们日常使用的应用程序性能。通过简单的比喻,我们将理解这些看似抽象的概念,并学会如何在编程实践中高效利用进程与线程。准备好跟随我一起,揭开操作系统的神秘面纱,让我们的代码运行得更加流畅吧!
|
15天前
|
C语言 开发者 内存技术
探索操作系统核心:从进程管理到内存分配
本文将深入探讨操作系统的两大核心功能——进程管理和内存分配。通过直观的代码示例,我们将了解如何在操作系统中实现这些基本功能,以及它们如何影响系统性能和稳定性。文章旨在为读者提供一个清晰的操作系统内部工作机制视角,同时强调理解和掌握这些概念对于任何软件开发人员的重要性。
|
14天前
|
Linux 调度 C语言
深入理解操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅,从进程管理的基本概念出发,逐步探索到内存管理的高级技巧。我们将通过实际代码示例,揭示操作系统如何高效地调度和优化资源,确保系统稳定运行。无论你是初学者还是有一定基础的开发者,这篇文章都将为你打开一扇了解操作系统深层工作原理的大门。
|
15天前
|
存储 算法 调度
深入理解操作系统:进程调度的奥秘
在数字世界的心脏跳动着的是操作系统,它如同一个无形的指挥官,协调着每一个程序和进程。本文将揭开操作系统中进程调度的神秘面纱,带你领略时间片轮转、优先级调度等策略背后的智慧。从理论到实践,我们将一起探索如何通过代码示例来模拟简单的进程调度,从而更深刻地理解这一核心机制。准备好跟随我的步伐,一起走进操作系统的世界吧!
|
15天前
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
16天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
19天前
|
算法 Linux 调度
深入浅出操作系统的进程管理
本文通过浅显易懂的语言,向读者介绍了操作系统中一个核心概念——进程管理。我们将从进程的定义出发,逐步深入到进程的创建、调度、同步以及终止等关键环节,并穿插代码示例来直观展示进程管理的实现。文章旨在帮助初学者构建起对操作系统进程管理机制的初步认识,同时为有一定基础的读者提供温故知新的契机。
|
18天前
|
消息中间件 算法 调度
深入理解操作系统之进程管理
本文旨在通过深入浅出的方式,带领读者探索操作系统中的核心概念——进程管理。我们将从进程的定义和重要性出发,逐步解析进程状态、进程调度、以及进程同步与通信等关键知识点。文章将结合具体代码示例,帮助读者构建起对进程管理机制的全面认识,并在实践中加深理解。
|
20天前
|
负载均衡 算法 调度
深入理解操作系统:进程管理与调度
在数字世界的心脏,操作系统扮演着至关重要的角色。它如同一位精明的指挥家,协调着硬件资源和软件需求之间的和谐乐章。本文将带你走进操作系统的核心,探索进程管理的艺术和调度策略的智慧。你将了解到进程是如何创建、执行和消亡的,以及操作系统如何巧妙地决定哪个进程应该在何时获得CPU的青睐。让我们一起揭开操作系统神秘的面纱,发现那些隐藏在日常计算背后的精妙机制。