操作系统-进程与线程(互斥锁,信号量,信号量实现进程同步与互斥)

1. 互斥锁(解决进程互斥问题)

解决临界区临界资源冲突的最简单的方式就是通过互斥锁。一个进程在进入临界区的时候加锁,出临界区时解锁。解决进程互斥问题

假设通过release函数释放锁资源,acquire函数获得锁

其中,每个互斥锁都会有一个变量available,这个变量标记这个锁现在是否是可用的。如果进程申请不可用锁会阻塞。直到锁被释放

伪代码如下:

acquire (){
	while(!available);//忙等待
	available = false;//获得锁
}
release() {
	available = true;//释放锁
}

需要注意:
release和acquire这两个函数都需要原子性操作,因此通常采用硬件机制来实现

缺点:

  1. 在锁没有就绪时,CPU处于忙等状态。浪费CPU资源。不太适合单处理机

优点:

  1. 互斥锁通常用于多处理机上,进程在一个处理机上等待,不会影响另一个进程的运行。等待消耗比进程切换的代价更低。

需要连续循环忙等的互斥锁,都可称为自旋锁(spin lock),如TSL指令、swap指令、单标志法

2. 信号量

用户可以通过系统提供的一对原语对信号量进行操作,实现进程的同步互斥

信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量)。

可以用一个信号量来表示系统中某种资源的数量。
eg:比如:系统中只有一台打印机,就可以设置一个初值为1的信号量

这一对原语为:
wait(S) signal(S)原语 S是信号量

wait(S)又称信号量的P操作,使计数器-- P(S)
signal(S)又称信号量的V操作,使计数器++V(S)

Linux下的信号量机制练习

整型信号量

用一个整数来表示系统资源的数量,对信号量的操作只有三种。

初始化,P操作,V操作

伪代码:

int s = 1; //初始化整型信号量s,表示当前系统中可用的打印机资源数

//P操作
void wait (int s) {  // wait原语,相当于“进入区”
	while (s <= 0);//如果资源数不够,就一直循环等待,会出现忙等状态
	s=s-1;//如果资源数够,则占用一个资源
}
//V操作
void signal (int s) { // signal原语,相当于“退出区”
	s=s+1; //使用完资源后,在退出区释放资源
}

问题:会发生忙等状态。

记录型信号量

整型信号量的缺陷是存在“忙等”问题,因此人们又提出了“记录型信号量”,即用记录型数据结构表示的信号量。

伪代码:

/*记录型信号量的定义*/
typedef struct {
	int value;//剩余资源数
	struct process *L;//等待队列
}semaphore;

//P操作:
/*某进程需要使用资源时,通过wait原语申请*/
void wait ( semaphore s) {
	s.value--;
	if(s.value < 0){
		block (s.L);//block原语,把当前进程阻塞,将其挂到等待队列中
	}
}
//V操作:
/*进程使用完资源后,通过signal原语释放*/
void signal (semaphore s) {
	s.value++;
	if (s.value <= 0) {
		wakeup(s.L);//wakeup原语,从等待队列中唤醒某个进程,该进程从阻塞态变为就绪态
	}
}

优点:

  1. 记录型信号量可以实现系统资源的申请与释放。
  2. 可以使用记录型信号量实现进程同步与互斥。

3. 信号量实现同步与互斥

首先,先明确信号量的定义:

信号量的值=这种资源的剩余数量(信号量的值如果小于0,说明此时有进程在等待这种资源)

P(S)一申请一个资源s,如果资源不够就阻塞等待
V(S)―释放一个资源s,如果有进程在等待该资源,则唤醒一个进程

信号量实现进程互斥

伪代码:

/*信号量机制实现互斥*/
semaphore mutex=1;//初始化信号量
P1(){
	……
	P(mutex);
	//使用临界资源前需要加锁
	临界区代码段...
	V(mutex);
	//使用临界资源后需要解锁
	……
}

P2(){
	……
	P( mutex) ;
	临界区代码段...
	V(mutex);
	……
}

具体实例:
Linux-Centos环境下C++ 封装信号量:

#include<iostream>
#include<semaphore.h>
#include<pthread.h>
#include<unistd.h>
class Sem//封装信号量
{
  public:
    Sem(int Num)
    {
      sem_init(&sem,0,Num);//初始化信号量,0表示线程间共享,非0表示进程间共享(基本不用)
    }

    void P()//使计数器--
    {
      sem_wait(&sem);
    }

    void V()//使计数器++
    {
      sem_post(&sem);
    }

    ~Sem()
    {
      sem_destroy(&sem);
    }
  private:
    sem_t sem;
};

用三个线程测试互斥操作

Sem sem(1);//初始化信号量的值为1
//考试中初始化信号量的方式默认为semaphore mutex=1;


int cout=1000;

void*Run(void*arg)
{
  char*Str=(char*)arg;
  while(true)
  {
    sem.P();//申请到资源
    if(cout>0){
      usleep(1000);
      cout--;
      std::cout<<Str<<"make cout-- cout="<<cout<<std::endl;
      sem.V();//释放资源
    }
    else{
      sem.V();//如果cout为0说明此时应该退出循环,释放资源后退出
      break;
    }
  }
  std::cout<<Str<<"quit"<<std::endl;
  pthread_exit((void*)1);
}

int main()
{
  pthread_t tid1,tid2,tid3;
  pthread_create(&tid1,nullptr,Run,(void*)"pthread1");
  pthread_create(&tid2,nullptr,Run,(void*)"pthread2");
  pthread_create(&tid3,nullptr,Run,(void*)"pthread3");

  pthread_join(tid1,nullptr);
  pthread_join(tid2,nullptr);
  pthread_join(tid3,nullptr);
  return 0;
}

在这里插入图片描述

信号量实现进程同步

进程同步:要让各并发进程按要求有序地推进。

首先要分析在什么时候要实现进程同步,即必须保证“一前一后”执行的两个操作(或两句代码)

设置同步信号量S,初始值为0

伪代码:

/*信号量机制实现同步*/
semaphore S=0;//初始化同步信号量,初始值为0
P1(){
	代码1;
	代码2;
	V(S);//信号量+1,唤醒阻塞的进程
	代码3;
}

P2(){
	P(S);
	代码4;
	代码5;
	代码6; 
}
//实现了代码4必须在代码2之后执行的目的。

如果代码1,代码2没有执行,P2会阻塞在P(S)上,没法运行代码4。

只有代码1,代码2执行后,V(S)唤醒P2进程,P2进程才执行代码4

简单的理解就是:前V后P,先执行的进程V操作,后执行的进程P操作等待先执行的进程V操作。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUC_Dodamce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值