面试手撕---线程安全双缓冲区---C语言
2026/8/29 4:53:57 网站建设 项目流程

线程安全的乒乓缓冲区(单写者,多读者)

伪代码

结构体 PingPong{缓冲区 buffer[2]原子变量 version// 从1开始大小 data_size}/******************************************/函数Init(handle_ptr,data_size):分配 PingPong 结构体内存 分配 buffer[0]内存(data_size 字节)分配 buffer[1]内存(data_size 字节)将两个缓冲区清零 设置 data_size 设置 version=1// 1表示buffer[0]可读,buffer[1]可写*handle_ptr=指向结构体 返回 成功 函数Destroy(ppb):释放 buffer[0]释放 buffer[1]设置 data_size=0设置 version=0释放 ppb 结构体/******************************************/函数Write(ppb,data):// 获取当前版本号current_version=原子加载(ppb->version)// 计算写入缓冲区索引:偶数写buffer[0],奇数写buffer[1]write_idx=current_version%2// 将数据复制到写缓冲区拷贝 data 到 ppb->buffer[write_idx](data_size 字节)// 版本号+1,使刚写入的缓冲区变为可读原子增加 ppb->version(1)//版本号一直累加,直达溢出,依然安全---只区分奇偶返回 成功 函数Read(ppb,data):循环:// 读取版本号version_before=原子加载(ppb->version)// 计算读缓冲区索引(前一个版本)read_idx=(version_before-1)%2// 复制数据拷贝 ppb->buffer[read_idx]data(data_size 字节)// 再次读取版本号,检查是否发生变化version_after=原子加载(ppb->version)//如果版本号未变,数据一致,读取成功如果 version_before==version_after:返回 成功// 版本号已变,说明写操作在读取过程中发生,重试让出CPU(thrd_yield)继续循环

头文件.h

#ifndef_PING_PONG_BUFC_H_#define_PING_PONG_BUFC_H_#include<stdint.h>#include<stdbool.h>#include<stdatomic.h>#include<threads.h>#ifdef__cplusplusextern"C"{#endiftypedefstructPING_PONG_TPING_PONG;/** * @brief 初始化乒乓缓冲区 * * @param handle 指向乒乓缓冲区结构体的指针 * @param data_size 单个数据元素的大小(字节数) * @return int32_t 成功返回0,失败返回-1 */int32_tpingpong_init(PING_PONG**handle,size_tdata_size);/** * @brief 销毁乒乓缓冲区并释放内存 * * @param ppb 指向乒乓缓冲区结构体的指针 */voidpingpong_destroy(PING_PONG*ppb);/** * @brief 向缓冲区写入数据(仅限单写者使用) * * @param ppb 指向乒乓缓冲区结构体的指针 * @param data 指向要写入数据的指针 * @return int32_t 成功返回0,失败返回-1 */int32_tpingpong_write(PING_PONG*ppb,constvoid*data);/** * @brief 向缓冲区读取数据 * * @param ppb 指向乒乓缓冲区结构体的指针 * @param data 指向要读取数据的指针 * @return int32_t 成功返回0,失败返回-1 */int32_tpingpong_read(PING_PONG*ppb,void*data);/** * @brief 检查缓冲区是否为空(尚未执行过写入操作) * * @param ppb 指向乒乓缓冲区结构体的指针 * @return bool 为空返回true,否则返回false */boolpingpong_empty(constPING_PONG*ppb);#ifdef__cplusplus}#endif#endif// _PING_PONG_BUFC_H_

源文件.c

#include<stdlib.h>#include<string.h>#include<stdatomic.h>#include<threads.h>#include"pingpong_bufc.h"/** * @brief 乒乓缓冲区结构体(隐藏实现) * * 提供双缓冲机制,一个缓冲区用于写入,另一个用于读取,两者角色可以互换。 * 版本号计数器确保读者看到一致的数据。 */structPING_PONG_T{void*buffers[2];// 双缓冲区指针atomic_ullong version;// 版本号,用于同步size_tdata_size;// 单个数据元素的大小(字节数)};int32_tpingpong_init(PING_PONG**handle_ptr,size_tdata_size){if(data_size==0||handle_ptr==NULL){return-1;}PING_PONG*ppb=(PING_PONG*)malloc(sizeof(PING_PONG));if(ppb==NULL){return-1;}// 分配两个缓冲区内存ppb->buffers[0]=malloc(data_size);ppb->buffers[1]=malloc(data_size);if(ppb->buffers[0]==NULL||ppb->buffers[1]==NULL){if(ppb->buffers[0]!=NULL)free(ppb->buffers[0]);if(ppb->buffers[1]!=NULL)free(ppb->buffers[1]);free(ppb);return-1;}// 正确初始化缓冲区内存memset(ppb->buffers[0],0,data_size);memset(ppb->buffers[1],0,data_size);ppb->data_size=data_size;// 初始化版本号为1,确保第一个可读的buffer索引为0atomic_init(&ppb->version,1);*handle_ptr=ppb;// 修改调用者的指针return0;}voidpingpong_destroy(PING_PONG*ppb){if(ppb==NULL){return;}free(ppb->buffers[0]);free(ppb->buffers[1]);ppb->buffers[0]=NULL;ppb->buffers[1]=NULL;ppb->data_size=0;atomic_store(&ppb->version,0);free(ppb);}int32_tpingpong_write(PING_PONG*ppb,constvoid*data){if(ppb==NULL||data==NULL){return-1;}// 获取当前版本号,计算写入的缓冲区索引unsignedlonglongcurrent_version=atomic_load_explicit(&ppb->version,memory_order_acquire);size_twrite_idx=(size_t)(current_version%2);// 写入数据memcpy(ppb->buffers[write_idx],data,ppb->data_size);// 增加版本号,发布写入完成// 使用memory_order_release确保写入对其他线程可见atomic_fetch_add_explicit(&ppb->version,1,memory_order_release);return0;}int32_tpingpong_read(PING_PONG*ppb,void*data){if(ppb==NULL||data==NULL){return-1;}unsignedlonglongversion_before,version_after;do{// 读取当前版本号version_before=atomic_load_explicit(&ppb->version,memory_order_acquire);// 检查是否有数据可读if(version_before<=1){return-1;// 无数据可读}// 计算读缓冲区索引并复制数据size_tread_idx=(size_t)((version_before-1)%2);memcpy(data,ppb->buffers[read_idx],ppb->data_size);// 再次读取版本号,检查在复制过程中是否有新的写入version_after=atomic_load_explicit(&ppb->version,memory_order_acquire);// 如果版本号未变,说明读取的数据是一致的if(version_before==version_after){return0;// 读取成功}// 版本号已变,说明读取过程中有新的写入,重试// 让出CPU,避免忙等待消耗太多CPU资源thrd_yield();}while(1);return0;}// size_t pingpong_get_read_index(const PING_PONG* ppb)// {// if (ppb == NULL)// {// return 0;// }// unsigned long long version = atomic_load_explicit(&ppb->version, memory_order_acquire);// // 可读的缓冲区是版本号减1然后取模2// return (size_t)((version - 1) % 2);// }// size_t pingpong_get_write_index(const PING_PONG* ppb)// {// if (ppb == NULL)// {// return 0;// }// unsigned long long version = atomic_load_explicit(&ppb->version, memory_order_acquire);// // 当前正在写的缓冲区是版本号取模2// return (size_t)(version % 2);// }boolpingpong_empty(constPING_PONG*ppb){if(ppb==NULL){returntrue;}returnatomic_load_explicit(&ppb->version,memory_order_acquire)<=1;}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询