brpc源码解析(十七)—— bthread上的类futex同步组件butex详解

2023-11-18

我们知道在linux 下,锁和其他一些同步机制都会用到futex,futex诞生后扮演着非常重要的角色,可以说futex是linux底层最重要的同步手段之一,无论是pthread_mutex还是semaphore都用到了futex,bthread作为基于pthread实现的一套线程库,自然也需要类似的线程同步机制,用于在bhtread上进行阻塞同步,因此就有了类futex的butex,这篇文章就来聊聊butex的实现。

一、futex简介

在介绍butex之前我们首先来简单看下futex,网上关于futex的介绍有很多,这里只是简单介绍下用于引出本文的主题butex,想更深入地学习可以看看2篇参考文章,写得很不错。

futex是一个高效的同步组件,futex由一个内核态的队列和一个用户态的integer构成,有竞争时会放到等待队列供后面唤醒,整个操作主要用到了自旋锁来保护临界区。基于futex构造锁到时候一个典型的模式是先通过对一个原子变量进行cas操作尝试直接获取锁,如果没竞争直接返回,有竞争调用futex_wait。

简单来说,futex主要包括等待和唤醒两个方法:futex_wait和futex_wake,简化后的定义如下:

//uaddr指向一个地址,val代表这个地址期待的值,当*uaddr==val时,才会进行wait
int futex_wait(int *uaddr, int val);

//唤醒n个在uaddr指向的锁变量上挂起等待的进程
int futex_wake(int *uaddr, int n);

这里贴一下参考文章对两个流程的总结:

(1)futex_wait:

  1. 加自旋锁
  2. 检测*uaddr是否等于val,如果不相等则会立即返回
  3. 将进程状态设置为TASK_INTERRUPTIBLE
  4. 将当期进程插入到等待队列中
  5. 释放自旋锁
  6. 创建定时任务:当超过一定时间还没被唤醒时,将进程唤醒
  7. 挂起当前进程

(1)futex_wake:

  1. 找到uaddr对应的futex_hash_bucket
  2. 对其加自旋锁
  3. 遍历f链表,找到uaddr对应的节点
  4. 调用wake_futex唤起等待的进程
  5. 释放自旋锁

二、butex源码解析

既然是类futex,实现机制自然基本类似,只是更为上层,下面就从源码层面来看下butex的实现。

2.1 butex相关数据结构

因为需要维护等待队列,因此需要push到等待队列的数据结构,bthread的核心的api都是兼容pthread的,所以butex也要支持pthread。也就有了ButexBthreadWaiter和ButexPthreadWaiter两种用于阻塞后唤醒的waiter。
在这里插入图片描述

另一个重要的数据结构就是butex本身,里面的value扮演的也就是我们futex wait所用到的u32的val的角色,waiters则是当前butex对应的等待队列,waiter_lock则是操作队列用到的互斥锁,在futex里,操作队列用的是内核的自旋锁,butex本身的逻辑是在用户态的,也就是不阻塞pthread仅切换bthread,因此可以直接使用pthread互斥锁,这里的FastPthreadMutex按官方文档的说明是比原生的pthread_mutex性能略好。
在这里插入图片描述

2.2 butex主要机制

下面就以bthread中等待fd的fd_wait函数为例来看butex的整体机制,这个函数用于等待fd就绪,比如ssl相关的逻辑就用到了这个来等待ssl握手完毕。
在这里插入图片描述
在这里插入图片描述
在初始状态下,需要新建butex对象,在这个场景下用的是cas来建立单例,butex对象的类型是EpollButex,也就是一个原子int,首先需要调用butex_create_checked来创建,函数内容如下:
在这里插入图片描述
之所以叫create_checked,是因为有一个类型大小的check,我们知道futex依赖的就是一个u32的整形,但是butex为了方便使用是直接支持所有大小为32位的类型的,因此会先做一个sizeof(T) == sizeof(int)的check。随后才是从objec pool里获得真正的butex对象,类型就是上面提到的Butex,用户侧的butex value是被封装在这个结构里面的、butex_create_checked返回的是static_cast<T*>之后的类型,之所以可以这么转是因为butex的第一个成员变量就是一个原子int。

得到butex变量后,先把butex值保存起来,随后添加epoll时间后调用butex_wait,也就是butex机制的核心函数之一。

2.2.1 butex_wait

在这里插入图片描述
在这里插入图片描述
和futex_wait类似,butex_wait里,首先也是判断值是否匹配,如果不匹配就直接返回不阻塞,注意这里对butex变量的使用,传入的arg变量是用户侧实际使用的32位长度的任意类型的butex变量,根据前面获取的过程我们可以知道,这个变量的地址其实也就是背后隐含的Butex struct的起始地址,因此可以直接通过这个地址来拿到整个Butex对象,也就是container_of宏定义做的事情,不得不感叹各种神奇的玩法是真的多。这么绕一圈的好处就是对于用户来说,只需要关心自己的butex变量。

不等于expected_value的时候,直接返回。否则进入wait相关操作,如果是pthread,就走pthread_wait的逻辑,原理类似,这里不一一介绍了。否则创建bthread 的waiter并进行相关变量的赋值,包括超时时间的设定。说到超时,这里提一下,是通过一个全局的timer thread实现的,它的schedule函数会新增一项调度,也就是abstime到了之后用bbw作为参数去调用erase_from_butex_and_wakeup,实现超时唤醒。

waiter的container是用来保存butex的指针的,在唤醒从队列移除的时候需要用它来找到锁,这里首先设置为null,在下面的wait_for_butex函数里会被置成b,这里为NULL也代表了没有在队列里的状态。
在这里插入图片描述
然后就是把当前bthread挂到队列里并让出worker。具体到实现,首先是把当前waiter保存到当前任务信息里,然后是将wait_for_butex通过set_remained交给下一个获得worker的bthread来运行从而将waiter挂载进队列。
在这里插入图片描述
wait_for_butex函数如下:
在这里插入图片描述
首先会对waiter_list的互斥锁加锁,在真正放进队列之前再check一次value是否发生了变化,如果发生了变化,直接unsleep并且调度原来的bthread,否则,append到waiter队列。如前面所说,这里将butex的指针保存到了这个waiter的container里,这是为了从双端队列里删除的时候对butex加锁。

2.2.2 butex_wake

前面说了butex wait的过程,wait之后该如何唤醒呢,回到上面那个场景,前面的入口fd_wait就是需要等待fd就绪,就绪后的唤醒由一个epoll thread来负责,一旦发生了相应的epoll out事件就会调用butex_wake_all来唤醒睡在对应butex上的bthread,如下:
在这里插入图片描述
butex_wake_all是唤醒等在butex上的所有线程,还有一个butex_wake只会唤醒一个,原理类似,这里只介绍wak_all。首先是对butex值原子+1,原子+1保证了后面的butex_wait会直接返回。随后调用butex_wake_all唤醒所有等在当前butex上的bthread。butex_wake_all的主要逻辑如下:
在这里插入图片描述
开头仍然是通过宏定义拿到butex对象。然后遍历butex的waiter队列,分别将其中的bth和pth放到不同的临时list里,因为对双端队列进行删除操作非线程安全,所以会加锁。这里有个对container置NULL的操作,表明已经从队列移除了,一个典型的场景是对于设置了超时时间的wait,在唤醒后最初定的超市任务到了看到container为NULL就不需要进行移除操作了。
在这里插入图片描述
通过wakeup_pthread函数唤醒所有的pthread。
在这里插入图片描述
通过bthread函数唤醒所有的bthread,除了移出队列进行调度以外,还调用了unsleep_if_necessary用于取消超时的任务。
在这里插入图片描述
除此还有包括butex_requeue和butex_wake_except在内的其他一些函数,原理都差不多,这里不再赘述。

参考:

  1. 关于同步的一点思考-上
  2. linux内核级同步机制–futex
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

brpc源码解析(十七)—— bthread上的类futex同步组件butex详解 的相关文章

  • 为什么shared_ptr删除器必须是可复制构造的?

    在 C 11 中std shared ptr有四个构造函数 可以传递删除器对象d类型的D 这些构造函数的签名如下 template
  • 如何显示图片目录中的图像?

    我想显示图片库中的图片 我获取图片并绑定数据 StorageFolder picturesFolder KnownFolders PicturesLibrary IReadOnlyList
  • C# 中的嵌套正则表达式替换

    我不太擅长正则表达式 但我了解基础知识 我试图弄清楚如何根据匹配中的某个值进行条件替换 例如 假设我有一些嵌套的字符串结构 如下所示 id value id and value are space delimited id will nev
  • 具有自定义字段名称的 RavenDB 查询索引

    我在 RavenDB 中收集了 Message 文档 定义 class Message string Content Tag Tags class Tag string Value 我有索引 from doc in docs Message
  • gcc 无效版本(最大)错误添加符号:错误值

    我已经在 Linux x86 x64 上成功构建了几个 32 位静态和共享库 现在我尝试将它们链接到一个可执行文件 但出现以下错误 usr bin ld foo so moddi3 invalid version 21 max 0 foo
  • C# 中是否有相当于 php array_merge 的函数

    如果不是 创建它的最佳方法是什么 注意 合并不仅仅是附加 它融合了相同的键 此功能存在于 List 元素上 在 C 中 数组是固定宽度的项 因此在不创建新数组的情况下无法修改大小 然而 列表却是另一回事 你可以做 List
  • C++ 获取两个分隔符字符串之间的字符串

    C C 中是否有任何内置函数可以在两个分隔符字符串之间获取字符串 我的输入看起来像 STARTDELIMITER 0 192 168 1 18 STOPDELIMITER 我的输出应该是 0 192 168 1 18 提前致谢 你可以这样做
  • Arduino C++ 析构函数?

    我知道在Arduino中你不能使用delete 那么什么时候调用 C 类中定义的析构函数呢 同样 如果我想创建一个指向数组的指针 我必须使用malloc and free 当对象被销毁时 析构函数被调用 对于自动 堆栈上 变量 它在离开其作
  • C#等待串口数据

    我试图通过 C 应用程序从指纹扫描仪获取数据 但在指纹发送之前 我的整个代码都会执行 我尝试使用延迟功能System Threading Thread Sleep 1000 因此它可以在下一步执行之前获取数据 但这一切似乎都是徒劳的 任何人
  • C# 从视频文件的一部分中提取帧

    使用 AForge ffmpeg 包装器 您可以使用 VideoFileReader 类从视频中提取帧并将其保存为位图 请参阅以下示例 提取 avi 文件的帧 https stackoverflow com questions 178256
  • 什么是排列索引?

    我正在阅读 加速 C 我不明白练习 5 1 设计并实现一个程序 根据以下输入生成排列索引 排列索引是其中每个短语由短语中的每个单词索引的索引 The quick brown fox jumped over the fence The qui
  • #region 描述编译到.net 中的.exe 中?

    region endregion 指令 描述 是否编译到 NET 中的 EXE 中 我知道注释不是 但我经常在一个区域内对代码组进行分块并给出有用的描述 我想确保这些描述在我编译的代码中不可见 我不是在寻找混淆信息 不过 谢谢 不 他们不是
  • 如何在 C++ 中检查文件是否已被另一个应用程序打开?

    我知道 有is open C 中的函数 但我希望一个程序检查文件是否尚未被另一个应用程序打开 有没有办法使用标准库来做到这一点 编辑 在答案中澄清这是针对 Linux 应用程序的 不仅标准库没有这个功能 一般来说也是不可能的 你可以 在li
  • ARM + gcc:不要使用一大块 .rodata 部分

    我想使用 gcc 编译一个程序 并针对 ARM 处理器进行链接时间优化 当我在没有 LTO 的情况下编译时 系统会被编译 当我启用 LTO 时 使用 flto 我收到以下汇编错误 错误 无效的文字常量 池需要更近 环顾网络 我发现这与我系统
  • 为数据提供有效类型是否会产生副作用?

    假设我有一大块动态分配的数据 void allocate size t n void foo malloc n return foo 我希望使用指向的数据foo作为一种特殊类型 type t 但我想稍后再这样做 而不是在分配期间这样做 为了
  • 如何显式调用其 conversion-type-id 包含占位符说明符的转换函数

    struct A operator auto return 0 int main A a a operator auto 1 a operator int 2 GCC https godbolt org z 3jdaK9接受 2 是显式调用
  • 将计时器添加到 Windows 窗体应用程序

    我想添加一个计时器而不是倒计时 它会在什么时候自动开始 表单加载 开始时间应为 45 分钟 一旦结束 即达到 0 分钟时 表单应终止并显示一条消息 我怎样才能做到这一点 语言 最好是C 更详细一点 private void Form1 Lo
  • 是否可以在 Visual Studio 2010 项目中使用多个“字符集”?

    如您所知 在 Visual Studio 2010 c 中 我们有 noset unicode 和 MBCS 字符集 我们可以通过菜单或预处理器指令 如 define UNICODE 来设置它 我正在开发一个项目 它有一个使用 MBCS 字
  • Xamarin Mac 中 AttributeName 的用途

    我正在尝试对 Xamarin 中的 NSMutableAttributedString 中的子字符串进行着色 但它似乎缺少正确的常量 我应该在那里放什么 Update 这越来越接近 var s new NSMutableAttributed
  • WPF 中的 InvokeRequired [重复]

    这个问题在这里已经有答案了 我在一个中使用了这个函数Windows forms应用 delegate void ParametrizedMethodInvoker5 int arg private void log left accs in

随机推荐

  • MySQL导出和导入SQL脚本

    首先 使用mysqldump命令的前提是 在Cmd中进入mysql安装目录下的bin目录下 才可以使用该命令 我的mysql安装在E 盘 所以 首先进入bin目录下 E Program Files MySQL MySQL Server 5
  • SLAM常用最小二乘最优化方法学习、分析和总结

    SLAM中二维视觉的定位问题被最终归为一个最小二乘问题 那么紧随其后的就是对最小二乘的最优化求解 对其求解析解显然是不太合适的 所以就需要一些数值的最优化方法对最小二乘问题进行求解 在SLAM中 常用的算法有 梯度下降法 牛顿法 以及牛顿法
  • 数字后端——布局

    由于I O单元和模块的布放已经在布图规划时完成 因此布局的剩余任务主要是对标准单元的布局 布局方案在布图规划时就已经做了决定 要么选择展平式布局 要么就是层次化布局 一 布局目标 布局的目标也即布局内容实施之后所要达到的预期值 可以归纳为以
  • 【网络基础】路由表,分组转发算法

    前提 IP数据报的首部中没有地方可以用来指明 下一跳路由器的 IP 地址 那么 当路由器接受到一个待转发的报文时 是如何确定将该报文的传向呢 在此 我们引入 路由表 概念 路由表如图所示 当一个IP报文传到路由器R2时 则会通过查询R2所维
  • js基础--获取浏览器当前页面的滚动条高度的兼容写法

    前言 在开发中 兼容性问题是最常见的 今天就来介绍一下关于获取滚动条高度的兼容性写法 宽度同理 我在这里就不一一解释了 各浏览器的写法 IE6 7 8 document documentElement scrollTop IE9以上 win
  • 3D图形渲染技术

    如何用2D平面展现3D图形 2D图形 在一个平面中有了两个点 知道了他们的XY坐标 就可以把它们链接起来画成一条线 通过控制A和B点的XY坐标可以控制一条线 在3D图像中 点的坐标多了一个Z轴的坐标系 但是在2D的屏幕坐标上不可能有XYZ立
  • linux上mysql整库完全备份命令(包括函数和存储过程)

    mysqldump E R triggers single transaction master data 2 default character set utf8 u root p 库名 gt tmp 库名 sql R表示导出functi
  • 【毕业设计】基于SSM实现酒店管理系统(论文+源码+ppt+视频)

    技术架构SSM 1 Spring是一个开源的Java Java EE全功能栈的应用程序框架 以Apache许可证形式发布 也有 NET平台上的移植版本 当需要用到某一对象时不需要程序员在代码中增加一个新对象而是在可扩展标记语言 extens
  • php中流行的rpc框架详解(修改版)

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 什么是RPC框架 如果用一句话概括RPC就是 远程调用框架 Remote Procedure Call 那什么是远程调用 通常我们调用一个php中的方法 比如这样一个函数方
  • Windows 文件共享功能使用教程,局域网多台电脑之间传送文件

    设想一下 家里或者公司有多台电脑 连接同一个Wifi 也就是处于同一个局域网中 在不能使用微信 网盘的文件传输功能的情况下 这多台电脑之间 就只能用U盘传送数据吗 不 Windows系统中已经提供了文件共享功能 比如一些公司或者学校机房经常
  • H5 静态页面跳转微信小程序

    官方文档指引 开放标签说明文档 静态网站 H5 跳小程序 H5 静态页面跳转微信小程序 准备工作 开放标签 开放对象 版本要求 使用步骤 1 绑定域名 2 引入 jweixin js 需要 1 6 0 版本 3 设置 wx config 4
  • 服务器时间管理器

    时间戳管理器 using System using UnityEngine public class SyncTime Singleton
  • Tomcat中404/500 错误,自定义错误页面

    Tomcat中404 500 错误 自定义错误页面 当服务器出现404 500错误时候希望能够给用户友好的现实界面 只需要在项目的web xml中添加一些配置
  • unity和ffmpeg修改局部视频速度

    unity版本2020 3 17 前言 最近有个功能是 在一个展馆里面 有一个摄像头旋转拍照 拍一圈 本来功能很简单 就录屏就可以了上传生成二维码就ok了 但是 需要一个视频中间快两边变慢的效果 查了很多资料 最终决定使用ffmpeg和un
  • 移动端H5页面,上下滑动翻页

    升级版本 https blog csdn net qq 16494241 article details 122239278 改用原生JS编写 此版本基于JQ 可设置页面内容元素内部滚动及滚动至顶部或最底部触发翻页效果 PC端模式也可鼠标滑
  • Mac中安装anaconda3的2种方法:手动或homebrew安装

    Mac 上非常好用的包管理器 Homebrew 我们经常用它来安装软件包 它不仅可以安装MySQL MongoDB等软件包 还可以用Homebrew cask安装图形界面的 App 如谷歌浏览器等 也可以用终端上的 Mac App Stor
  • GetThreadContext failed报错解决方法

    最近编译后的项目总是不稳定 运行一会就崩溃 然后报错GetThreadContext failed 网上报这个错误的还挺多的 不过大部分都是玩家遇到的 GetThreadContext failed的原因很多 但我这次绝对是被steamVR
  • 斯皮尔曼(Spearman)\ 皮尔逊(Pearson)相关系数计算

    斯皮尔曼 Spearman 相关 斯皮尔曼 Spearman 相关是衡量两个变量的依赖性的 非参数 指标 它利用单调方程评价两个统计变量的相关性 如果数据中没有重复值 并且当两个变量完全单调相关时 斯皮尔曼相关系数则为 1或 1 scipy
  • Android Studio中SVN重新关联项目,使其文件变色

    一般我们的项目中文件夹中通过svn关联 通过Android Studio打开按道理的话 可算是关联了 这里先普及下 SVN的文件的颜色所代表的意思 如下 绿色 已加入VCS 但未提交 红色 未加入VCS 白色 已提交 蓝色 有修改 问题 在
  • brpc源码解析(十七)—— bthread上的类futex同步组件butex详解

    文章目录 一 futex简介 二 butex源码解析 2 1 butex相关数据结构 2 2 butex主要机制 2 2 1 butex wait 2 2 2 butex wake 我们知道在linux 下 锁和其他一些同步机制都会用到fu