POSIX信号量与SystemV信号量的作用相同,都是能进行同步操作,以达到无冲突的访问共享资源的目的。除此以外,POSIX可以用于线程间的同步。
问题:什么是信号量?
信号量(信号灯),本质就是一个计数器!它是描述临界资源有效个数的计数器!
问题:为什么要使用信号量?
信号量将临界资源看成多份,在不冲突的部分,可以提高效率(让线程处于并行)。
//二元信号量(0和1) “等价于” mutex互斥锁
问题:如何对信号量进行操作?
sem_t 是信号量的类型(和前面的pthread_mutex_t、pthread_cond_t一样,用于定义变量)
· 信号量的初始化
//包含于
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
#include
#include
#include
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
#include
#include
#include
#include
#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;
}