环形缓冲区优点及实现

环形缓冲区优点及实现

目录

  • 环形缓冲区优点及实现
    • 一、环形缓冲区概念
    • 二、环形缓冲区优点
      • 1、一个有缺陷的数据读写示例
      • 2、使用环形缓冲区解决数据读写缺陷
    • 三、环形缓冲区实现代码

一、环形缓冲区概念

环形缓冲区是一种特殊的缓冲区,其读指针和写指针都指向同一个缓冲区,通过移动指针来实现数据的读取和写入。环形缓冲区的示意图可以如下:

  1. 初始状态:环形缓冲区的读指针和写指针都指向第一个缓冲区处。
  2. 添加数据:向环形缓冲区中添加一个数据后,写指针移动到数据块2的位置,而读指针没有移动。
  3. 读取和添加:环形缓冲区进行了读取和添加后的状态,可以看到环形缓冲区中已经添加了两个数据,已经读取了一个数据。

环形缓冲区所有的push和pop操作都是在一个固定的存储空间内进行,相比队列方式,少掉了对于缓冲区元素所用存储空间的分配、释放。这是环形缓冲区的一个主要优势。

在这里插入图片描述

二、环形缓冲区优点

1、一个有缺陷的数据读写示例

假设在多任务系统中,对数据有1个写任务和1个读任务:

/* 定义一个位置结构体,包含x位置和y位置 */
typedef struct
{
    volatile unsigned int x;           /* x位置 */
    volatile unsigned int y;           /* y位置 */
} position;

position sensorPosition;   //定义sensor位置结构体

/* 定义一个写位置的任务 */
void TaskWritePosition(void *pvParameters)
{
    unsigned int x0 = 0;   //定义一个x初始位置
    unsigned int y0 = 0;   //定义一个y初始位置

    while(1)
    {
        getSensorPosition(&x0, &y0);   //获取Sensor的x位置和y位置

        sensorPosition.x =  x0;   //更新sensorPosition结构体中x位置
        sensorPosition.y =  y0;   //更新sensorPosition结构体中y位置
    }
}
    

/* 定义一个读位置的任务 */
void TaskReadPosition(void *pvParameters)
{
    unsigned int x1 = 0;   //定义一个x位置存放变量
    unsigned int y1 = 0;   //定义一个y位置存放变量

    while(1)
    {
        x1 = sensorPosition.x;   //获取sensorPosition结构体中x位置
        y1 = sensorPosition.y;   //获取sensorPosition结构体中y位置
    }
}

此示例中存在缺陷:

  1. TaskReadPosition获取到x位置并存放到x1变量,此时任务切换到TaskWritePosition
  2. TaskWritePosition中更新x位置和y位置,再切换到TaskReadPosition
  3. TaskReadPosition继续获取新的y位置并存放到y1变量
  4. 此时x1变量存放的是sensor上次的x位置,y1变量存放的是sensor最新的y位置,导致x位置与y位置匹配

2、使用环形缓冲区解决数据读写缺陷

注:仅限与2个任务之间的数据传输,若大于2个任务还是会存在线程安全问题!

使用环形缓冲区解决此问题:

/* 定义一个位置结构体,包含x位置和y位置 */
typedef struct
{
    volatile unsigned int x;           /* x位置 */
    volatile unsigned int y;           /* y位置 */
} position;

//定义一个环形缓冲区,用来存放sensor的x与y位置
typedef struct
{
    volatile unsigned int pW;           /* 写地址 */
    volatile unsigned int pR;           /* 读地址 */
    position positionBuffer[100];  /* 缓冲区空间 */
} position_ring_buffer;

position_ring_buffer positionRingBuffer;   //定义一个sensor位置的环形缓冲区,用来存放sensor的x与y位置

/* 定义一个写位置的任务 */
void TaskWritePosition(void *pvParameters)
{
    unsigned int x0 = 0;   //定义一个x初始位置
    unsigned int y0 = 0;   //定义一个y初始位置
	
    while(1)
    {
        getSensorPosition(&x0, &y0);   //获取Sensor的x位置和y位置
		
        /* 获取环形缓冲区写指针的下一个位置 */
        int i = (positionRingBuffer->pW + 1) % BUFFER_SIZE;
        if(i != positionRingBuffer->pR)    // 环形缓冲区没有写满
        {
            positionRingBuffer.positionBuffer[positionRingBuffer->pW].x = x0;   //更新sensorPosition结构体中x位置
            positionRingBuffer.positionBuffer[positionRingBuffer->pW].y = y0;   //更新sensorPosition结构体中y位置   

            positionRingBuffer->pW = i;   //将环形缓冲区的写指针更新为下一个写位置
        }
    }
}
    
/* 定义一个读位置的任务 */
int TaskReadPosition(void *pvParameters)
{
    unsigned int x1 = 0;   //定义一个x位置存放变量
    unsigned int y1 = 0;   //定义一个y位置存放变量

    while(1)
    {
        getSensorPosition(&x0, &y0);   //获取Sensor的x位置和y位置

    /* 如果环形缓冲区当前的读指针与写指针位置相同表示当前环形缓冲区为空 */
    if(positionRingBuffer->pR == positionRingBuffer->pW)
    {
        return -1;
    }
    else
    {
        /* 将当前环形缓冲区读指针位置的数据传给字符c的地址 */
        x1 = positionRingBuffer.positionBuffer[positionRingBuffer->pW].x;   //获取新x位置
        y1 = positionRingBuffer.positionBuffer[positionRingBuffer->pW].y;   //获取新y位置
        /* 将环形缓冲区读指针的位置更新为下一个读位置 */
        positionRingBuffer->pR = (positionRingBuffer->pR + 1) % BUFFER_SIZE;
        return 0;
    }
}

使用环形缓冲区时存放sensor的x和y位置时,读与写互不干扰:读数据根据读指针读取,只有TaskReadPosition任务能够修改读指针位置;写数据使用写指针写数据,只有TaskWritePosition任务能够修改写指针的位置。读sensor和写sensor互相不干扰。

三、环形缓冲区实现代码

参考韦东山老师代码,ring_buffer.c和ring_buffer.h:

ring_buffer.h

/*  Copyright (s) 2019 深圳百问网科技有限公司
 *  All rights reserved
 * 
 * 文件名称:ring_buffer.h
 * 摘要:
 *  
 * 修改历史     版本号        Author       修改内容
 *--------------------------------------------------
 * 2021.8.21      v01         百问科技      创建文件
 *--------------------------------------------------
*/
#ifndef __RING_BUFFER_H
#define __RING_BUFFER_H

#include "stm32f1xx_hal.h"

#define BUFFER_SIZE 1024        /* 环形缓冲区的大小 */
typedef struct
{
    volatile unsigned int pW;           /* 写地址 */
    volatile unsigned int pR;           /* 读地址 */
    unsigned char buffer[BUFFER_SIZE];  /* 缓冲区空间 */
} ring_buffer;

/*
 *  函数名:void ring_buffer_init(ring_buffer *dst_buf)
 *  输入参数:dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:无
 *  函数作用:初始化缓冲区
*/
extern void ring_buffer_init(ring_buffer *dst_buf);

/*
 *  函数名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
 *  输入参数:c --> 要写入的数据
 *            dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:无
 *  函数作用:向目标缓冲区写入一个字节的数据,如果缓冲区满了就丢掉此数据
*/
extern void ring_buffer_write(unsigned char c, ring_buffer *dst_buf);

/*
 *  函数名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
 *  输入参数:c --> 指向将读到的数据保存到内存中的地址
 *            dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:读到数据返回0,否则返回-1
 *  函数作用:从目标缓冲区读取一个字节的数据,如果缓冲区空了返回-1表明读取失败
*/
extern int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf);

#endif /* __RING_BUFFER_H */

ring_buffer.c

/*  Copyright (s) 2019 深圳百问网科技有限公司
 *  All rights reserved
 * 
 * 文件名称:ring_buffer.c
 * 摘要:
 *  
 * 修改历史     版本号        Author       修改内容
 *--------------------------------------------------
 * 2021.8.21      v01         百问科技      创建文件
 *--------------------------------------------------
*/

#include "ring_buffer.h"


/*
 *  函数名:void ring_buffer_init(ring_buffer *dst_buf)
 *  输入参数:dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:无
 *  函数作用:初始化缓冲区
*/
void ring_buffer_init(ring_buffer *dst_buf)
{
    /* 唤醒缓冲区初始化,将读写指针设置为0 */
    dst_buf->pW = 0;
    dst_buf->pR = 0;
}

/*
 *  函数名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
 *  输入参数:c --> 要写入的数据
 *            dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:无
 *  函数作用:向目标缓冲区写入一个字节的数据,如果缓冲区满了就丢掉此数据
*/
void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
{
    /* 获取环形缓冲区写指针的下一个位置 */
    int i = (dst_buf->pW + 1) % BUFFER_SIZE;
    /* 
    如果环形缓冲区写指针的下一个位置和读指针不相等代表环形缓冲区未写满,若写满则数据直 
   接丢弃 */
    if(i != dst_buf->pR)    // 环形缓冲区没有写满
    {
        /* 将字符C写到唤醒缓冲区写指针的位置 */
        dst_buf->buffer[dst_buf->pW] = c;
        /* 将环形缓冲区的写指针更新为下一个写位置 */
        dst_buf->pW = i;
    }
}

/*
 *  函数名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
 *  输入参数:c --> 指向将读到的数据保存到内存中的地址
 *            dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:读到数据返回0,否则返回-1
 *  函数作用:从目标缓冲区读取一个字节的数据,如果缓冲区空了返回-1表明读取失败
*/
int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
{
    /* 如果环形缓冲区当前的读指针与写指针位置相同表示当前环形缓冲区为空 */
    if(dst_buf->pR == dst_buf->pW)
    {
        return -1;
    }
    else
    {
        /* 将当前环形缓冲区读指针位置的数据传给字符c的地址 */
        *c = dst_buf->buffer[dst_buf->pR];
        /* 将环形缓冲区读指针的位置更新为下一个读位置 */
        dst_buf->pR = (dst_buf->pR + 1) % BUFFER_SIZE;
        return 0;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL之视图索引执行计划

目录 一.视图 二.执行计划 2.1.什么是执行计划 2.2.执行计划的作用 三.使用外连接、内连接和子查询进行举例 四.思维导图 好啦今天就到这里了哦!!!希望能帮到你哦!!! 一.视图 含义 :在数…

【BIAI】lecture 3 - GD BP CNN Hands-on

GD & BP & CNN & Hands-on 专业术语 gradient descent (GD) 梯度下降 back propagation (BP) 向传播 Convolutional Neural Network (CNN) 卷积神经网络 forward propagation 前向传播 biologically symmetry 生物对称性 synaptic 突触 axon 轴突 课程大纲 The go…

webgl调试之排查内存泄漏

内存泄漏自然而然是要看内存是不是涨了 然后我们如何确认泄露了呢,我们需要把代码梳理清楚,知道哪个时机,在delete,在create,那么这个时候,按道理,delete了n个对象,create了N个对象&…

Redis 键中冒号的用途是什么?可以使匹配查询更快吗?

Redis 键中冒号的用途是什么在Redis中,冒号(:)用作键的分隔符,它的主要作用是创建层次结构和命名空间。通过在键中使用冒号,可以将键分为多个部分,从而更好地组织和管理数据。 以下是冒号在Redis键中的用途…

2024苹果Mac电脑免费文件数据恢复软件EasyRecovery

EasyRecovery是一个操作安全、价格便宜、用户自主操作的非破坏性的只读应用程序,它不会往源驱上写任何东西,也不会对源驱做任何改变!EasyRecovery是一个操作安全、价格便宜、用户自主操作的非破坏性的只读应用程序,它不会往源驱上…

MySQL第四战:视图以及常见面试题(上)

目录 目录: 一.视图 1.介绍什么是视图 2.视图的语法 语法讲解 实例操作 二.MySQL面试题 1.SQL脚本 2.面试题实战 三.思维导图 目录: 随着数字化时代的飞速发展,数据库技术,特别是MySQL,已经成为IT领域中不可…

短网址的新玩法,短到只剩域名

短网址大家应该都不陌生了,一句话就可以解释清楚,把一串很长的网址缩短到只有几个字符依然可以正常访问,缩短之后会更加简洁美观。 那大家见过的短网址一般长啥样呢,比如t.cn/xxxxx、dwz.cn/xxxxx、c1ns.cn/xxxxx。这些短网址都有…

初始MySQL

一、数据库 1.什么是数据库 数据库( Database,简称DB ):长期存放在计算机内,有组织、可共享的大量数据的集合,是一个数据“仓库” 2.数据库的作用 可以结构化存储大量的数据,方便检索和访问保持数据信息…

JVM工作原理与实战(八):类加载器的分类

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、类加载器介绍 二、类加载器的分类 1.Java代码实现的类加载器 2.Java虚拟机底层源码实现的类加载器 3.默认的类加载器层次(JDK8及之前的版本) 总结 前言…

迅为RK3588开发板使用 FFMpeg 进行推流

Debian/Ubuntu 系统使用以下命令安装 FFMpeg ,如下图所示: apt-get install ffmpeg 使用 ifconfig 查看开发板 ip 为 192.168.1.245 如下图所示: 使用 FFMpeg 推流一个 mp4 视频进行测试,作者将测试视频 test.mp4 放在了根目录下…

学习笔记——C++运算符之赋值运算符

上次我们说到C的运算符共有四种&#xff0c;分别是算术运算符&#xff0c;赋值运算符&#xff0c;比较运算符和逻辑运算符 &#xff0c;下面介绍赋值运算符&#xff0c;赋值运算符主要的种类及作用如下表所示。 #include<bits/stdc.h> using namespace std; int main(){…

求两个数之间的最小公约数

目录 前言 方法&#xff1a;求两个数之间的最小公约数 1.欧几里得算法 2.枚举法 3.公共因子积 4.更相减损术 5.Stein算法 解题&#xff1a;在链表中插入最大公约数 总结 前言 今天刷每日一题&#xff1a;2807. 在链表中插入最大公约数 - 力扣&#xff08;LeetCode&#xff09;…

jenkins安装报错:No such plugin: cloudbees-folder

jenkins安装报错&#xff1a;No such plugin: cloudbees-folder 原因是缺少cloudbees-folder.hpi插件 解决&#xff1a; 一&#xff0c;重新启动 http://xxx:8800/restart 二&#xff0c;跳到重启界面时&#xff0c;点击系统设置 三&#xff0c;找到安装插件&#xff0c;然…

Python基础-07(for循环、range()函数)

文章目录 前言一、for循环1.for循环结构2.参数 end&#xff08;使其输出时变为横向&#xff09; 二、range()函数1.range(常数)2.range(起始值&#xff0c;结束值)3.range(起始值&#xff0c;结束值&#xff0c;步长)4.例子 总结 前言 此章介绍循环结构中最常用的循环&#xf…

Go (一) 基础部分5 -- 单元测试,协程(goroutine),管道(channel)

一、单元测试 Go自带一个轻量级的"测试框架testing"和自带的"go test"命令来实现单元测试和性能测试。 1.确保每个函数时可运行&#xff0c;并且运行结果是正确的。 2.确保写出来的代码性能是好的。 3.单元测试能及时的发现程序设计或实现的逻辑错误&#…

mysql基础-数据操作之增删改

目录 1.新增数据 1.1单条数据新增 1.2多条数据新增 1.3查询数据新增 2.更新 2.1单值更新 2.2多值更新 2.3批量更新 2.3.1 批量-单条件更新 2.3.2批量-多条件更新 2.4 插入或更新 2.5 联表更新 3.删除 本次分享一下数据库的DML操作语言。 操作表的数据结构&#xf…

Spark回归分析与特征工程

回归分析是统计学和机器学习中的一个重要分支&#xff0c;用于建立因变量与自变量之间的关系模型。在大数据领域&#xff0c;Apache Spark为回归分析提供了强大的工具和库&#xff0c;以处理大规模数据集。本文将深入探讨如何使用Spark进行回归分析以及如何进行特征工程&#x…

使用qtquick调用python程序,pytorch

一. 内容简介 使用qtquick调用python程序 二. 软件环境 2.1vsCode 2.2Anaconda version: conda 22.9.0 2.3pytorch 安装pytorch(http://t.csdnimg.cn/GVP23) 2.4QT 5.14.1 新版QT6.4,&#xff0c;6.5在线安装经常失败&#xff0c;而5.9版本又无法编译64位程序&#xf…

第1章 初识JavaScript

学习目标 了解JavaScript基本概念&#xff0c;能够说出JavaScript的作用、由来、组成和特点 熟悉常见浏览器的特点&#xff0c;能够说出浏览器的组成以及作用 掌握下载和安装Visual Studio Code编辑器&#xff0c;能够独立完成编辑器的下载和安装 掌握JavaScript代码引入方式…

Windows电脑无法睡眠解决办法

原因 电脑无法休眠的原因&#xff0c;是打开离开模式策略后&#xff0c;windows内核会持续调用CPU资源&#xff0c;导致系统一直在运行而无法关闭。关闭后就好了。 解决步骤 修改注册表 操作步骤如下: 按winR&#xff0c;输入regedit&#xff0c;打开注册表编辑页面。输入如下…