POSIX信号量
POSIX
信号量和SystemV
信号量作用类似,都用于同步操作;
POSIX
信号量可以用于线程间同步;
对于信号量,简单来说就一个计数器;
POSIX
信号量相关接口:
信号量相关接口都在头文件<semaphore.h>
下
1. 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
初始化信号量sem_init
,存在三个参数:
sem
:指针变量指向要初始化的信号量
pshared
:传递0
表示线程间共享,非0
表示进程间共享
value
:信号量的初始值
2. 销毁信号量
对于创建出来的信号量,在使用完后需要调用sem_destroy
进行销毁。
int sem_destroy(sem_t *sem);
3. P(申请)
等待信号量(申请),也就是P
操作
int sem_wait(sem_t *sem); //P()
调用sem_wait
申请信号量,会将信号量的值减1
。
4. V(释放)
释放信号量,也就是V
操作
int sem_post(sem_t *sem);//V()
资源使用完毕,调用sem_post
释放信号量,信号量的值加1
。
基于环形队列实生产者消费者模型
在之前基于blockqueue
实现的生产者消费者模型中,使用队列当做生产者和消费者的交易场所(临界资源);
生产者和消费者在生产与消费时,都是将blockqueue
当中一个整体,在一个线程访问时,其他的线程(生产者/消费者)都不能访问blockqueue
。
如果我们将临界资源分成几部分,生产者与消费者访问队列(临界资源)时,只访问其中的一部分;这样在生产者生产的过程中,消费者同时也就可以消费。
所以,我们就可以将队列分成几部分,每次访问时只访问其中的一部分。
但是,随着线程运行(生产者生产数据,消费者消费数据),如果使用普通的队列或者数组,其长度就会越来越长;所以,这里使用环形队列;
当环形队列为满时:生产者不能继续生产,阻塞等待。
当环形队列为空时:消费者不能继续消费,阻塞等待直到生产者生成数据。
当环形队列不为空,也不为满时:生产者可以生产数据,消费者可以消费数据(可以同时进行,访问不同的位置)。
1. 环形队列
要基于环形队列实现生产者消费者模型:
首先,要知道环形队列的大小;
生产者和消费者要访问不同的位置,那就也要将生产者和消费者所对应的位置(下标)记录下来;
其次,要维护生产者和生产者之间、消费者和消费者直接的互斥关系,就要存在所对应的互斥量(锁);
最后,要保证队列为满时,生产者不能继续生产;队列为空时,消费者不能继续消费;这里使用信号量(对信号量进行
P
、V
操作)环形队列,可以使用数组或者
vector
来模拟
static int size_default = 5; // 队列默认大小
template <class T>
class ringqueue
{
public:
ringqueue(int sz = size_default) : _sz(sz), _psub(0), _csub(0), _q(sz)
{
pthread_mutex_init(&_pmutex, nullptr);
pthread_mutex_init(&_cmutex, nullptr);
sem_init(&_empty, 0, _sz);
sem_init(&_full, 0, 0);
}
~ringqueue()
{
pthread_mutex_destroy(&_pmutex);
pthread_mutex_destroy(&_cmutex);
sem_destroy(&_full);
sem_destroy(&_empty);
}
private:
std::vector<T> _q;
int _sz; // 环形队列大小
int _psub; // 生产下标
int _csub; // 消费下标
pthread_mutex_t _pmutex; // 生产者互斥量
pthread_mutex_t _cmutex; // 消费者互斥量
sem_t _empty; // 生产者信号量 --- 空位置
sem_t _full; // 消费者信号量 --- 满位置
};
2. 生产
生产者要生产数据:
首先要判断是否存在空位置,就要申请一个信号量_empty
;P(_empty);
其次,为了保证生产者和生产者的互斥关系,就要申请互斥量_pmutex
;
然后,生产者进行生成数据。
最后生产完数据,就可以释放互斥量_pmutex
;此时队列中多了一个数据的,就要释放一个信号量_full
;V(_full);
void Produce(const T &data)
{
sem_wait(&_empty);
pthread_mutex_lock(&_pmutex);
// 生产数据
_q[_psub++] = data;
_psub %= _sz;
pthread_mutex_unlock(&_pmutex);
sem_post(&_full);
}
3. 消费
消费者要消费数据:
首先,判断队列中是否存在数据(满位置),就要申请一个信号量_full
;P(_full);
其次,为了保证消费者和消费者之间的互斥关系,就要申请互斥量_cmutex
;
然后,消费者进行消费数据;
最后消费完数据,就可以释放互斥量_cmutex
;此时队列中多了一个空位置,就要释放一个信号量_empty
;V(_empty);
void Consume(T &data)
{
sem_wait(&_full);
pthread_mutex_lock(&_cmutex);
// 消费数据
data = _q[_csub++];
_csub %= _sz;
pthread_mutex_unlock(&_cmutex);
sem_post(&_empty);
}
4. 测试
到此,就完成了环形队列的设计,现在来让生产者和消费者线程进行生产和消费:(测试)
#include "ringqueue.hpp"
#include <unistd.h>
using namespace RingQueue;
template <class T>
class Thread
{
public:
Thread(ringqueue<T> &rq, std::string name) : _name(name), _rq(rq) {}
~Thread() {}
std::string _name;
ringqueue<T> &_rq;
};
pthread_mutex_t pmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t cmutex = PTHREAD_MUTEX_INITIALIZER;
int count = 1;
void *produce(void *args)
{
Thread<int> *pthread = static_cast<Thread<int> *>(args);
while (true)
{
// 生产数据
pthread_mutex_lock(&pmutex);
pthread->_rq.Produce(count);
std::cout << pthread->_name << " 生产了一个数据 : " << count << std::endl;
count++;
pthread_mutex_unlock(&pmutex);
sleep(1);
}
return (void *)"1";
}
void *consume(void *args)
{
Thread<int> *pthread = static_cast<Thread<int> *>(args);
while (true)
{
// 消费数据
sleep(1);
pthread_mutex_lock(&cmutex);
int msg = 0;
pthread->_rq.Consume(msg);
std::cout << pthread->_name << " 消费了一个数据 : " << msg << std::endl;
pthread_mutex_unlock(&cmutex);
}
return (void *)"0";
}
int main()
{
pthread_t t1, t2, t3, t4;
ringqueue<int> rq;
// t1,t2生产
Thread td1(rq, "thread-1 ");
pthread_create(&t1, nullptr, produce, &td1);
Thread td2(rq, "thread-2 ");
pthread_create(&t2, nullptr, produce, &td2);
// t3,t4消费
Thread td3(rq, "thread-3 ");
pthread_create(&t3, nullptr, consume, &td3);
Thread td4(rq, "thread-4 ");
pthread_create(&t3, nullptr, consume, &td4);
pthread_join(t1, nullptr);
pthread_join(t2, nullptr);
pthread_join(t3, nullptr);
pthread_join(t4, nullptr);
return 0;
}
在上述代码中,设计了Thread
类方便测试;创建新线程thread-1和thread-2生产数据、新线程thread-3和thread-4消费数据。