C++生产者-消费者无锁缓冲区的简单实现

文章目录

      • 1. 引言
      • 2. SPSC 环形缓冲区设计思想
      • 3. 缓冲区类定义
      • 4. 追加数据
      • 5. 读取数据
      • 6. 完整代码
      • 7. 基准测试
        • 7.1. 测试代码
      • 8. 执行结果

1. 引言

本文将介绍如何实现无锁字节缓冲区(LockFreeBytesBuffer),并通过Google Benchmark对其性能进行测试。该缓冲区设计为单生产者单消费者(SPSC)模型,以简化线程安全问题。

2. SPSC 环形缓冲区设计思想

在环形缓冲区的上下文中,实现零拷贝的方法之一是通过直接操作缓冲区的指针,而不是复制数据。这种方式需要修改接口,使得消费者直接访问缓冲区的数据,而不是通过中间变量进行复制。

在设计LockFreeBytesBuffer时遵循了以下原则:

  1. 线程安全:使用原子操作确保数据一致性。
  2. 内存效率:使用固定大小的缓冲区,避免动态内存分配。

LockFreeBytesBuffer使用一个固定大小的环形缓冲区来存储数据,并通过原子操作管理读写索引。生产者线程将数据追加到缓冲区,消费者线程从缓冲区读取数据。

为了确保单生产者单消费者模型下的线程安全,我们需要确保:

  1. 生产者和消费者线程不会同时写入或读取同一块内存。
  2. 使用原子操作来更新索引,以确保读写操作的正确性和可见性。

3. 缓冲区类定义

下面是LockFreeBytesBuffer类的定义:

class LockFreeBytesBuffer {public:static const std::size_t kBufferSize = 1024U;  // 缓冲区大小LockFreeBytesBuffer() noexcept : reader_index_(0U), writer_index_(0U) {std::memset(buffer_, 0, kBufferSize);}bool Append(const char* data, std::size_t length) noexcept;std::size_t BeginRead(const char** target) noexcept;void EndRead(std::size_t length) noexcept;private:char buffer_[kBufferSize];std::atomic<std::size_t> reader_index_;std::atomic<std::size_t> writer_index_;
};

4. 追加数据

生产者线程调用Append方法将数据写入缓冲区。该方法首先检查缓冲区是否有足够的空间存放新数据。如果有足够的空间,它将数据写入缓冲区,并更新写索引。

bool LockFreeBytesBuffer::Append(const char* data, std::size_t length) noexcept {const std::size_t current_write_index = writer_index_.load(std::memory_order_relaxed);const std::size_t current_read_index = reader_index_.load(std::memory_order_acquire);const std::size_t free_space = (current_read_index + kBufferSize - current_write_index - 1U) % kBufferSize;if (length > free_space) {return false;  // 缓冲区满}const std::size_t pos = current_write_index % kBufferSize;const std::size_t first_part = std::min(length, kBufferSize - pos);std::memcpy(&buffer_[pos], data, first_part);std::memcpy(&buffer_[0], data + first_part, length - first_part);writer_index_.store(current_write_index + length, std::memory_order_release);return true;
}

5. 读取数据

消费者线程调用BeginRead方法获取缓冲区中可用的数据。该方法返回一个指向缓冲区中数据的指针,并更新读索引。消费者处理完数据后,调用EndRead方法标记数据已读。

std::size_t LockFreeBytesBuffer::BeginRead(const char** target) noexcept {const std::size_t current_read_index = reader_index_.load(std::memory_order_relaxed);const std::size_t current_write_index = writer_index_.load(std::memory_order_acquire);const std::size_t available_data = (current_write_index - current_read_index) % kBufferSize;if (available_data == 0U) {return 0U;  // 缓冲区空}const std::size_t pos = current_read_index % kBufferSize;*target = &buffer_[pos];return std::min(available_data, kBufferSize - pos);  // 返回第一段数据的大小
}void LockFreeBytesBuffer::EndRead(std::size_t length) noexcept {const std::size_t current_read_index = reader_index_.load(std::memory_order_relaxed);reader_index_.store(current_read_index + length, std::memory_order_release);
}

6. 完整代码

以下是完整的LockFreeBytesBuffer实现:

#ifndef EMBEDDED_BYTE_BUFFER_H_
#define EMBEDDED_BYTE_BUFFER_H_#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstring>class LockFreeBytesBuffer {public:static const std::size_t kBufferSize = 1024U;  // 缓冲区大小LockFreeBytesBuffer() noexcept : reader_index_(0U), writer_index_(0U) {std::memset(buffer_, 0, kBufferSize);}// 将数据追加到缓冲区bool Append(const char* data, std::size_t length) noexcept;// 获取指向缓冲区的读指针std::size_t BeginRead(const char** target) noexcept;// 标记数据已读void EndRead(std::size_t length) noexcept;private:char buffer_[kBufferSize];std::atomic<std::size_t> reader_index_;std::atomic<std::size_t> writer_index_;
};bool LockFreeBytesBuffer::Append(const char* data, std::size_t length) noexcept {const std::size_t current_write_index = writer_index_.load(std::memory_order_relaxed);const std::size_t current_read_index = reader_index_.load(std::memory_order_acquire);const std::size_t free_space = (current_read_index + kBufferSize - current_write_index - 1U) % kBufferSize;if (length > free_space) {return false;  // 缓冲区满}const std::size_t pos = current_write_index % kBufferSize;const std::size_t first_part = std::min(length, kBufferSize - pos);std::memcpy(&buffer_[pos], data, first_part);std::memcpy(&buffer_[0], data + first_part, length - first_part);writer_index_.store(current_write_index + length, std::memory_order_release);return true;
}std::size_t LockFreeBytesBuffer::BeginRead(const char** target) noexcept {const std::size_t current_read_index = reader_index_.load(std::memory_order_relaxed);const std::size_t current_write_index = writer_index_.load(std::memory_order_acquire);const std::size_t available_data = (current_write_index - current_read_index) % kBufferSize;if (available_data == 0U) {return 0U;  // 缓冲区空}const std::size_t pos = current_read_index % kBufferSize;*target = &buffer_[pos];return std::min(available_data, kBufferSize - pos);  // 返回第一段数据的大小
}void LockFreeBytesBuffer::EndRead(std::size_t length) noexcept {const std::size_t current_read_index = reader_index_.load(std::memory_order_relaxed);reader_index_.store(current_read_index + length, std::memory_order_release);
}#endif  // EMBEDDED_BYTE_BUFFER_H_

7. 基准测试

为了评估LockFreeBytesBuffer的性能,我们使用Google Benchmark库编写了基准测试程序。该程序创建生产者和消费者线程,并在基准测试循环中进行数据读写操作。

7.1. 测试代码
#include <benchmark/benchmark.h>#include <cstring>
#include <thread>#include "LockFreeBytesBuffer.h"void Producer(LockFreeBytesBuffer* buffer, bool* stop) noexcept {const char data[] = "test data";while (!*stop) {buffer->Append(data, std::strlen(data));// 模拟生产数据的延迟std::this_thread::sleep_for(std::chrono::microseconds(10));}
}void Consumer(LockFreeBytesBuffer* buffer, bool* stop) noexcept {const char* data = nullptr;while (!*stop) {const std::size_t length = buffer->BeginRead(&data);if (length > 0U) {// 处理数据buffer->EndRead(length);// 模拟处理数据的延迟std::this_thread::sleep_for(std::chrono::microseconds(10));}}
}// Benchmark函数
static void BM_Buffer(benchmark::State& state) {LockFreeBytesBuffer buffer;bool stop = false;std::thread producer_thread(Producer, &buffer, &stop);std::thread consumer_thread(Consumer, &buffer, &stop);for (auto _ : state) {// 在这里进行基准测试const char data[] = "benchmark data";buffer.Append(data, std::strlen(data));const char* target = nullptr;const std::size_t length = buffer.BeginRead(&target);buffer.EndRead(length);}stop = true;  // 停止生产者和消费者线程producer_thread.join();consumer_thread.join();
}// 注册benchmark
BENCHMARK(BM_Buffer)->Threads(1);int main(int argc, char** argv) {::benchmark::Initialize(&argc, argv);::benchmark::RunSpecifiedBenchmarks();return 0;
}

8. 执行结果

安装Google Benchmark:如果还没有安装Google Benchmark,可以按照Google Benchmark GitHub上的说明进行安装。

编译: g++ -O2 buffer_benchmark.cpp -lbenchmark -lpthread -o buffer_benchmark

运行基准测试程序:

$ ./buffer_benchmark 
2024-06-23T14:47:30+00:00
Running ./buffer_benchmark
Run on (2 X 2096.07 MHz CPU s)
CPU Caches:L1 Data 32 KiB (x2)L1 Instruction 64 KiB (x2)L2 Unified 512 KiB (x2)L3 Unified 4096 KiB (x2)
Load Average: 0.00, 0.00, 0.00
***WARNING*** Library was built as DEBUG. Timings may be affected.
--------------------------------------------------------------
Benchmark                    Time             CPU   Iterations
--------------------------------------------------------------
BM_Buffer/threads:1       10.8 ns         10.8 ns     64004334

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/36361.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

强化学习盾牌:scikit-learn模型正则化全面指南

&#x1f6e1;️ 强化学习盾牌&#xff1a;scikit-learn模型正则化全面指南 &#x1f6e1;️ 在机器学习中&#xff0c;模型正则化是一项关键技术&#xff0c;用于防止过拟合&#xff0c;提高模型的泛化能力。scikit-learn&#xff0c;作为Python中一个功能强大的机器学习库&a…

Flutter笔记(一)- 安装和配置Flutter

一、下载Flutter 访问网址&#xff1a;https://docs.flutter.dev/get-started/install?hlzh-cn 根据电脑所使用的操作系统的平台进行选择。笔者电脑的操作系统为Windows&#xff0c;因此选择如图1-1的Windows图片&#xff1a; 图1-1 Flutter网站&#xff08;一&#xff09; …

国行版苹果Vision Pro即将发售 高昂定价吓退普通消费者?

2024年2月2日&#xff0c;苹果第一代空间计算设备Vision Pro在美国上市。6月28日&#xff0c;国行版苹果Vision Pro也将正式发售&#xff0c;别为256GB版29999元、512GB版31499元、1TB版32999元。不过从此前Vision Pro预售情况来看&#xff0c;Vision Pro的“杀手锏”在“价格”…

【应届应知应会】Linux常用指令

SueWakeup 个人主页&#xff1a;SueWakeup 系列专栏&#xff1a;学习技术栈 个性签名&#xff1a;保留赤子之心也许是种幸运吧 本文封面由 凯楠&#x1f4f8;友情提供 目录 文件与目录管理 目录操作命令&#xff1a; ls [选项] [目录或文件] mkdir 文件操作命令&#xf…

多媒体本地化的五个步骤

多媒体本地化为试图在多个全球目的地建立市场的企业提供了许多好处。 由于多媒体并不局限于一个内容标签&#xff0c;因此您需要注意一些元素。 这个过程通常从翻译开始&#xff0c;但因为我们处理的是视频和音频&#xff0c;所以从一开始就要处理一个附加层。让我们从这里开…

SqlServer 2008远程过程调用失败,错误代码[0x800706be]

1、解决方式&#xff1a; 将SQL 2008 R2升级到SP1或SP2 下载地址&#xff1a;SQL Server 2008 R2 Service Pack 2下载地址

非最大值抑制(NMS)函数

非最大值抑制&#xff08;NMS&#xff09;函数 flyfish 非最大值抑制&#xff08;Non-Maximum Suppression, NMS&#xff09;是计算机视觉中常用的一种后处理技术&#xff0c;主要用于目标检测任务。其作用是从一组可能存在大量重叠的候选边界框中&#xff0c;筛选出最具代表…

初学51单片机之长短键应用定时炸弹及扩展应用

51单片机RAM区域划分 51单片机的RAM分为两个部分&#xff0c;一块是片内RAM&#xff0c;一块是片外RAM。 data&#xff1a; 片内RAM从 0x00 ~0x7F 寻址范围&#xff08;0-127&#xff09; 容量共128B idata: 片外RAM从 0x00~0xFF 寻址范围(0-255) 容量共256B pdata&am…

即时和及时的微妙区别

即时与及时在汉语中的确存在一些微妙的区别&#xff0c;以下是对这两个词的分析&#xff0c;并通过例子进行说明&#xff1a; 一、词义与词性 即时&#xff1a;具有立刻、马上的意思&#xff0c;强调在某个时间段立即出现、产生或做出&#xff0c;侧重于和当时实际情况同步。…

Leetcode-Java 无重复字符的最长子串

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串的长度。 示例 1:输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3。 示例 2:输入: s "bbbbb" 输出: 1 解释: 因为无重复…

无缝集成的艺术:iCloud与Apple TV的协同之旅

无缝集成的艺术&#xff1a;iCloud与Apple TV的协同之旅 在苹果构建的生态系统中&#xff0c;iCloud和Apple TV是两个重要的组成部分&#xff0c;它们共同为用户提供了一种无缝的娱乐体验。iCloud作为苹果的云服务&#xff0c;不仅存储用户的照片、视频、文档等数据&#xff0…

定位问题6.27 petal数据接口问题

petal接口响应结果 响应结果为空的数据&#xff0c;而我们需要的是正确的响应结果。 排查问题 确认接口是否正确 以下是爬虫的配置文件内容&#xff0c;我查看了PETAL_URL的接口&#xff0c;并询问接口开发人员&#xff0c;得知接口地址并未改变 确认接口请求体是否正确 我使…

记一次对ouija渗透测试c语言逆向学习

概要 初始知识 web应用枚举 二进制逆向 文件枚举 堆栈溢出 学到知识 hash长度攻击 任意文件读取 二进制逆向分析 信息收集 端口扫描 nmap --min-rate 1000 -p- 10.129.30.104 发现22&#xff0c;80&#xff0c;3000端口 网站探测 目录枚举 feroxbuster -u http://10.1…

“数字政协”平台如何提高政协工作效率?正宇软件助力建设!

随着信息技术的飞速发展&#xff0c;数字化已成为推动各行各业转型升级的重要力量。在政协工作中&#xff0c;数字政协平台的建设与运用&#xff0c;正成为提高政协工作效率、促进民主协商的重要手段。本文将从数字政协平台的功能特点、优势分析以及实践应用等方面&#xff0c;…

何用Vue3和Plotly.js打造交互式3D图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 利用 Plotly.js 创建交互式动画图表 应用场景 本代码适用于需要创建交互式动画图表的数据可视化项目。例如&#xff0c;可以用来展示时间序列数据或比较不同函数的行为。 基本功能 该代码使用 Plotly.js 库…

架构师篇-5、架构语言-ArchiMate

内容摘要&#xff1a; TOGAF内容元模型TOGAF架构语言ArchiMate3ArchiMate实践案例分享 TOGAF内容框架【核心内容元模型】 作为一个通用且开放式的标准&#xff0c;TOGAF需要采用一种非常灵活的方式来对其内容元模型进行定义&#xff0c;从而使得不同的企业可以根据自身需要对…

stp、rstp、mstp学习

文章目录 STP&#xff08;生成树协议&#xff09;RSTP&#xff08;快速生成树协议&#xff09;MSTP&#xff08;多生成树协议&#xff09;三者区别 STP&#xff08;生成树协议&#xff0c;Spanning Tree Protocol&#xff09;、RSTP&#xff08;快速生成树协议&#xff0c;Rapi…

头歌——机器学习——决策树案例

第1关&#xff1a;基于决策树模型的应用案例 任务描述 本关任务&#xff1a;使用决策树算法完成成人收入预测。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.数据特征处理&#xff0c;2.使用决策树算法完成成人收入预测。 数据处理及特征工程 本次任务…

2024最新特种设备(锅炉作业)题库分享。

1.锅炉蒸发量大小是由(  )决定的。 A.压力的高低 B.受压元件多少 C.受热面积大小 答案:C 2.哪项不是自然循环的故障?&#xff08; &#xff09; A.停滞 B.倒流 C.下降管带汽 D.上升管带汽 答案:D 3.水冷壁被现代大型锅炉广泛采用的是(  )。 A.光管水冷壁 B.膜…

【C++】继承(详解)

前言&#xff1a;今天我们正式的步入C进阶内容的学习了&#xff0c;当然了既然是进阶意味着学习难度的不断提升&#xff0c;各位一起努力呐。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:高质量&#xff23;学习 &#x1f448; &#…