双缓冲区实现


#include <iostream>
#include <boost/thread.hpp>
#include <boost/interprocess/sync/interprocess_semaphore.hpp>

using namespace boost::interprocess;

const int size = 5;

struct Data_buff
{
    Data_buff()
        :putSemaphore(2), getSemaphore(0), getMutex(1)
    {
        put  = buf1;
        nput = buf2;
        get  = 0;
    }

    int buf1[size];
    int buf2[size];

    int *put;
    int *nput;
    int *get;

    interprocess_semaphore putSemaphore,    // 是否可以进行数据的添加
                           getSemaphore,    // 是否可以进行数据的读取
                           getMutex;        // 是否可以进行指针的更改
};

struct MutexBuf
{
    boost::mutex dataMutex;                  // 数据访问的互斥变量
    boost::condition_variable_any putCond;   // 是否可以进行数据添加,如果没有缓冲区则不能,添加完一个缓冲区的数据,但是read没有释放get
    boost::condition_variable_any getCond;   // 是否可以进行数据读取,如果get为0,则不能

    int buf1[size];
    int buf2[size];

    int *put;
    int *nput;
    int *get;

    MutexBuf()
    {
        put  = buf1;
        nput = buf2;
        get  = 0;
    }
};


const int total = 4*size;
int putcount = 0;
int getcount = 0;
Data_buff buf;
MutexBuf mutexBuf;

void MutexPut()
{
    while(true)
    {
        if (putcount >= total)
            return;
      
        for( int i = 0; i < size; ++i )
            mutexBuf.put[i] = putcount + i;

        putcount += size;

        {
            boost::mutex::scoped_lock lock(mutexBuf.dataMutex);
            // 如果表征不能在缓冲区中添加数据:添加完一个缓冲区中的数据,但是get没有释放掉,则不能添加
            while(mutexBuf.get)
                mutexBuf.putCond.wait(mutexBuf.dataMutex);

            mutexBuf.get = mutexBuf.put;
            mutexBuf.put = mutexBuf.nput;

            // 通知读者,可以进行数据的操作
            mutexBuf.getCond.notify_one();
        }
    }
}

void MutexRead()
{
    while(true)
    {
        if(getcount >= total)
            return;

        {
            boost::mutex::scoped_lock lock(mutexBuf.dataMutex);
            // 判断是否可以进行数据读
            while(!mutexBuf.get)
                mutexBuf.getCond.wait(mutexBuf.dataMutex);
        }

        std::cout << "addr = " << mutexBuf.get << "\n";
        for( int i = 0; i < size; ++i )
            std::cout << "value = " << mutexBuf.get[i] << ",";
        std::cout << "\n";
        getcount += size;

        {
            boost::mutex::scoped_lock lock(mutexBuf.dataMutex);
            // 释放get的读操作区域
            mutexBuf.nput = mutexBuf.get;
            mutexBuf.get  = 0;
            mutexBuf.putCond.notify_one();
        }
    }
}

void put()
{
    while(true)
    {
        if (putcount >= total)
            return;

        buf.putSemaphore.wait();

        for( int i = 0; i < size; ++i )
            buf.put[i] = putcount + i;
        putcount += size;

        // 判断get是否被read用了,如果用了才可以在重新设置
        buf.getMutex.wait();
        buf.get = buf.put;
        buf.put = buf.nput;

        // get可用,通知get
        buf.getSemaphore.post();
    }
}

void read()
{
    while(true)
    {
        if (getcount >= total)
            return;

        buf.getSemaphore.wait();

        std::cout << "addr = " << buf.get << "\n";
        for( int i = 0; i < size; ++i )
            std::cout << "value = " << buf.get[i] << ",";
        std::cout << "\n";

        getcount += size;

        buf.nput = buf.get;
        buf.getMutex.post();      // get被read用完,那么可以进行改写
        buf.putSemaphore.post();  // 可以进行数据的put
    }
}

int main(int argc, char *argv[])
{
    boost::thread_group gr;
    gr.create_thread(MutexPut);
    boost::this_thread::sleep(boost::posix_time::seconds(5));


    for( int i = 0 ; i < size; ++i )
    {
        std::cout << "buf1["<< i << "] = " << mutexBuf.buf1[i] << ",";
    }
    std::cout << "\n";

    for( int i = 0 ; i < size; ++i )
    {
        std::cout<< "buf2["<< i << "] = " << mutexBuf.buf2[i] << ",";
    }
    std::cout << "\n";

    boost::this_thread::sleep(boost::posix_time::seconds(5));

    gr.create_thread(MutexRead);


    gr.join_all();

    return 0;
};

<think>嗯,用户现在想了解如何使用C++条件变量实现队列的双缓冲区解析。我需要先回顾一下之前的对话,用户之前已经询问过双缓冲区实现,我给出了一个具体的代码示例。现在用户可能希望更深入,特别是结合队列结构来实现双缓冲区,或者想了解如何将双缓冲区应用于队列的场景。 首先,我需要确认用户的需求。他们提到的“queue的双缓冲区”可能是指使用两个队列作为缓冲区,交替进行生产和消费。这可能与传统的双缓冲区(使用两个数组或向量)有所不同,但核心思想相似,都是通过交替使用两个缓冲区来减少锁竞争,提高并发性能。 接下来,我要考虑如何将条件变量与双队列结合起来。在之前的例子中,使用了两个向量作为缓冲区,并通过交换指针来切换。而队列的实现可能需要不同的同步机制,因为队列本身是动态增长的,可能需要处理不同的生产消费速率。 用户可能希望了解如何用两个队列来交替填充和消费,确保生产者和消费者不会同时操作同一个队列。这时,条件变量可以用来通知对方缓冲区已满或已空的状态。例如,生产者填充一个队列,当队列满时切换到另一个队列,并通知消费者处理已满的队列。消费者处理完队列后,切换回来,并通知生产者继续填充。 需要注意的问题包括:如何正确使用条件变量来等待和通知,避免死锁,处理边界情况(如终止条件),以及如何高效地切换队列。此外,使用std::queue还是其他容器类型,以及是否需要限制队列的大小(例如固定容量)也是需要考虑的。 用户可能对实现细节感兴趣,比如如何设计类的成员变量,如何管理两个队列的切换,以及如何确保线程安全。需要确保在切换缓冲区时,生产者和消费者不会同时访问同一个队列,这可能需要互斥锁的保护。 另外,用户可能想了解这种实现与传统单队列的优缺点比较,比如性能提升、实现的复杂性、适用场景等。需要解释双缓冲区如何减少锁的争用,允许生产者和消费者并行处理不同的缓冲区,从而提高吞吐量。 可能还需要提到条件变量与信号量的区别,何时选择条件变量而不是其他同步机制,尤其是在需要复杂条件判断的情况下,条件变量更为灵活。 最后,提供示例代码是关键,这样用户可以看到具体的实现方式,包括类的结构、线程函数、同步逻辑等。同时,代码注释和解释可以帮助用户理解每个部分的作用,避免混淆。 总结一下,回答需要涵盖双缓冲队列的设计思路、同步机制、代码实现、关键点解析以及相关问题,确保用户能够全面理解如何使用C++条件变量实现双缓冲队列,并解决实际中的线程同步问题。</think>### C++条件变量实现双缓冲队列详解 双缓冲队列通过交替使用两个队列容器实现生产消费解耦,结合条件变量可构建高效线程安全模型。以下为基于C++11标准的完整实现方案: #### 一、核心设计思路 1. **双队列结构**:使用`active_queue`和`process_queue`实现物理隔离 2. **状态标识**:通过`bool`标志位控制缓冲区的可操作状态 3. **同步工具**: - `std::mutex`保证操作的原子性 - `std::condition_variable`实现线程间精准通知 4. **切换机制**:当活跃队列达到容量阈值时触发缓冲区交换[^1] #### 二、完整实现代码 ```cpp #include <queue> #include <thread> #include <mutex> #include <condition_variable> template<typename T> class DoubleBufferedQueue { private: std::queue<T> queueA, queueB; std::queue<T>* active_queue = &queueA; std::queue<T>* process_queue = &queueB; std::mutex mtx; std::condition_variable cv_producer, cv_consumer; bool processing = false; bool terminate = false; const size_t MAX_SIZE = 10; // 单个队列最大容量 public: // 生产者接口 void push(const T& item) { std::unique_lock<std::mutex> lock(mtx); cv_producer.wait(lock, [this]{ return active_queue->size() < MAX_SIZE || terminate; }); if (terminate) return; active_queue->push(item); // 触发消费者处理的条件 if (active_queue->size() >= MAX_SIZE) { processing = true; cv_consumer.notify_one(); } } // 消费者接口 std::queue<T> consume() { std::unique_lock<std::mutex> lock(mtx); cv_consumer.wait(lock, [this]{ return processing || terminate; }); if (terminate && !processing) return std::queue<T>(); // 返回空队列 // 交换缓冲区 std::swap(active_queue, process_queue); processing = false; // 转移数据到临时队列 std::queue<T> output_queue; std::swap(*process_queue, output_queue); cv_producer.notify_all(); // 唤醒所有生产者 return output_queue; } void shutdown() { std::lock_guard<std::mutex> lock(mtx); terminate = true; cv_producer.notify_all(); cv_consumer.notify_all(); } }; ``` #### 三、关键机制解析 1. **双队列切换**: - 生产者始终向`active_queue`写入 - 消费者处理`process_queue`时自动触发交换操作 - 使用指针交换避免数据拷贝(`std::swap`时间复杂度$O(1)$) 2. **容量控制**: - 当活跃队列达到`MAX_SIZE`时阻塞生产者 - 消费者处理完成后通过`cv_producer`唤醒生产者 3. **终止处理**: - `shutdown()`方法设置终止标志 - 双条件变量确保线程安全退出 - 返回空队列作为终止信号[^2] 4. **条件变量优化**: - 使用独立条件变量`cv_producer`和`cv_consumer` - 避免虚假唤醒(spurious wakeup)的lambda检测条件 - 通过`notify_all()`应对多生产者场景 #### 四、使用示例 ```cpp // 生产者线程函数 void producer(DoubleBufferedQueue<int>& dbq) { for (int i = 0; i < 25; ++i) { dbq.push(i); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } dbq.shutdown(); } // 消费者线程函数 void consumer(DoubleBufferedQueue<int>& dbq) { while (true) { auto data = dbq.consume(); if (data.empty()) break; std::cout << "处理数据: "; while (!data.empty()) { std::cout << data.front() << " "; data.pop(); } std::cout << "\n"; } } int main() { DoubleBufferedQueue<int> dbq; std::thread p1(producer, std::ref(dbq)); std::thread p2(producer, std::ref(dbq)); std::thread c(consumer, std::ref(dbq)); p1.join(); p2.join(); c.join(); } ``` #### 五、性能优化点 1. **批量处理**:消费者每次处理整个队列提升缓存利用率 2. **无锁时段**:生产者填充时消费者可并行处理数据 3. **动态扩容**:根据负载自动调整`MAX_SIZE`值 4. **等待策略**:使用`wait_for()`实现超时机制 #### 六、与传统方案的对比 | 特性 | 单队列加锁 | 双缓冲队列 | |--------------------|------------|------------------| | 生产者阻塞概率 | 高 | 低(双缓冲切换) | | 消费者处理延迟 | 随机 | 可预测批量 | | 内存占用 | 低 | 2×单队列容量 | | 适用场景 | 低吞吐 | 高吞吐流式处理 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值