操作系统之进程同步问题

在学习操作系统的同时,做了笔记加上了一些自己的备注和听课的内容,方便自己回顾知识点,如果能帮助到看文章的你那就更棒了。

目录

1.进程同步的基本概念

①临界资源

②同步

③互斥(让权等待等四个遵循的原则)

2.实现临界区互斥的基本方法

①软件实现

算法一:单标志法。

算法二:双标志法先检查。

算法三:双标志法后检测。

算法四:Peterson’s算法。

②硬件实现方法

中断屏蔽方法

硬件指令方法


本文需要了解的问题有以下几个

  1. 为什么要引入进程同步的概念
  2. 不同的进程之间会存在什么样的关系
  3. 会遇到怎么样的问题

关于信号量和管程单独开一篇文章

1.进程同步的基本概念

在多道程序环境下,进程是并发执行的,每个进程之间都存在着不同的相互制约的关系。为了协调进程之间的相互制约的关系,引入了进程同步的概念(1)。

进程具有异步性(异步性是指,各并发执行的进程以各自独立的、不可预知的速度向前推进)的特征,如何解决异步问题,就是“进程同步”所讨论的内容。

①临界资源

多个进程可以共享系统中的各种资源,但是其中有些资源一次只能为一个进程所用(比如打印机)。我们把一次仅允许一个进程使用的资源称为临界资源

对临界的资源的访问,必须互斥地进行,访问临界资源的那段代码称为临界区

为了临界资源的正确使用,把临界资源的访问分成4个部分:

  1. 进入区。设置临界区的标志,这样可以当一个进程进入了进入区,其余进程无法进入。
  2. 临界区。访问临界资源的代码,又叫临界段
  3. 退出区。将正在访问临界区的标志清除。
  4. 剩余区。代码中的其他部分。

②同步

同步也叫做直接制约关系,是协调它们的工作次序而等待、传递信息所产生的制约关系,这种关系源于它们之间的相互合作。

例如,有一根吸管,一杯奶茶和人。当吸管为空的时候人不能吃到奶茶里的珍珠会“阻塞”,一旦当珍珠送入吸管时,人才能被“唤醒”。反之,当吸管里都是珍珠时,奶茶被“阻塞”,只有当人吃掉一些的时候,才能“唤醒”奶茶。

③互斥

互斥也叫间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一个进程才允许访问此临界资源。

为了禁止两个进程同时进入临界区,同步机制应遵循以下准则:

  1. 空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程进入。
  2. 忙则等待。临界区有进程时,其他试图进入的必须等待。
  3. 有限等待。保证进程不会产生饥饿。
  4. 让权等待。当进程不能进入临界区时,应立即释放处理器,防止进程忙等。

两种资源共享的方式

互斥共享:一个时间段内只允许一个进程访问该资源。

同时共享:允许一个时间段内多个进程“同时”访问。

2.实现临界区互斥的基本方法

①软件实现

在进入区设置并检查一些标志来标明是否有进程在临界区,如果有了就通过循环检查进行等待,直到进程离开临界区后在退出区修改标志。以下有四种软件实现的方法,下面是学习这四个算法的提示。

  1. 理解各个算法的思想、原理。
  2. 结合“实现互斥的四个逻辑部分”,重点理解各算法在进入区、退出区都做了些什么。
  3. 分析各算法存在的缺陷(结合上面的“四个原则”)。
  • 算法一:单标志法。设置一个公用整型变量turn。若turn为0,则允许P0进程进入临界区。该算法可确保每次只允许一个进程进入临界区。但两个进程必须交替进入临界区,若某个进程不再进入临界区,则另一个进程也将无法进入临界区(违背“空闲让进”)。

  1. P0进程  
  2. while(turn!=0)//进入区  
  3. critical section;//临界区  
  4. turn=1;//退出区  
  5. remainder section;//剩余区  
  1. P1进程  
  2. while(turn!=1)//进入区  
  3. critical section;//临界区  
  4. turn=0;//退出区  
  5. remainder section;//剩余区  

可以发现,如果我就P0进程进入并且结束退出,那么turn的值就是1,P1进程并不让他进入,那么下次P0就无法再次进入,因为turn就是1,违背了空闲让进

  • 算法二:双标志法先检查。算法基本思想是在每个进程访问临界区资源之前先查看临界资源是否被访问,若正被访问,进程等待;否则,进程才进入自己的临界区。设置一个数据flag[i],如果第i个元素的值为FALSE,表示Pi进程未进入临界区,值为TRUE,表示Pi进程进入临界区

  1. Pi进程  
  2. while(flag[j])//进入区 ①  
  3. flag[i]=TRUE;//进入区 ③  
  4. critical section;//临界区  
  5. flag[i]=FALSE;//退出区  
  6. remainder section;//剩余区  
  1. Pj进程  
  2. while(flag[i])//进入区 ②  
  3. flag[j]=TRUE;//进入区 ④  
  4. critical section;//临界区  
  5. flag[j]=FALSE;//退出区  
  6. remainder section;//剩余区  

优点:不用交替进入,可以连续使用;缺点:Pi和Pj可能同时进入临界区。按序列①②③④执行时,会同时进入临界区(违背“忙则等待”)。即在检查对方的flag后和切换自己的flag前有一段时间,结果都检查通过。问题出在检查和修改操作不能一次执行

  • 算法三:双标志法后检测。结合二的问题,先将自己的标志设置为TRUE,再检测对方的状态标志,若对方标志为TRUE,则进程等待;否则进入临界区

  1. Pi进程  
  2. flag[i]=TRUE;//进入区   
  3. while(flag[j])//进入区   
  4. critical section;//临界区  
  5. flag[i]=FALSE;//退出区  
  6. remainder section;//剩余区  

 

  1. Pj进程  
  2. flag[j]=TRUE;//进入区   
  3. while(flag[i])//进入区   
  4. critical section;//临界区  
  5. flag[j]=FALSE;//退出区  
  6. remainder section;//剩余区  

两个进程几乎同时都想进入临界区时,它们分别将自己的标志设为flag设置为TRUE,并且同时检测到了对方的while,发现对方都要进,双方都进入不了,从而导致了“饥饿”的发生。(因为都进入不了,所以违背了空闲让进,导致了饥饿,违背了有限等待

  • 算法四:Petersons算法。为了防止两个进程为进入临界区无限期等待。设置了变量turn,每个进程在先设置自己的标志后再设置turn。这时,再同时检测另一个进程状态标志和不允许进入标志,以保证两个进程同时要求进入临界区时,只允许一个进入临界区。

  1. P1进程  
  2. flag[1]=TRUE;turn=2;//进入区  
  3. while(flag[2]&&turn==2);//进入区  
  4. critical section;//临界区  
  5. flag[1]=FALSE;//退出区  
  6. remainder section;//剩余区  
  1. P2进程  
  2. flag[2]=TRUE;turn=1;//进入区  
  3. while(flag[1]&&turn==1);//进入区  
  4. critical section;//临界区  
  5. flag[2]=FALSE;//退出区  
  6. remainder section;//剩余区  

P1设置flag=true表示P1想进入临界区,turn=2是检查P2是不是再临界区,如果符合while条件,则P1不能进入临界区。若P2不想要进入临界区,即flag[2]=false,循环条件不符合,则P1可以顺利进入。本算法结合了算法一和三。利用flag解决临界资源的互斥访问,利用turn解决“饥饿”现象。但是此算法并未遵循让权等待原则。进入区:1.主动争取 2.主动谦让 3.检查对方是否也想使用,且最后一次是谁想让。例如:先执行P1,那么flag[1]=TRUE;表示P1想访问,然后P1“谦让”,询问P2要不要访问,P2如果先执行flag[2]=TRUE,turn=1,P2就是想访问,那么P1检查P2想访问,但是P2说P1你可以先访问,因为turn设置成了1,所以最后一次是P2谦让给P1,那么P1就进入临界区。

②硬件实现方法

提示:

  1. 理解各方法的原理
  2. 了解各方法的优缺点
  • 中断屏蔽方法

当一个进程使用处理机执行它的临界区代码,防止其他进程进入临界区最简单的方法是禁止一切中断的发生。因为CPU只在发生中断时引起进程切换,因为关掉或屏蔽中断能让临界区代码顺利执行完,进而保证互斥的正确实现。

  1. .  
  2. .  
  3. .  
  4. 关中断;  
  5. 临界区;  
  6. 开中断;  
  7. .  
  8. .  
  9. .  

优点:简单、高效

缺点:不适用于多处理机,一个处理机再访问临界区,另一个处理机可能也会来访问临界区。只适用于操作系统内核进程,不适用于用户进程(因为开/关中断指令只能运行在内核态,这组指令如果能让用户随意使用会很危险。)

  • 硬件指令方法

TestAndSet指令:这条指令是原子操作,即执行该代码时不允许被中断。其功能是读出指定标志后把该标志设置为真。

  1. boolean TestAndSet(boolean *lock){  //布尔型共享变量lock表示当前临界区是否加锁
  2.    boolean old;  
  3.    old = *lock;   //old用来存放lock原来的值
  4.    *lock = true;  //true表示加锁,false表示未加锁  无论之前是否加锁都将lock设置为true
  5.    return old;  //然后返回lock的值
  6. }  
  1. while TestAndSet(&lock);//上锁并检查  
  2. 进程的临界区代码段  
  3. lock=false;//解锁  
  4. 进程的其他代码;  

Swap指令:该指令的功能是交换两个字(字节)的内容。

  1. Swap(boolean *a,boolean *b){  
  2.   boolean temp;  
  3.   Temp=*a;  
  4.   *a=*b;  
  5.   *b=temp;  
  6. }  

应为每个临界资源设置一个共享布尔变量lock,储值为false;在每个进程中再设置一个局部变量key,用于与lock交换信息。在进入临界区前,先利用Swap指令交换lock与key的内容,然后检查key的状态;有进程在临界区时,重复交换和检查过程,直到进程退出。利用Swap指令实现进程互斥的算法描述入下:

  1. key=true;  
  2. while(key!=false)  
  3.    Swap(&lock,&key);  
  4. 进程的临界区代码段;  
  5. lock=false;  
  6. 进程的其他代码段; 

硬件方法的优点:使用于任意数目的进程,而不管是单处理机还是多处理机;简单、容易验证其正确性。支持进程内有多个临界区,只需为每个临界区设置一个布尔变量。

硬件方法的缺点:不能实现让权等待。从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致“饥饿”。

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经典的进程同步问题通常包括生产者-消费者问题、读者-写者问题和哲学家就餐问题等。这里以生产者-消费者问题为例,简单介绍一下如何在 C 语言实现进程同步。 生产者-消费者问题是指多个进程共享同一个缓冲区,其生产者进程向缓冲区写入数据,而消费者进程则从缓冲区读取数据。为了避免竞争条件和死锁等问题,需要使用同步机制来保证进程之间的协调工作。 使用信号量可以实现进程同步。信号量是一个计数器,用于控制对共享资源的访问。在 C 语言,我们可以使用 POSIX 信号量库来创建和使用信号量。 下面是一个简单的生产者-消费者问题的代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; int in = 0, out = 0; sem_t empty; // 缓冲区空闲信号量 sem_t full; // 缓冲区满信号量 pthread_mutex_t mutex; // 互斥锁 void *producer(void *arg) { int item; while (1) { item = rand() % 100; // 生产一个随机数 sem_wait(&empty); // 当缓冲区空间为 0 等待 pthread_mutex_lock(&mutex); buffer[in] = item; in = (in + 1) % BUFFER_SIZE; // 循环缓冲区 printf("Producer produced item %d\n", item); pthread_mutex_unlock(&mutex); sem_post(&full); // 增加缓冲区的项目数 } pthread_exit(NULL); } void *consumer(void *arg) { int item; while (1) { sem_wait(&full); // 当缓冲区没有项目等待 pthread_mutex_lock(&mutex); item = buffer[out]; out = (out + 1) % BUFFER_SIZE; // 循环缓冲区 printf("Consumer consumed item %d\n", item); pthread_mutex_unlock(&mutex); sem_post(&empty); // 增加缓冲区的空闲空间 } pthread_exit(NULL); } int main(int argc, char **argv) { pthread_t producer_thread, consumer_thread; sem_init(&empty, 0, BUFFER_SIZE); // 初始化信号量 sem_init(&full, 0, 0); pthread_mutex_init(&mutex, NULL); pthread_create(&producer_thread, NULL, producer, NULL); pthread_create(&consumer_thread, NULL, consumer, NULL); pthread_join(producer_thread, NULL); pthread_join(consumer_thread, NULL); sem_destroy(&empty); // 销毁信号量和互斥锁 sem_destroy(&full); pthread_mutex_destroy(&mutex); return 0; } ``` 在这个示例,我们使用了两个信号量 `empty` 和 `full` 来控制缓冲区的空闲空间和项目数。当 `empty` 为 0 ,表示缓冲区已满,生产者需要等待;当 `full` 为 0 ,表示缓冲区没有项目,消费者需要等待使用互斥锁 `mutex` 来保护共享资源的访问,避免竞争条件。 当生产者生成一个随机数,将其写入缓冲区,同增加 `in` 指针,表示缓冲区的项目数增加了一个。当消费者读取缓冲区的项目,将其存储到 `item` ,并将 `out` 指针增加,表示缓冲区的项目数减少了一个。 上述代码,生产者和消费者都是无限循环运行的,可以使用 `pthread_cancel()` 函数来终止它们的运行。此外,还需要注意的是,在使用信号量等同步机制,不要忘记释放已经申请的资源,否则可能会导致死锁等问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值