生产者消费者双缓冲模式

#include "stdafx.h"
#include <windows.h>
#include <process.h>

template <typename T>
class CircularQueue
{
public:
    explicit CircularQueue(int capacity) : m_capacity(capacity), m_head(0), m_tail(0)
    {
        m_array = new T[m_capacity + 1];
    }

    ~CircularQueue()
    {
        delete[] m_array;
        m_capacity = m_head = m_tail = 0;
    }

    bool IsFull()
    {
        int offset = (m_tail + 1) % (m_capacity + 1);

        return (offset == m_head);
    }

    bool IsEmpty()
    {
        return (m_tail == m_head);
    }

    void Push(const T& item)
    {
        if ( !IsFull() )
        {
            m_array[m_tail] = item;
            m_tail = (m_tail + 1) % (m_capacity + 1);
        }
    }

    T Pop()
    {
        if ( IsEmpty() )
        {
            return T();
        }

        int index = m_head;
        m_head = (m_head + 1) % (m_capacity + 1);

        return m_array[index];
    }

private:
    T* m_array; // 缓冲区队列
    int m_capacity; // 队列最大存储容量
    int m_head; // 队列头指针
    int m_tail; // 队列尾指针
};

// 同步队列
template <typename T>
struct SynQueue
{
    SynQueue(int size) : buffer(size)
    {
        synEventHandle = CreateEvent(NULL, FALSE, FALSE, 0);
    }

    ~SynQueue()
    {
        CloseHandle(synEventHandle);
    }

    operator HANDLE()
    {
        return synEventHandle;
    }

    CircularQueue<T> buffer; // 缓冲区
    HANDLE synEventHandle; // 同步事件
};

class PacketQueue
{
public:
    explicit PacketQueue(int size) : m_readQueue(size), 
        m_sendQueue(size), m_bFreezeQueue(false)
    {
    }

    // 刷新读取缓冲区,使得发送线程有机会接管读取缓冲区,在读取完毕后调用
    void Flush()
    {
        SetEvent(m_readQueue);
        SetEvent(m_sendQueue);
    }

    // 冻结缓冲区
    void FreezeQueue()
    {
        m_bFreezeQueue = true;
    }

    // 从发送缓冲区取出一个数据包
    int Popup()
    {
        static SynQueue<int>* pSendQueue = &m_sendQueue;
        static SynQueue<int>* pReadQueue = &m_readQueue;

        // 判断发送缓冲区数据包是否为空
        if ( pSendQueue->buffer.IsEmpty() )
        {
            // 释放当前发送缓冲区
            SetEvent(pSendQueue->synEventHandle);

            // 得到当前读取缓冲区
            pReadQueue = ExchangeQueue(pSendQueue);

            // 接管当前读取缓冲区的拥有权
            WaitForSingleObject(pReadQueue->synEventHandle, INFINITE);

            // 接管读取缓冲区
            pSendQueue = pReadQueue;
        }

        // 从缓冲区取出一个数据包
        return pSendQueue->buffer.Pop();
    }

    bool Push(int item)
    {
        static SynQueue<int>* pReadQueue = &m_readQueue;
        static SynQueue<int>* pSendQueue = &m_sendQueue;

        if ( m_bFreezeQueue )
        {
            return false;
        }

        // 判断读取缓冲区是否数据已填满
        if ( pReadQueue->buffer.IsFull() )
        {
            // 释放当前缓冲区拥有权
            SetEvent(pReadQueue->synEventHandle);

            pSendQueue = ExchangeQueue(pReadQueue);

            // 等待接管另一个缓冲区的拥有权
            WaitForSingleObject(pSendQueue->synEventHandle, INFINITE);

            // 接管另一个缓冲区
            pReadQueue = pSendQueue;
        }

        // 插入数据包
        pReadQueue->buffer.Push(item);

        return true;
    }

private:
    // 交换缓冲区
    SynQueue<int>* ExchangeQueue(SynQueue<int>* queue)
    {
        if ( (queue != &m_readQueue) && (queue != &m_sendQueue) )
        {
            return 0;
        }

        return (queue == &m_readQueue ? &m_sendQueue : &m_readQueue);
    }

private:
    // 双缓冲队列
    SynQueue<int> m_readQueue; // 数据读取缓冲区
    SynQueue<int> m_sendQueue; // 数据发送缓冲区

    bool m_bFreezeQueue; // 发送线程出现致命错误,冻结缓冲区
};

// 读取线程入口函数
unsigned WINAPI ReadThreadEntry( PVOID param )
{
    PacketQueue* queue = (PacketQueue*)param;

    // 插入100个数据包
    for ( int i = 0; i < 10000; ++i )
    {
        Sleep(100); // 读取间隔时间

        if ( !queue->Push(i) )
        {
            goto exit;
        }

        printf("intput data: %d\n", i);
    }

exit:

    // 插入结束包
    queue->Push(-1);
    printf("input end data\n");

    // 刷新读缓冲区
    queue->Flush();
    printf("flush read buffer\n");

    return 0;
}

// 发送线程入口函数
unsigned WINAPI SendThreadEntry( PVOID param )
{
    PacketQueue* queue = (PacketQueue*)param;

    // 循环读取数据包
    for (;;)
    {
        //queue->FreezeQueue();
        //return 0;

        int item = queue->Popup();
        if ( -1 == item )
        {
            printf("send thread fetch end data\n");
            break; // 遇到结束包
        }

        //Sleep(150);
        printf("send thread fetch data: %d\n", item);
    }

    return 0;
}

int main(int argc, char* argv[])
{
    PacketQueue queue(50);

    // 启动读取线程
    HANDLE hReadThread = (HANDLE)_beginthreadex(NULL, 0, ReadThreadEntry, (void*)&queue, 0, NULL);

    // 启动发送线程
    HANDLE hSendThread = (HANDLE)_beginthreadex(NULL, 0, SendThreadEntry, (void*)&queue, 0, NULL);

    DWORD T1 = GetTickCount();

    WaitForSingleObject(hReadThread, INFINITE);
    WaitForSingleObject(hSendThread, INFINITE);

    printf("total time: %d", GetTickCount() - T1);

    return 0;
}
<think>好的,用户让我介绍一下单片机开发中的生产者消费者模式。首先,我需要确认自己对这一概念的理解是否正确。生产者消费者模式是一种常见的多线程协作模型,用于解耦生产数据和消费数据的过程。在单片机这样的嵌入式系统中,由于资源有限,这种模式的应用可能有一些特定的实现方式。 接下来,我需要考虑用户可能的背景。用户可能正在学习单片机开发,或者在项目中遇到了需要处理数据生产和消费的问题。他们可能想了解如何在资源受限的环境下有效管理任务间的数据传递,避免竞争条件和资源冲突。 然后,我需要组织回答的结构。首先解释基本概念,然后说明在单片机中的应用,接着给出一个具体的例子,比如使用环形缓冲区和中断服务程序。还要提到注意事项,比如资源限制和同步机制,以及可能的优化方法。 需要注意的是,用户可能对实时性、资源管理、中断处理等概念不太熟悉,所以要用简单的语言解释。同时,确保例子贴近实际应用,比如传感器数据采集和处理的场景,这样用户更容易理解。 另外,要检查是否有遗漏的关键点。比如,是否提到了互斥锁或信号量在单片机中的实现方式,是否有考虑无操作系统的环境下的实现,以及可能遇到的挑战,如缓冲区溢出等。 最后,总结生产者消费者模式在单片机开发中的优势,比如解耦、提高效率和稳定性,帮助用户理解其重要性。确保回答准确、清晰,并且符合用户的需求层次,从理论到实际应用都有覆盖。</think>在单片机开发中,生产者-消费者模式是一种常用的任务协作模型,特别适合处理数据产生和处理速度不匹配的场景。以下是详细说明: 一、基本概念 1. 生产者:负责生成数据(如传感器采集、串口接收) 2. 消费者:负责处理数据(如算法运算、数据存储) 3. 缓冲区:连接两者的存储区域(常用环形队列实现) 二、典型应用场景 - 串口通信:接收中断为生产者,协议解析为消费者 - 数据采集:ADC采样为生产者,滤波算法为消费者 - 事件处理:按键扫描为生产者,业务逻辑为消费者 三、实现示例(基于C语言) ```c #define BUFFER_SIZE 32 // 环形缓冲区定义 typedef struct { uint8_t buffer[BUFFER_SIZE]; volatile uint16_t head; // 写指针 volatile uint16_t tail; // 读指针 } RingBuffer; RingBuffer rx_buffer; // 生产者(中断服务程序) void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE)) { uint8_t data = USART_ReceiveData(USART1); uint16_t next_head = (rx_buffer.head + 1) % BUFFER_SIZE; if(next_head != rx_buffer.tail) { // 缓冲区未满 rx_buffer.buffer[rx_buffer.head] = data; rx_buffer.head = next_head; } } } // 消费者(主循环处理) void process_data(void) { while(rx_buffer.tail != rx_buffer.head) { uint8_t data = rx_buffer.buffer[rx_buffer.tail]; rx_buffer.tail = (rx_buffer.tail + 1) % BUFFER_SIZE; // 实际处理逻辑 printf("Received: %d\n", data); } } ``` 四、关键注意事项 1. 原子操作保护:对head/tail的读写需保证原子性(关闭中断或使用信号量) 2. 缓冲区大小:根据数据产生/消费的最大速率差确定 3. 阻塞机制:当缓冲区空/满时的等待策略(轮询/事件驱动) 4. 内存屏障:防止编译器优化导致的数据访问顺序问题 五、优化方向 - 双缓冲区切换:DMA传输时常用 - 优先级控制:高优先级任务及时消费关键数据 - 动态扩容:在内存允许时自动调整缓冲区大小 - 错误处理:增加溢出计数器等监控机制 这种模式在单片机开发中能有效解耦不同速率的任务,提升系统实时性和资源利用率,是嵌入式系统中实现任务间通信的基础设计模式之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值