操作系统:进程通信-信号量和互斥量

参考:

https://blog.csdn.net/benjamin721/article/details/50703692(进程/线程通信:信号量和互斥量总结)

https://blog.csdn.net/qq_46275568/article/details/120630019(信号量及其应用)

https://www.jianshu.com/p/6674400a98b2(信号量与PV操作)

信号量的引入

由于对互斥量的操作在cpu执行时不是原子性的,如果多个进程/线程同时访问互斥量,被cpu分解为一个一个指令时有重叠,就会出问题。由此引入信号量!

信号量:

信号量s是非负整数值的全局变量。只能有两种特殊的操作,这两种操作称为P和V,(再加上一个信号量的初始化)

P(s):如果 s 是非零的,那么 P 将 s 减 1,并且立即返回。(如果 s 为零,那么就挂起这个线程,直到 s 变为非零)。

V(s): V 操作将 s 加 1。(如果有任何线程阻塞在 Р 操作等待s变成非零,那么 V 操作会重启这些线程中的一个)。
 

 

用PV操作解决并发互斥问题

用PV操作,解决进程间互斥问题有以下4点:
1.划定临界区
2.对多个进程的临界区,设定一个信号量mutex。mutex这个信号量,初值为1。
简单说明一下mutex,mutex是常用于解决互斥问题的时候所定义的一个信号量变量。是互斥(mutual exclusion)的缩写。现在已经形成一种约定俗成的互斥的命名。
3.在临界区前实施P(mutex)
4.在临界区之后实施V(mutex)

信号量与互斥锁的区别:

1. 互斥锁用于线程的互斥,信号量用于线程的同步

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

2. 互斥锁量值只能为0/1,信号量值可以为非负整数。

3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

二值信号量也可以理解为互斥锁:show me the code

复制代码
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<semaphore.h>

// global shared varible
volatile int cnt = 0; // counter

sem_t mutex;

void P(sem_t* s) {sem_wait(s);}
void V(sem_t* s) {sem_post(s);}

// thread routine
void *thread(void* arg)
{
  int i, niters = *((int*)arg);
  
  for(int i = 0; i < niters; ++i)
  {
    P(&mutex);
    cnt++;
    V(&mutex);
  }
  return NULL;
}

int main(int argc, char* argv[])
{
  int niters;
  pthread_t tid1, tid2;  
  sem_init(&mutex, 0, 1); // mutex = 1,初始化为1,并且先P后V,那么mutex只可能市0或1,即为二值信号量

  if(argc != 2)
  {
    printf("usage: %s <niters>\n", argv[0]);
    exit(0);
  }

  niters = atoi(argv[1]);
  
  // create thread and wait for them to finish
  pthread_create(&tid1, NULL, thread, &niters);
  pthread_create(&tid2, NULL, thread, &niters);
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
  
  printf("cnt = %d\n", cnt);
  return 0;
}
复制代码

 

生产者消费者模型(有界缓冲区问题)

复制代码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<semaphore.h>
#include<time.h>

typedef struct
{
  int *buf;  
  int n;
  int front; // 指向第一个元素前面的位置
  int rear;  // 指向最后一个元素
  sem_t mutex;
  sem_t slots;
  sem_t items;
}sbuf_t;

// PV操作
void P(sem_t *s){sem_wait(s);}
void V(sem_t *s){sem_post(s);}

// 创建一个大小为 n 个槽,空的缓冲区
void sbuf_init(sbuf_t *sp, int n)
{
  sp->buf = (int*)calloc(n, sizeof(int));
  sp->n = n;
  sp->front = sp->rear = 0;
  sem_init(&sp->mutex, 0, 1);
  sem_init(&sp->slots, 0, n);
  sem_init(&sp->items, 0, 0);
}

// 释放缓冲区
void sbuf_deinit(sbuf_t *sp)
{
  free(sp->buf);
}

// 向缓冲区中加入 item 
void sbuf_insert(sbuf_t *sp, int item)
{
  P(&sp->slots);
  P(&sp->mutex);
  sp->buf[++sp->rear % sp->n] = item;
  V(&sp->mutex);
  V(&sp->items);
}

// 从缓冲区中取出 item 
int sbuf_remove(sbuf_t *sp)
{
  int ret;
  P(&sp->items);
  P(&sp->mutex);
  ret = sp->buf[(++sp->front) % sp->n];
  V(&sp->mutex);
  V(&sp->slots);

  return ret;
}

void *producer1(void* arg)
{
  while(1)
  {
    usleep(300); 
    int num = rand() % 10000;
    sbuf_insert((sbuf_t*)arg, num);
    printf("producer1 生产:%d\n", num);
  }
  return NULL;
}

void *consumer1(void* arg)
{
  while(1)
  {
    sleep(1);
    printf("consumer1 消费 %d\n", sbuf_remove((sbuf_t*)arg));
  }

  return NULL;
}

void *consumer2(void* arg)
{
  while(1)
  {
    sleep(2);
    printf("consumer2 消费 %d\n", sbuf_remove((sbuf_t*)arg));
  }

  return NULL;
}

int main()
{ 
  pthread_t tid1, tid2, tid3;
  sbuf_t sbuf;
  
  srand(time(NULL));
  sbuf_init(&sbuf, 5);

  pthread_create(&tid1, NULL, producer1, &sbuf);
  pthread_create(&tid2, NULL, consumer1, &sbuf);
  pthread_create(&tid3, NULL, consumer2, &sbuf);
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
  pthread_join(tid3, NULL);
  
  sbuf_deinit(&sbuf);

  return 0;
}
复制代码

 

posted @   指令跳动  阅读(550)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示