环形队列
- 前言
- sem信号量
- 程序代码
- pthread.hpp
- 代码说明
- RingQueue.cc
- 代码说明
- Makefile
- 运行
前言
sem信号量
sem_t 是信号量(Semaphore)的数据类型,用于在多线程或多进程环境中实现线程同步和资源控制。
信号量是一个计数器,用来控制对共享资源的访问。它主要有两个基本操作:wait 和 post,也被称为 P 操作和 V 操作。wait 操作用于申请资源并等待资源可用,若资源不可用,则线程或进程进入阻塞状态。post 操作用于释放资源,使得其他线程或进程可以继续执行。
在 C/C++ 中,信号量的相关接口主要包括以下函数:
sem_init(sem_t *sem, int pshared, unsigned int value):初始化信号量。sem 是指向信号量变量的指针;pshared 表示信号量是否在多个进程之间共享,一般在线程间使用时设置为 0;value 是信号量的初始值。
sem_wait(sem_t *sem):执行 P 操作。如果信号量的值大于 0,将信号量的值减 1,继续执行。如果信号量的值为 0,则线程或进程进入阻塞状态,直到有其他线程或进程执行了 sem_post 操作来释放资源。
sem_trywait(sem_t *sem):非阻塞的 P 操作。与 sem_wait 类似,但如果信号量的值为 0,则不会阻塞线程或进程,而是直接返回,并返回错误码为 EAGAIN。
sem_timedwait(sem_t *sem, const struct timespec *abs_timeout):超时 P 操作。与 sem_wait 类似,但如果信号量的值为 0,则线程或进程将等待指定的超时时间,超过时间后仍未获得资源,则返回错误码为 ETIMEDOUT。
sem_post(sem_t *sem):执行 V 操作。将信号量的值加 1,并通知等待在该信号量上的其他线程或进程可以继续执行。
sem_getvalue(sem_t *sem, int *sval):获取信号量的当前值,并将其保存在 sval 中。
sem_destroy(sem_t *sem):销毁信号量,释放相关资源。
使用信号量可以实现对共享资源的有序访问和线程同步,保证多个线程或进程之间的正确协作,避免资源竞争和死锁等问题。
程序代码
pthread.hpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <semaphore.h>
const int gCap=5;
template<class T>
class RingQueue
{
public:
RingQueue(int cap=gCap):_ringqueue(cap),_Pindex(0),_Cindex(0)
{
// 生产
sem_init(&_roomSem, 0, _ringqueue.size());
// 消费
sem_init(&_dataSem, 0, 0);
pthread_mutex_init(&_pmutex ,nullptr);
pthread_mutex_init(&_cmutex,nullptr);
}
~RingQueue()
{
sem_destroy(&_roomSem);
sem_destroy(&_dataSem);
pthread_mutex_destroy(&_pmutex);
pthread_mutex_destroy(&_cmutex);
}
void push(const T &in)
{
sem_wait(&_roomSem);//挂起等待机制 确保无法被多次申请
//加锁 临界区
pthread_mutex_lock(&_pmutex);
_ringqueue[_Pindex]=in;
_Pindex++;
_Pindex%=_ringqueue.size();//更新下标 确保环形队列
pthread_mutex_unlock(&_pmutex);
sem_post(&_dataSem);//唤醒该线程下面等待的其他线程执行
}
T pop()
{
sem_wait(&_dataSem);
pthread_mutex_lock(&_cmutex);
T temp = _ringqueue[_Cindex];
_Cindex++;
_Cindex %= _ringqueue.size();// 更新下标,保证环形特征
pthread_mutex_unlock(&_cmutex);
sem_post(&_roomSem);
return temp;
}
private:
std::vector <T> _ringqueue;//环形队列
sem_t _roomSem;//衡量空间计数器,生产者
sem_t _dataSem;//衡量数据计数器,消费者
uint32_t _Pindex;//当前生产者写入的位置,如果是多线程,那么要作为临界资源处理
uint32_t _Cindex;//当前消费者读取的位置
pthread_mutex_t _pmutex;//消费者线程
pthread_mutex_t _cmutex;//生产者线程
};
代码说明
这是一个环形队列(RingQueue)的C++实现。环形队列是一种数据结构,可以用于在生产者和消费者之间传递数据,且当队列满时会从队列的起始位置开始覆盖数据。以下是对代码的详细解释:
成员变量:
_ringqueue:一个std::vector类型的容器,用于存储队列数据。
_roomSem和_dataSem:两个信号量,用于生产者和消费者之间的同步。其中,_roomSem信号量用于保护队列剩余空间的计数,_dataSem信号量用于保护队列中数据项的计数。
_Pindex和_Cindex:两个uint32_t类型的索引,分别表示生产者和消费者在队列中的位置。
_pmutex和_cmutex:两个pthread_mutex_t类型的互斥量,用于保护_Pindex和_Cindex。
构造函数和析构函数:
RingQueue(int cap=gCap):构造函数,接受一个可选参数表示队列的容量,初始化环形队列、信号量和互斥量。
~RingQueue():析构函数,销毁环形队列中的信号量和互斥量。
成员函数:
push(const T &in):生产者调用的函数,将一个元素添加到环形队列中。首先,它等待_roomSem信号量,以确保队列中有空间可用。然后,它获取_pmutex互斥量来保护生产者索引,将元素添加到队列,并更新生产者索引。最后,释放互斥量并发布_dataSem信号量,表示队列中的数据项数量增加了。
pop():消费者调用的函数,从环形队列中取出一个元素。首先,它等待_dataSem信号量,以确保队列中有数据可用。然后,它获取_cmutex互斥量来保护消费者索引,从队列中取出元素,并更新消费者索引。最后,释放互斥量并发布_roomSem信号量,表示队列中的可用空间增加了。
这个类实现了生产者-消费者问题的一种解决方案,其中生产者和消费者可以是在不同线程中运行的函数或方法。这种模式常用于多线程编程,例如,生产者可以是生成数据的线程,而消费者可以是处理数据的线程。
RingQueue.cc
#include"pthread.hpp"
#include <ctime>
#include <unistd.h>
using namespace std;
void *productor(void *args)
{
RingQueue<int> *rqp = static_cast<RingQueue<int> *>(args);
while(true)
{
int data = rand()%10;
rqp->push(data);
cout << "pthread[" << pthread_self() << "]" << " 生产了一个数据: " << data << endl;
sleep(1);
}
}
void *consumer(void *args)
{
RingQueue<int> *rqp = static_cast<RingQueue<int> *>(args);
while(true)
{
//sleep(10);
int data = rqp->pop();
cout << "pthread[" << pthread_self() << "]" << " 消费了一个数据: " << data << endl;
}
}
int main()
{
srand((unsigned long)time(nullptr)^getpid());
RingQueue<int> rq;
pthread_t c1,c2,c3, p1,p2,p3;
pthread_create(&p1, nullptr, productor, &rq);
pthread_create(&p2, nullptr, productor, &rq);
pthread_create(&p3, nullptr, productor, &rq);
pthread_create(&c1, nullptr, consumer, &rq);
pthread_create(&c2, nullptr, consumer, &rq);
pthread_create(&c3, nullptr, consumer, &rq);
pthread_join(c1, nullptr);
pthread_join(c2, nullptr);
pthread_join(c3, nullptr);
pthread_join(p1, nullptr);
pthread_join(p2, nullptr);
pthread_join(p3, nullptr);
return 0;
return 0;
}
代码说明
这是一个C++程序,使用前面定义的RingQueue类实现一个多线程的生产者-消费者模型。以下是对代码的详细解释:
函数:
productor(void *args):这是生产者线程的入口函数。它从传入的参数中获取一个RingQueue对象的指针,然后在一个无限循环中不断向队列中添加随机数。每次添加一个数字后,它会打印一条信息,并休眠一秒。
consumer(void *args):这是消费者线程的入口函数。它从传入的参数中获取一个RingQueue对象的指针,然后在一个无限循环中不断从队列中取出数字。每次取出一个数字后,它会打印一条信息。
主函数:
首先,它使用当前时间和进程ID来初始化随机数生成器。
创建一个RingQueue实例。
创建三个生产者线程和三个消费者线程。每个线程的入口函数分别是productor和consumer,并且都传入了RingQueue的地址作为参数。
使用pthread_join函数等待所有的线程结束(尽管在这个程序中,线程并不会自行结束)。
这个程序展示了如何在多线程环境中使用RingQueue实现生产者-消费者模型。生产者线程生成数据并添加到队列,消费者线程从队列中取出数据并处理。由于RingQueue类内部使用了信号量和互斥量来同步线程,所以这个程序可以在多线程环境中安全地使用。
Makefile
CC=g++
FLAGS=-std=c++11
LD=-lpthread
bin=test
src=RingQueue.cc
$(bin):$(src)
$(CC) -o $@ $^ $(LD) $(FLAGS)
.PHONY:clean
rm -f $(bin)