目录
1. 整体学习思维导图
2. 信号量的概念
3. 基本接口
4. 基于环形队列的生产者消费者模型(信号量)
1. 整体学习思维导图
2. 信号量的概念
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但
POSIX可以用于线程间同步。
POSIX信号量的本质是一个计数器,是对特定资源的预定机制!我们之前在进程通信时已经做了了解!
-
多线程使用资源,有两种场景:
-
将目标资源整体使用 【Mutex + 二元信号量】
-
将目标资源划分为不同的"块",分批使用【信号量】 --> 电影院的观影座位售卖
-
-
信号量一旦申请成功,就会保证该线程一定有块资源可以使用!(预定机制的体现)
-
所有线程都需要看到sem信号量,因此信号量是临界资源。
-
PV操作需要是原子性的:
-
P:--
-
V: ++
-
3. 基本接口
1. 初始化信号量:
sem_t sem;
sem_init(&sem, 0, 1);
&sem 是信号量指针。
0 表示信号量用于线程间同步(1 表示进程间同步)。
1 是信号量的初始值。
2. P 操作:
sem_wait(&sem);
3. V 操作:
sem_post(&sem);
4. 销毁信号量:
sem_destroy(&sem);
-
Sem.hpp 封装
#include <iostream>
#include <semaphore.h>
#include <pthread.h>
namespace SemModule
{
const int defaultvalue = 1;
class Sem
{
public:
Sem(unsigned int sem_value = defaultvalue)
{
sem_init(&_sem, 0, sem_value);
}
void P()
{
int n = sem_wait(&_sem); // 原子的
(void)n;
}
void V()
{
int n = sem_post(&_sem); // 原子的
}
~Sem()
{
sem_destroy(&_sem);
}
private:
sem_t _sem;
};
}
4. 基于环形队列的生产者消费者模型(信号量)
-
实现单生产者/单消费者模型
-
P C --> p与c之间互斥和同步关系由信号量维持
-
-
实现多生产者/多消费者模型
-
PP CC --> pp/cc之间的互斥关系需要维持 --> Mutex加锁 Enqueue/Pop
-
因此我们需要两边锁,一把锁解决pp,一把锁解决cc
-
问题:先申请信号量,还是先加锁?
应该先申请信号量,有了信号量说明该对象将执行对应的Enqueue/Pop操作,再让有了信号量的对象竞争锁访问临界资源,这样排除了没有对应信号量执行操作的对象。如果先加锁,会导致拥有信号量的对象想要访问临界资源时的锁被没有信号量不需要执行操作的对象拿到了锁,从而造成死锁问题。
-
信号量筛选有效线程:只有持有信号量的线程才有资格竞争锁。
-
锁保证操作原子性:避免多个线程同时修改队列。
-
避免死锁:防止无效线程占用锁导致资源饥饿。
-
代码实现:
#pragma once
#include <iostream>
#include <vector>
#include "Sem.hpp"
#include "Mutex.hpp"
static const int gcap = 5; // for debug
using namespace SemModule;
using namespace MutexModule;
template <typename T>
class RingQueue
{
public:
RingQueue(int cap = gcap)
: _cap(cap),
_rq(cap),
_blank_sem(cap),
_p_step(0),
_data_sem(0),
_c_step(0)
{
}
void Equeue(const T &in)
{
// 生产者
// 1. 申请信号量,空位置信号量
_blank_sem.P();
{
LockGuard lockguard(_pmutex);
// 2. 生产
_rq[_p_step] = in;
// 3. 更新下标
++_p_step;
// 4. 维持环形特性
_p_step %= _cap;
}
_data_sem.V();
}
void Pop(T *out)
{
// 消费者
// 1. 申请信号量,数据信号量
_data_sem.P();
{
LockGuard lockguard(_cmutex);
// 2. 消费
*out = _rq[_c_step];
// 3. 更新下标
++_c_step;
// 4. 维持环形特性
_c_step %= _cap;
}
_blank_sem.V();
}
private:
std::vector<T> _rq;
int _cap;
// 生产者
Sem _blank_sem; // 空位置
int _p_step;
// 消费者
Sem _data_sem; // 数据
int _c_step;
// 维护多生产,多消费, 2把锁
Mutex _cmutex;
Mutex _pmutex;
};