【Linux多线程】一个基于环形队列实现的案例

【Linux多线程】一个基于环形队列实现的案例_第1张图片

环形队列

  • 前言
  • 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 
#include 
#include 
#include 
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 
#include 
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)

运行

【Linux多线程】一个基于环形队列实现的案例_第2张图片

你可能感兴趣的:(Linux,linux,运维,服务器,环形队列)