rcu锁原理以及rcu example学习

news2024/11/16 8:46:15

rcu参考资料:
https://airekans.github.io/c/2016/05/10/dive-into-liburcu
https://lwn.net/Articles/262464/
https://cloud.tencent.com/developer/article/1684477
https://www.cnblogs.com/LoyenWang/p/12681494.html

userspace rcu:
https://github.com/urcu/userspace-rcu
内核rcu实现不太一样,但是原理类似

rcu的原理:

rcu会维护一个全局的数据结构rcu_gp

struct rcu_gp {
    unsigned long ctr;
    int32_t futex;
} __attribute__((aligned(CAA_CACHE_LINE_SIZE)));
extern struct rcu_gp rcu_gp;

他是通过ctr成员变量来实现一写多读操作

假设一个写线程,4个读线程
读线程ctr可能的值:
读线程1:rcu_register_thread,会获取ctr的值,假设此时为10
读线程2:rcu_register_thread,会获取ctr的值,假设此时为11(可能发生在synchronize_rcu之后,获取到了新的值)
读线程3:rcu_register_thread,会获取ctr的值,假设此时为10
读线程4:rcu_register_thread,会获取ctr的值,假设此时为10

rcu_quiescent_state:
1、如果将全局g_ctr的值等于读线程的ctr值,直接唤醒写线程(写线程等待的读线程数减一)
2、不相等的话直接把全局g_ctr的值赋值给读线程的ctr

/*
 * This is a helper function for _rcu_quiescent_state().
 * The first cmm_smp_mb() ensures memory accesses in the prior read-side
 * critical sections are not reordered with store to
 * URCU_TLS(urcu_qsbr_reader).ctr, and ensures that mutexes held within an
 * offline section that would happen to end with this
 * urcu_qsbr_quiescent_state() call are not reordered with
 * store to URCU_TLS(urcu_qsbr_reader).ctr.
 */
static inline void _urcu_qsbr_quiescent_state_update_and_wakeup(unsigned long gp_ctr)
{
	cmm_smp_mb();
	_CMM_STORE_SHARED(URCU_TLS(urcu_qsbr_reader).ctr, gp_ctr);
	cmm_smp_mb();	/* write URCU_TLS(urcu_qsbr_reader).ctr before read futex */
	urcu_qsbr_wake_up_gp();
	cmm_smp_mb();
}

/*
 * Inform RCU of a quiescent state.
 *
 * This function is less than 10 lines long.  The intent is that this
 * function meets the 10-line criterion for LGPL, allowing this function
 * to be invoked directly from non-LGPL code.
 *
 * We skip the memory barriers and gp store if our local ctr already
 * matches the global urcu_qsbr_gp.ctr value: this is OK because a prior
 * _rcu_quiescent_state() or _rcu_thread_online() already updated it
 * within our thread, so we have no quiescent state to report.
 */
static inline void _urcu_qsbr_quiescent_state(void)
{
	unsigned long gp_ctr;

	urcu_assert_debug(URCU_TLS(urcu_qsbr_reader).registered);
	if ((gp_ctr = CMM_LOAD_SHARED(urcu_qsbr_gp.ctr)) == URCU_TLS(urcu_qsbr_reader).ctr)
		return;
	_urcu_qsbr_quiescent_state_update_and_wakeup(gp_ctr);
}

写线程1:synchronize_rcu会将ctr的值加1,ctr的值为11,并且会等待所有读线程的值都为11(即所有读线程都执行完一次rcu_quiescent_state)才会执行下一个步骤

所以可以理解为临界区间在rcu_register_thread开始,到rcu_quiescent_state结束,写线程可以继续执行

rcu锁example:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <urcu-qsbr.h>

struct foo
{
    int a;
    int b;
    int c;
};

static struct foo *g_foo;

void g_foo_init()
{
    g_foo = (struct foo *)malloc(sizeof(struct foo));
    g_foo->a = 1;
    g_foo->b = 1;
    g_foo->c = 1;
}

void g_foo_release()
{
    free(g_foo);
    g_foo = NULL;
}

void do_something(struct foo *l_foo)
{  
    int i = 0;
    int j = 0;
    long sum = 0;
    for (i = 0; i < 10000; i++) {
        for (j = 0; j < 100000; j++) {
            // 加锁或者不加锁都ok,rcu_read_lock内部实现为NULL
            // rcu_read_lock();
            if (l_foo) {
                sum = sum + l_foo->a + l_foo->b + l_foo->c;
            }
            // rcu_read_unlock();
        }
    }
    printf("sum:%ld\n", sum);
}

void * reader_thread()
{
    rcu_register_thread();
    struct foo *l_foo = rcu_dereference(g_foo);
    if (l_foo) {
        do_something(l_foo);
    }
    rcu_quiescent_state();
	rcu_unregister_thread();
}

void * writer_thread()
{
    struct foo *old_foo = g_foo;
    struct foo *new_foo = (struct foo *)malloc(sizeof(struct foo));
    new_foo->a = 10;
    new_foo->b = 11;
    new_foo->c = 12;
    rcu_xchg_pointer(&g_foo, new_foo);
    synchronize_rcu();
    if (old_foo) {
        free(old_foo);
    }
}

/**
 * rcu,一写多读需要遵循的规则
 * 
 * 读线程:
 * 1、rcu_register_thread    将改线程加入rcu链表,synchronize_rcu判断的时候会用到
 * 2、rcu_unregister_thread  将改线程从rcu链表中移除
 * 3、rcu_quiescent_state    读线程主动通知写线程已经结束一批临界区
 * 4、rcu_dereference        读线程获取被保护的共享指针需要用该API
 * 5、rcu_read_lock/rcu_read_unlock 可选API,实际什么都没做
 * 
 * 写线程:
 * 1、rcu_xchg_pointer       写线程更新指针需要用到该API
 * 2、synchronize_rcu        等待Grace Period结束(所有读线程都已经调用过rcu_quiescent_state)
 */
int main()
{
    pthread_t reader1, reader2, reader3, reader4, writer;
    g_foo_init();

    pthread_create(&writer, NULL, writer_thread, NULL);
    pthread_create(&reader1, NULL, reader_thread, NULL);
    pthread_create(&reader2, NULL, reader_thread, NULL);
    pthread_create(&reader3, NULL, reader_thread, NULL);
    pthread_create(&reader4, NULL, reader_thread, NULL);

    pthread_join(writer, NULL);
    pthread_join(reader1, NULL);
    pthread_join(reader2, NULL);
    pthread_join(reader3, NULL);
    pthread_join(reader4, NULL);
    g_foo_release();
    return 0;
}

运行结果:
在这里插入图片描述
从上图也可以看出来,rcu锁并不保证每次读到的数据到底是旧数据还是新数据,但是会保证旧数据和新数据都能正常访问到

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

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

相关文章

PHP 过滤器

PHP 过滤器用于验证和过滤来自非安全来源的数据&#xff0c;比如用户的输入。什么是 PHP 过滤器&#xff1f;PHP 过滤器用于验证和过滤来自非安全来源的数据。测试、验证和过滤用户输入或自定义数据是任何 Web 应用程序的重要组成部分。PHP 的过滤器扩展的设计目的是使数据过滤…

目标检测、实例分割、旋转框样样精通!详解高性能检测算法 RTMDet

1. 简介 近几年来&#xff0c;目标检测模型&#xff0c;尤其是单阶段目标检测模型在工业场景中已经得到广泛应用。对于检测算法来说&#xff0c;模型的精度以及运行效率是实际使用时最受关注的指标。因此&#xff0c;我们对目前的单阶段目标检测器进行了全面的改进&#xff1a…

超参数、划分数据集、

目录1.超参数(hyperparameters)参数(Parameters)&#xff1a;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;...超参数&#xff1a;能够控制参数W,b的参数&#xff0c;是在开始学习之前设置的参数。比如&#xff1a;学习率、梯度下降循环的数量#iterations、隐…

【阶段三】Python机器学习23篇:机器学习项目实战:XGBoost分类模型

本篇的思维导图: XGBoost算法可以说是集成学习方法中的王牌算法。在著名的数据挖掘比赛平台Kaggle上,众多获胜者都使用了XGBoost算法,它在绝大多数回归问题和分类问题上的表现都十分不错。 XGBoost算法的核心思想 XGBoost算法在某种程度上可以说是GBDT算法…

蓝桥杯:超级质数

目录 题目链接 问题描述 答案提交 本题答案为&#xff1a;373。 思路&#xff1a; 模拟代码(Java)&#xff1a; 问题描述 如果一个质数 P 的每位数字都是质数, 而且每两个相邻的数字组成的两位 数是质数, 而且每三位相邻的数字组成的三位数是质数, 依次类推, 如果每相 邻…

【Node.js实战】一文带你开发博客项目之日志(文件读写、stream流、写日志)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

《码出高效:java开发手册》八 -单元测试

前言 这章内容主要是讲单测&#xff0c;单元就是指一个程序分的最小单位&#xff0c;一般是类或者方法&#xff0c;在面向对象编程里&#xff0c;一般就是认为方法是最小单位&#xff0c;单测是程序功能的基本保障&#xff0c;在软件上线前非常重要的一环 正文 单测的好处&a…

_Linux多线程-死锁Linux线程同步篇

文章目录1. 死锁死锁四个必要条件避免死锁避免死锁算法&#xff08;了解&#xff09;2. Linux线程同步线程同步出现的背景条件变量同步概念与竞态条件条件变量函数1. 初始化2. 销毁3. 等待条件满足4. 唤醒等待小结测试实验1. 死锁 死锁是指在一组进程中的各个进程均占有不会释…

linux服务器CPU飙高排查

文章目录前言一、第一步 top二、根据pid查找具体线程2.根据pid找到16进制3. 根据进程和线程查找原因总结前言 系统cpu飙高,尤其对于后端人员来说,其实应该学会排查,这样也算是综合能力的体现;那么当出现了cpu严重飙高的时候怎么排查呢? 一、第一步 top 直接在问题服务器输入…

如何系统地学习 C++ 语言?

C 最大的缺点就是语法复杂&#xff0c;而且难学难精。 学习 C 语言也许一本 K&R 的《C程序设计语言》就够了&#xff0c;但是在 C 世界里&#xff0c;找不到这样一本书。在这个爱速成的年代&#xff0c;大家可能耐不住寂寞花很长时间去学习一门语言&#xff0c;所以可以看…

车载以太网 - DoIP电源模式 - 07

DoIP电源模式&节点状态&激活检查这3块内容没有太大的逻辑性可言,主要是概念性内容介绍,所以本篇内容可能会略显枯燥,不过我回尽量完整的把这几块内容介绍清晰,为后面的测试用例设计做好铺垫,方便大家在看完文章后,更加快速的提取知识点,并完成测试设计。 诊断电…

回溯法--图的m着色问题--子集树

问题描述 给定无向连通图和m种不同的颜色&#xff0c;用这些颜色为图G的各个顶点着色&#xff0c;每个顶点有一种颜色 是否有一种着色方法&#xff1f;使得图G中每条边的两个顶点有不同的颜色 这个问题就是图的m可着色判定问题 色数&#xff1a;如果有一个图最少需要m种颜色…

C进阶:自定义类型:结构体、枚举、联合体

自定义类型&#xff1a;结构体、枚举、联合体自定义类型&#xff1a;结构体、枚举、联合体结构体结构的定义&#xff1a;结构体的声明&#xff1a;特殊的声明(匿名)结构体的自引用&#xff1a;结构体变量的定义和初始化结构体内存对齐为什么要有内存对齐修改默认对齐数结构体传…

李宏毅ML-自动调整学习速率

自动调整学习速率 文章目录自动调整学习速率1. RMS and Adagrad2. RMSProp and Adam3. Learning rate scheduling3. 总结1. RMS and Adagrad 在下面有两幅图&#xff0c;如第一幅图所示&#xff0c;随着 iteration 的增加&#xff0c;loss 在不断减少&#xff0c;最后趋近于 0…

广告业务系统 之 智能保险丝 —— “智能流控”

文章目录广告业务系统 之 智能保险丝 —— “智能流控”智能流控常规流量调控数据源计算智能流控功能挂载阈值存储架构长短板服务构建及部署广告业务系统 之 智能保险丝 —— “智能流控” 除了 在 AB 环节 设计了出色的 重试机制 —— “ 双发 ” 外&#xff0c;在 ADX 系统的…

【Redis】Redis实现全局唯一ID

【Redis】Redis实现全局唯一ID 为什么要使用Redis实现全局唯一ID去替代传统的数据库自增ID&#xff0c;主要原因如下&#xff1a; 数据库自增ID的规律性太明显受单表数据量的限制&#xff0c;数据量很大时分表会出现ID重复的现象 1. 全局ID生成器 出于以上原因&#xff0c;我…

Vue3——第十三章(插槽 Slots)

一、插槽内容与出口 这里有一个 <FancyButton> 组件&#xff0c;可以像这样使用&#xff1a; 而 <FancyButton> 的模板是这样的&#xff1a; <slot> 元素是一个插槽出口 (slot outlet)&#xff0c;标示了父元素提供的插槽内容 (slot content) 将在哪里被…

excel图表技巧:如何用填充单元格制作比率分析图

在工作中&#xff0c;我们经常要向上级领导汇报某个指标的进度或完成情况。有时候&#xff0c;我们会用仪表盘或温度计图来展示数据。不会这类型图表的朋友&#xff0c;不用担心&#xff0c;因为今天&#xff0c;我将教给大家一种更简单的方法&#xff01;公司新来的职员小明&a…

你是真的“C”——详解C语言函数模块知识(下篇)

详解C语言函数模块知识(下篇&#xff09;&#x1f60e;前言&#x1f64c;1、 函数的嵌套调用和链式访问&#x1f64c;1.1 嵌套调用&#x1f49e;1.2 链式访问&#x1f49e;2、函数的声明和定义&#x1f64c;2.1函数声明&#x1f49e;2.1函数定义&#x1f49e;3、函数递归&#…

【算法题解】9. 邻值查找

文章目录题目解题思路代码实现复杂度分析这是一道中等难度的题。 题目来自&#xff1a;AcWing 题目 给定一个长度为 n 的序列 A&#xff0c;A 中的数各不相同。 对于 A 中的每一个数 Ai&#xff0c;求&#xff1a; min|Ai−Aj|&#xff0c;其中 1 < j < i。 以及令上式…