基于pthread的C++阻塞队列模板类的实现

来源:互联网 发布:mysql事务锁 编辑:程序博客网 时间:2024/05/17 12:06

在多线程处理数据时,常用生产者/消费者模型,而在这两者之间存在着一个仓库,通常使用阻塞队列实现。
阻塞队列即消费者发现队列为空时,阻塞在读取操作之前;生产者写入队列时,通知消费者解除阻塞状态,处理新的数据。这样即保证了操作的实时性,又避免了频繁轮询造成的不必要CPU消耗。
java中自带阻塞队列的实现,c++中则没有。在此使用pthread库的互斥量和条件变量实现一个简单的阻塞队列,并用模板进行封装。在使用时,将以下头文件包含进工程即可。
cblkque.h

#include<queue>#include<pthread.h>template<typename T>class BlockingQueue {public:    BlockingQueue () {        pthread_mutex_init(&_mutex, NULL);        pthread_cond_init(&_cond,NULL);    }    ~BlockingQueue() {}    void put(const T &input) {        pthread_mutex_lock(&_mutex);        _queue.push(input);        pthread_mutex_unlock(&_mutex);        pthread_cond_signal(&_cond);    }    T take() {        pthread_mutex_lock(&_mutex);        while(_queue.empty()) {            pthread_cond_wait(&_cond, &_mutex);        }        T front = _queue.front();        _queue.pop();        pthread_mutex_unlock(&_mutex);        return front;    }    size_t size() {        pthread_mutex_lock(&_mutex);        int num = _queue.size();        pthread_mutex_unlock(&_mutex);        return num;    }private:    pthread_mutex_t _mutex ;    pthread_cond_t _cond;    std::queue<T> _queue;};

一个简单的测试程序,程序的输出结果为顺序打印0-9:
test.cpp:

#include<iostream>#include"cblkque.h"BlockingQueue<int> bque;void *thd1(void* ) {    for(int i=0; i<10; i++) {        bque.put(i);    }}void *thd2(void*) {    for(int i=0; i<10; i++) {        std::cout << bque.take();    }    std::cout <<std::endl;}int main(void) {    pthread_t tid1,tid2;    pthread_create(&tid1,NULL,thd1,NULL);    pthread_create(&tid2,NULL,thd2,NULL);    pthread_join(tid1,NULL);    pthread_join(tid2,NULL);    return 0;}

注意,由于基于pthread库,编译时需要添加-lpthread选项。如g++ -o test test.cpp -lpthread
本来为了更好地进行封装,打算把这个模板编译为一个动态共享对象(so文件)并配合头文件使用。编译通过了,调用时始终提示找不到相关函数的定义。查阅资料发现,根本不可能将模板封装进二进制库中。
https://stackoverflow.com/questions/1724036/splitting-templated-c-classes-into-hpp-cpp-files-is-it-possible
摘录如下:(google翻译效果真棒啊)

不可能在单独的cpp文件中编写模板类的实现并进行编译。所有这样做的方法,如果有人声称,是解决方案来模拟单独的cpp文件的使用,但实际上如果你打算写一个模板类库并分发它与头和lib文件来隐藏实现,这是根本不可能的。
要知道为什么,让我们来看一下编译过程。头文件不会被编译。它们只是预处理。然后将预处理的代码与实际编译的cpp文件进行协调。现在,如果编译器必须为对象生成适当的内存布局,则需要知道模板类的数据类型。
实际上,必须了解的是,模板类不仅仅是一个类的模板,它的声明和定义是编译器在从参数获取数据类型的信息之后在编译时生成的。只要无法创建内存布局,就无法生成方法定义的指令。记住class方法的第一个参数是’this’运算符。所有类的方法都转换成具有名称变化的单个方法,第一个参数作为它所操作的对象。 ‘this’参数实际上讲述了模板类不能用于编译器的对象的大小,除非用户使用有效的类型参数来实例化对象。在这种情况下,如果将方法定义放在单独的cpp文件中,并尝试编译它,则不会使用类信息生成对象文件本身。编译不会失败,它会生成对象文件,但不会为对象文件中的模板类生成任何代码。这就是为什么链接器无法在目标文件中找到符号,并且构建失败的原因。 现在,隐藏重要实现细节的替代方法是什么?众所周知,分离接口与实现的主要目标是以二进制形式隐藏实现细节。这是您必须分离数据结构和算法的地方。您的模板类只能表示数据结构,而不是算法。这使您能够在单独的非模板化类库中隐藏更有价值的实现细节,该类可以在模板类上工作,也可以使用它们来容纳数据。模板类实际上将包含较少的代码来分配,获取和设置数据。其余的工作将由算法类完成。