Linux详解 --- 多线程4: POSIX信号量、基于环形队列的生产消费者模型

POSIX信号量

 POSIX信号量与SystemV信号量的作用相同,都是能进行同步操作,以达到无冲突的访问共享资源的目的。除此以外,POSIX可以用于线程间的同步。
问题:什么是信号量?
信号量(信号灯),本质就是一个计数器!它是描述临界资源有效个数的计数器!

问题:为什么要使用信号量?
信号量将临界资源看成多份,在不冲突的部分,可以提高效率(让线程处于并行)。
//二元信号量(0和1) “等价于” mutex互斥锁

问题:如何对信号量进行操作?
sem_t 是信号量的类型(和前面的pthread_mutex_t、pthread_cond_t一样,用于定义变量)

· 信号量的初始化

//包含于<semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);

参数:
 sem:要初始化的信号量变量
 pshared:0表示线程间共享,非零表示进程间共享
 value:信号量的初始值

· 销毁信号量

int sem_destroy(sem_t* sem);


· 等待信号量

int sem_wait(sem_t* sem);    //相当于P()操作 - P()操作是原子性的


· 发布信号量

int sem_post(sem_t* sem);    //相当于V()操作 - V()操作也是原子性的

演示

#include <iostream>    
#include <unistd.h>    
#include <pthread.h>    
#include <semaphore.h>    
using namespace std;    
    
sem_t sem;    
    
void* Routine(void* arg)    
{    
  int index = *(int*)arg;    
  while(1)    
  {    
    sem_wait(&sem);    
    
    cout << index <<"Doing some tasks!" << endl;    
    sleep(1);
    sem_post(&sem);    
    usleep(1000);    
  }    
}    
    
int main()    
{    
  sem_init(&sem, 0, 2);    
  pthread_t tid[5];    
    
  for(int i = 0; i < 5; ++i)    
  {    
    pthread_create(&tid[i], NULL, Routine, (void*)&i);    
    usleep(1000);    
  }    
    
  for(int i = 0; i < 5; ++i)    
    pthread_join(tid[i], NULL);    
    
  sem_destroy(&sem);    
  return 0;    
}

在这里插入图片描述

总结:
①:信号量是一个计数器,描述临界资源有效个数的计数器
②:你一旦申请成功了信号量,那么临界资源中一定会分配给你一个资源供你使用!
③:信号量本身也是临界资源也是需要收到保护的。信号量的操作P()和V()操作的原子性确保了临界资源的安全

基于环形队列的生产消费者模型

关于生产消费者模型的讲解看这里
这里的环形队列我们使用数组作为底层数据结构,使用模运算来实现环形。
环形队列一直有一个问题,就是如何判断何时为空?何时为满?
我们有2种做法,
①:预留一个位置。空的条件是head == tail;满的条件是(tail + 1) % size == head;
②:使用计数器。记录队列剩余的空位置的量和队列中已经存储的数据的量。

· 具体实现思路:
 我们这里使用方法2,因为我们要实现的环形队列的生产消费模型是基于“信号量”实现的。而这里的信号量恰好可以作为计数器来使用。我们可以定义2个信号量,一个用来表示空格子的量(没存储数据的位置),一个用来表示已经存储数据的格子的量。
 除了使用2个信号量表示先有的数据量和空格里之外,我们还需要能够找到下一个数据该存放在哪里的下标,以及下一个空格位置该覆盖到哪个位置的下标!也就是我们需要一个生产者当前位置的下标和消费者当前位置的下标。我们使用这两个下标确定下一个"Get数据要从哪里获取?"和 “Put数据该放在哪里?”。

/***	RingQueue.h		***/
#pragma once     
    
#include <iostream>    
#include <vector>    
#include <semaphore.h>    
#include <unistd.h>    
#include <pthread.h>
#define NUM 10    
    
template <typename T>    
class RingQueue    
{    
  public:    
    RingQueue(size_t _cap = NUM)    
      :cap(_cap),    
       rq(_cap)    
    {    
      sem_init(&blank_sem, 0, NUM);    
      sem_init(&data_sem, 0, 0);    
    }    
    
    void Get(T& data)    
    {    
      sem_wait(&data_sem);  //数据减少1     
      data = rq[con_index];    
      ++con_index;    
      con_index %= cap;    
      sem_post(&blank_sem); //空格增加1    
    }    
    
    void Put(T& data)    
    {    
      sem_wait(&blank_sem); //空格减少1    
      rq[pro_index] = data;    
      ++pro_index;    
      pro_index %= cap;    
      sem_post(&data_sem);  //数据增加1    
    }    
    
    ~RingQueue()    
    {    
      sem_destroy(&blank_sem);    
      sem_destroy(&data_sem);    
    }    
  private:    
    std::vector<T> rq;  //基于数组实现的环形队列    
    size_t cap; //环形队列最大容量(这里采用计数方式判断队列满还是空)    
    sem_t blank_sem;  //空格信号量    
    sem_t data_sem;  //数据信号量    
    size_t con_index = 0; //消费者当前位置下标
    size_t pro_index = 0; //生产者当前位置下标    
};
/***	main.cpp	***/
#include "RingQueue.hpp"    
//mutex1, mutex2  
//mutex1锁用于多个生产者线程之间的竞争, mutex2锁用于多个消费者线程之间的竞争    
    
void* Productor(void* arg)    
{    
  RingQueue<int>* rq = (RingQueue<int>*)arg;    
  while(true)    
  {    
    usleep(500000);//0.5s    
    int data = rand() % 10 + 1;    
    rq->Put(data);    
    std::cout << "Productor put a data!" << std::endl;    
  }    
}    
    
void* Consumer(void* arg)    
{    
  RingQueue<int>* rq = (RingQueue<int>*)arg;    
    
  while(true)    
  {    
    int data;    
    rq->Get(data);    
    std::cout << "Consumer get data: " << data << std::endl;    
  }    
}    
    
int main()    
{    
  RingQueue<int>* rq = new RingQueue<int>(5);    
  pthread_t t, t1;    
    
  pthread_create(&t, nullptr, Productor, rq);    
  pthread_create(&t1, nullptr, Consumer, rq);    
    
  pthread_join(t, nullptr);    
  pthread_join(t1, nullptr);    
  return 0;    
} 

在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信号量是一种同步机制,用于在多个进程或线程之间协调共享资源的使用。生产者-消费者问题是一个经典的同步问题,其中生产者进程生成数据并将其放入缓冲区,而消费者进程从缓冲区中获取数据并将其消耗掉。为了避免竞争条件和死锁,需要使用信号量来控制对共享缓冲区的访问。 下面是一个简单的实现,其中使用了 POSIX 信号量和线程库。 ``` #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; // 缓冲区 int buffer_index = 0; // 缓冲区当前索引 sem_t empty; // 空闲缓冲区信号量 sem_t full; // 已占用缓冲区信号量 pthread_mutex_t mutex; // 互斥锁 void *producer(void *arg) { int item; while (1) { item = rand() % 1000; // 随机生成一个数据项 sem_wait(&empty); // 等待空闲缓冲区 pthread_mutex_lock(&mutex); // 加锁 buffer[buffer_index] = item; buffer_index++; printf("Produced item: %d\n", item); pthread_mutex_unlock(&mutex); // 解锁 sem_post(&full); // 发布已占用缓冲区 } } void *consumer(void *arg) { int item; while (1) { sem_wait(&full); // 等待已占用缓冲区 pthread_mutex_lock(&mutex); // 加锁 buffer_index--; item = buffer[buffer_index]; printf("Consumed item: %d\n", item); pthread_mutex_unlock(&mutex); // 解锁 sem_post(&empty); // 发布空闲缓冲区 } } int main() { 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` 信号量,如果缓冲区已满则会被阻塞。消费者线程在消费一个数据项后会等待 `full` 信号量,如果缓冲区为空则会被阻塞。同时,我们使用了一个互斥锁 `mutex` 来保证对缓冲区的访问是互斥的,避免竞争条件。 需要注意的是,上面的代码仅仅是一个简单的实现,实际使用中还需要考虑许多其他因素,例如如何处理异常情况、如何优化性能等等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值