POSIX信号量
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突访问资源目的。但POSIX可以用于线程间同步
概念
- 信号量本质就是一把计数器,描述临界资源中资源数目的大小,即最多有多少资源能分配给线程。
- 每个线程想访问临界资源,都得先申请信号量资源
- 如果临界资源可以被划分为一个一个的小资源,我们也有可能让多个线程同时访问临界资源的不同区域,从而实现并发
总的来说,信号量可以看作一把计数器,对信号量的合理使用可以达到对临界资源的预定目的。
操作
既然说信号量可以看作是一个计数器,那么我们先来模拟看看信号量应该是怎样工作的。
start:
lock();
if (count <= 0){
// 挂起
unlock();
goto start;
} else{
count--;
}
unlock();
lock();
count++;
unlock();
初始化
#include <semaphore.h>
// pshared:0表示线程间共享,非零表示进程间共享
// value:信号量初始值
int sem_init(sem_t *sem, int pshared, unsigned int value);
销毁
int sem_destroy(sem_t *sem);
等待
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);
发布
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);
环形队列
基本原理
生产者和消费者在开始的时候指向同一位置
生产者和消费者在队列满的时候指向同一位置
当队列不为空或满的时候生产者和消费者一定指向不同的位置,此时生产和消费可以并发执行
实现思想
对于生产者,主要关心空的位置
对于消费者,主要关心数据资源
规则1:生产者不能把消费者套一圈
规则2:消费者不能超过生产者
规则3:当指向同一位置的时候,要根据空、满的状态来判定谁先执行
除此之外生产和消费可以并发执行
结合环形队列和信号量编写生产消费模型
/* 跟之前写的queue生产消费模型一样我们
仍然创建两个进程分别代表生产者和消费者,
他们分别进行生产和消费操作*/
int main()
{
srand((long long)time(nullptr));
RingQueue<int>* rq = new RingQueue<int>();
pthread_t c, p;
pthread_create(&c, nullptr, consumer, (void*)rq);
pthread_create(&p, nullptr, producter, (void*)rq);
pthread_join(c, nullptr);
pthread_join(p, nullptr);
return 0;
}
消费者和生产者的行为
void* consumer(void* args)
{
RingQueue<int>* rq = (RingQueue<int>*)args;
while (true)
{
int data = 0;
rq->Pop(&data);
std::cout << "消费数据是: " << data << std::endl;
}
}
void* producter(void* args)
{
RingQueue<int>* rq = (RingQueue<int>*)args;
while (true)
{
int data = rand() % 20 + 1;
std::cout << "生产数据是: " << data << std::endl;
rq->Push(data);
sleep(1);
}
}
我们发现消费者和生产者行为的核心就是Pop和Push,因此接下来的代码将实现这两个函数
ring_queue.hpp
#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>
namespace ssj_ring_queue
{
const int g_cap_default = 10;
template <class T>
class RingQueue
{
private:
pthread_mutex_t _mtx_p;
pthread_mutex_t _mtx_c;
std::vector<T> _ring_queue;
int _cap;
// 生产者关心空位资源
sem_t _blank_sem;
// 消费者关心数据资源
sem_t _data_sem;
int _c_step; // 消费者位置
int _p_step; // 生产者位置
public:
RingQueue(int cap = g_cap_default)
: _ring_queue(cap), _cap(cap)
{
pthread_mutex_init(&_mtx_p, nullptr);
pthread_mutex_init(&_mtx_c, nullptr);
sem_init(&_blank_sem, 0, cap);
sem_init(&_data_sem, 0, 0);
_c_step = _p_step = 0;
}
~RingQueue()
{
sem_destroy(&_blank_sem);
sem_destroy(&_data_sem);
pthread_mutex_destroy(&_mtx_p);
pthread_mutex_destroy(&_mtx_c);
}
public:
void Push(const T &in)
{
// 生产接口
sem_wait(&_blank_sem);
pthread_mutex_lock(&_mtx_p);
_ring_queue[_p_step] = in;
_p_step++;
_p_step %= _cap;
pthread_mutex_unlock(&_mtx_p);
sem_post(&_data_sem);
}
void Pop(T* out)
{
// 消费接口
sem_wait(&_data_sem);
pthread_mutex_lock(&_mtx_c);
*out = _ring_queue[_c_step];
_c_step++;
_c_step %= _cap;
pthread_mutex_unlock(&_mtx_c);
sem_post(&_blank_sem);
}
};
}