Linux Futex学习笔记

Futex

简介

概述: Futex(Fast Userspace Mutex)是linux的一种特有机制,设计目标是避免传统的线程同步原语(如mutex、条件变量等)在用户空间和内核空间之间频繁的上下文切换。Futex允许在用户空间处理锁定和等待的操作,只有在必要时进入内核,从而减少了不必要的开销。

对比:

  • SpinLock:如果上锁成功,立即进入临界区,开销很小;但是如果上锁失败,CPU空转,浪费资源
  • Mutex:如果上锁失败,内核会切换到其他线程在CPU运行,不用自旋等待锁;但是即使是上锁成功也要进出内核,使用系统调用,会消耗几百个指令
  • Futex:结合上述二者的优点,在用户态尝试上锁,上锁成功既可以不用进入内核态,上锁失败就通过系统调用睡眠,唤醒也要通过系统调用
    • 在用户空间尝试加锁通常通过原子操作来完成,如CAS(Compare-And-Swap)或者其他无锁的原子操作

原理

本质: futex直接通过虚拟地址(是一个用户空间地址,通常是一个32位的锁变量字段,0表示空闲,1表示有线程持有当前锁)来处理锁,而不是像以前一样创建一个需要初始化以及跨进程全局内核句柄

用户空间地址: futex用户空间地址可以通过mmap系统调用分配,而mmap分配的内存有三种:匿名内存、共享内存段、内存映射文件。匿名内存只能用在同一进程的线程之间使用,而共享内存段和内存映射文件可以在多个进程之间使用

主要功能:

  • Futex原子操作:用户空间线程首先执行一些原子操作(如使用atomic函数或cmpxchg指令)来尝试获取锁
  • 短路路径:如果锁在用户空间已经被释放,线程可以直接在用户空间完成同步,无需涉及内核
  • 等待:如果一个线程尝试获取的锁已经被另一个线程占用,它可以调用futex_wait()进入休眠
  • 唤醒:当锁被释放,另一个线程可以调用futex_wake()唤醒等待的线程

涉及的系统调用:

  • futex():允许用户空间线程等待或唤醒其他线程。

    int futex(int *uddr, int op, int val, const struct timespec *timeout, int *uaddr2, int val2);
    
    • uaddr:用户空间地址,表示锁的位置
    • val:如果是FUTEX_WAIT,表示原子性地检查uaddr中的值是否为val,如果是则让进程睡眠,否则返回错误;如果是FUTEX_WAKE,表示最多唤醒val个等待在uaddr上的进程
    • op:操作类型:
      • FUTEX_WAIT:等待操作,线程会阻塞,直到指定的内存地址值发生变化
      • FUTEX_WAKE:唤醒操作,唤醒一个或多个等待该内存地址的线程
    • timeout:可选的等待时间,如果为空,那么调用会无限期睡眠。timeout默认会根据CLOCK_MONOTONIC时钟来计算,从linux4.5开始,可以再futex_op上指定FUTEX_CLOCK_REALTIME来选择CLOCK_REALTIME时钟。
    • uaddr2和val2在某些情况下用于双重条件等待
  • futex_wait()和futex_wake()是更高级的封装,用于在应用程序中更方便地实现等待和唤醒操作

实际应用

  • pthread_mutex:在linux中,pthread_mutex是基于futex实现的。
  • 内存屏障和自旋锁:在一些高效的同步机制中,futex也常常与自旋锁、内存屏障等技术结合使用

代码示例

代码仓库: 1037827920/Futex-Example-Program

futex本身的使用

Rust示例程序:(需要使用到libc crate)

use libc::{syscall, SYS_futex, FUTEX_WAIT, FUTEX_WAKE};
use std::{panic,sync::{atomic::{AtomicU32, Ordering},Arc,},thread,time::Duration,
};const UNLOCKED: u32 = 0;
const LOCKED: u32 = 1;macro_rules! futex_status {($val:expr) => {if $val == 0 {"UNLOCKED"} else {"LOCKED"}};
}fn main() {test_futex();
}fn futex_wait(futex: &AtomicU32, thread: &str) {loop {// 如果当前futex没有被其他线程持有if futex.compare_exchange(UNLOCKED, LOCKED, Ordering::SeqCst, Ordering::SeqCst).is_ok(){// 加锁后直接返回,这样就不用执行系统调用,减少一定开销println!("线程{thread}上锁成功, futex状态: {}",futex_status!(futex.load(Ordering::SeqCst)));return;}// 线程进入等待状态println!("线程{thread}正在等待futex");let ret = unsafe {syscall(SYS_futex,futex as *const AtomicU32 as *mut u32,FUTEX_WAIT,futex.load(Ordering::SeqCst),0,0,0,)};if ret == -1 {panic!("futex_wait系统调用执行失败");}}
}fn futex_wake(futex: &AtomicU32, thread: &str) {let ret = unsafe {syscall(SYS_futex,futex as *const AtomicU32 as *mut u32,FUTEX_WAKE,1,0,0,0,)};if ret == -1 {panic!("futex_wake系统调用执行失败");}println!("线程{thread}释放锁");futex.store(UNLOCKED, Ordering::SeqCst);
}/// 测试基本的futex使用
fn test_futex() {// futex用户空间地址let futex = Arc::new(AtomicU32::new(0));let futex_clone1 = futex.clone();let futex_clone2 = futex.clone();// 线程1let thread1 = thread::spawn(move || {// 尝试获取锁futex_wait(&futex_clone1, "1");// 执行具体的业务逻辑thread::sleep(Duration::from_secs(5));// 释放锁futex_wake(&futex_clone1, "1");});// 线程2let thread2 = thread::spawn(move || {// 尝试获取锁futex_wait(&futex_clone2, "2");// 执行具体的业务逻辑thread::sleep(Duration::from_secs(5));// 释放锁futex_wake(&futex_clone2, "2");});thread1.join().unwrap();thread2.join().unwrap();
}

C示例程序:

#include <stdio.h>
#include <pthread.h>
#include <stdatomic.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/futex.h>#define UNLOCKED 0
#define LOCKED 1
#define FUTEX_STATUS(val) val == 0 ? "UNLOCKED" : "LOCKED"// 定义一个结构体来封装参数
typedef struct {atomic_uint* futex;int thread;
} thread_args;void* futex_wait(atomic_uint* futex, int thread) {while (1) {// 如果当前futex没有其他线程持有int expected = UNLOCKED;if (atomic_compare_exchange_strong(futex, &expected, LOCKED)) {// 加锁后直接返回printf("线程%d上锁成功. futex状态: %s\n", thread, FUTEX_STATUS(*futex));return NULL;}// 线程进入等待状态printf("线程%d正在等待futex\n", thread);long ret = syscall(SYS_futex, (unsigned*)futex, FUTEX_WAIT, *futex, 0, 0);if (ret == -1) {perror("futex_wait系统调用执行失败\n");return NULL;}}
}void* futex_wake(atomic_uint* futex, int thread) {long ret = syscall(SYS_futex, (unsigned*)futex, FUTEX_WAKE, 1, 0, 0, 0);if (ret == -1) {perror("futex_wake系统调用执行失败\n");return NULL;}atomic_store(futex, UNLOCKED);printf("线程%d释放锁\n", thread);return NULL;
}void* thread_task(void* arg) {thread_args* args = (thread_args*)arg;// futex用户空间地址atomic_uint* futex = args->futex;// 线程号int thread = args->thread;// 尝试获取锁futex_wait(futex, thread);// 执行具体的业务逻辑sleep(5);// 释放锁futex_wake(futex, thread);return NULL;
}int main() {// 线程句柄pthread_t t1, t2;// futex用户空间地址atomic_uint futex = 0;thread_args args1 = { &futex, 1 };thread_args args2 = { &futex, 2 };// 创建两个线程同时递增cntpthread_create(&t1, NULL, thread_task, (void*)&args1);pthread_create(&t2, NULL, thread_task, (void*)&args2);// 等待线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);return 0;
}

运行结果:

在这里插入图片描述

pthread_mutex的使用

C示例程序:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 共享计数器
int shread_cnt = 0;
// 互斥锁
pthread_mutex_t cnt_mutex = PTHREAD_MUTEX_INITIALIZER;// 线程执行的任务
void* thread_task(void* arg) {// 线程IDlong tid = (long)arg;// 循环5次,每次增加计数器for (int i = 0; i < 5; ++i) {// 加锁pthread_mutex_lock(&cnt_mutex);// 临界区:修改共享资源int tmp = shread_cnt;// 模拟一些处理时间usleep(100);shread_cnt = tmp + 1;printf("线程 %ld: 计数器值 = %d\n", tid, shread_cnt);// 解锁pthread_mutex_unlock(&cnt_mutex);// 模拟一些处理时间usleep(200);}return NULL;
}int main() {// 定义线程句柄数组pthread_t threads[3];// 创建3个线程for (long i = 0;i < 3; ++i) {int ret = pthread_create(&threads[i], NULL, thread_task, (void*)i);if (ret != 0) {perror("线程创建失败");return 1;}}// 等待所有线程完成for (int i = 0; i < 3; ++i) {pthread_join(threads[i], NULL);}// 打印最终计数器值printf("最终计数器值 = %d\n", shread_cnt);// 销毁互斥锁pthread_mutex_destroy(&cnt_mutex);return 0;
}

原理浅析:

pthread_mutex_lock实际上会执行__pthread_mutex_lock ,然后这个函数实现里面会调用LLL_MUTEX_LOCK宏,这个宏会调用__lll_lock宏,在这个宏里面就会先尝试在用户态进行上锁(也就是通过CAS的原子操作进行上锁),然后上锁失败再调用__lll_lock_wait(或_\lll_lock_wait_private),这个函数在追踪下去就会执行futex_wait系统调用。这个流程就跟上面futex本身的使用差不多了。

想要看更详细的讲解可以看这个博客:pthread_mutex_lock实现 - pslydhh

参考

  • ‍‌‌‌‬‬‬‍‬‌‍‬‬‌‍‬‌⁠‌‍futex技术分享
  • 2022年南大操作系统课程——并发控制:互斥
  • pthread_mutex_lock实现 - pslydhh

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

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

相关文章

小柯剧场训练营第一期音乐剧演员与第二期报名拉开帷幕!

在当下社会文化浪潮中&#xff0c;小柯剧场凭借其独特的培养模式和“先看戏后买票”的良心举措&#xff0c;已然成为艺术界的一股清流。1月12日&#xff0c;由“第一期免费训练营”优秀学员们所带来的新一代《稳稳的幸福》成功落幕&#xff0c;引起了社会的广泛关注。该活动不仅…

基于迁移学习的ResNet50模型实现石榴病害数据集多分类图片预测

完整源码项目包获取→点击文章末尾名片&#xff01; 番石榴病害数据集 背景描述 番石榴 &#xff08;Psidium guajava&#xff09; 是南亚的主要作物&#xff0c;尤其是在孟加拉国。它富含维生素 C 和纤维&#xff0c;支持区域经济和营养。不幸的是&#xff0c;番石榴生产受到降…

【论文阅读】HumanPlus: Humanoid Shadowing and Imitation from Humans

作者&#xff1a;Zipeng Fu、Qingqing Zhao、Qi Wu、Gordon Wetstein、Chelsea Finn 项目共同负责人&#xff0c;斯坦福大学 项目网址&#xff1a;https://humanoid-ai.github.io 摘要 制造外形与人类相似的机器人的一个关键理由是&#xff0c;我们可以利用大量的人类数据进行…

2025牛客寒假算法基础集训营2

H 一起画很大的圆&#xff01; 看起来像是一道计算几何的题&#xff0c;实际上通过分析和猜想&#xff0c;是有O1复杂度的结论的。具体证明略&#xff0c;结论是三点越接近共线&#xff0c;得出的半径越大。 #include <bits/stdc.h> using namespace std; #define endl \…

PVE 虚拟机安装 Debian 无图形化界面服务器

Debian 安装 Debian 镜像下载 找一个Debian镜像服务器&#xff0c;根据需要的版本和自己硬件选择。 iso-cd/&#xff1a;较小&#xff0c;仅包含安装所需的基础组件&#xff0c;可能需要网络访问来完成安装。有镜像 debian-12.9.0-amd64-netinst.isoiso-dvd/&#xff1a;较…

硬件学习笔记--35 AD23的使用常规操作

原理图设计 1&#xff09;新建原理图&#xff0c;File-new-Schematic。相关设置参考&#xff0c;主要包含图纸设置以及常规的工具栏。 PCB的设计 新建PCB&#xff0c;设置相应的规则&#xff08;与原理图中相对应&#xff09;&#xff0c;放到同一个工程中。如果有上一版本的…

解读2025年生物医药创新技术:展览会与论坛的重要性

2025生物医药创新技术与应用发展展览会暨论坛&#xff0c;由天津市生物医药行业协会、BIO CHINA生物发酵展组委会携手主办&#xff0c;山东信世会展服务有限公司承办&#xff0c;定于2025年3月3日至5日在济南黄河国际会展中心盛大开幕。展会规模60000平方米、800参展商、35场会…

Poseidon哈希为什么适合做ZKP

论文- https://eprint.iacr.org/2019/458.pdf Poseidon 哈希算法的硬件加速与实现 实用的计算完整性证明系统领域&#xff0c;如 SNARKs、STARKs、Bulletproofs&#xff0c;正在经历非常动态的发展&#xff0c;最近出现了几种具有改进性能和放宽设置要求的结构。此类系统的许多…

开始步入达梦中级dba

分析内存使用需要的方法之一 disql /nolog conn sysdba/sysdbaselect value from v$parameter where nameMEMORY_LEAK_CHECK; SP_SET_PARA_VALUE(0,MEMORY_LEAK_CHECK,1); select * from V$MEM_REGINFO; select * from V$MEM_HEAP;

UDP 广播组播点播的区别及联系

1、网络IP地址的分类 组播地址是分类编址的IPv4地址中的D类地址&#xff0c;又叫多播地址&#xff0c;他的前四位必须是1110&#xff0c;所以网络地址的二进制取值范围是11100000~11101111对应的十进制为 224~~239。所以以224~239开头的网络地址都是组播地址。 组播地址的功能…

opengrok_使用技巧

Searchhttps://xrefandroid.com/android-15.0.0_r1/https://xrefandroid.com/android-15.0.0_r1/ 选择搜索的目录&#xff08;工程&#xff09; 手动在下拉框中选择&#xff0c;或者 使用下面三个快捷按钮进行选择或者取消选择。 输入搜索的条件 搜索域说明 域 fullSearc…

IDEA中Maven使用的踩坑与最佳实践

文章目录 IDEA中Maven使用的踩坑与最佳实践一、环境配置类问题1. Maven环境配置2. IDEA中Maven配置建议 二、常见问题与解决方案1. 依赖下载失败2. 依赖冲突解决3. 编译问题修复 三、效率提升技巧1. IDEA Maven Helper插件使用2. 常用Maven命令配置3. 多模块项目配置4. 资源文件…

Flink读写Kafka(Table API)

前面(Flink读写Kafka(DataStream API)_flink kafka scram-CSDN博客)我们已经讲解了使用DataStream API来读取Kafka,在这里继续讲解下使用Table API来读取Kafka,和前面一样也是引入相同的依赖即可。 <dependency> <groupId>org.apache.flink</groupId&…

jira.issueviews

jira.issueviews 是 JIRA 提供的一种功能&#xff0c;用于以多种格式&#xff08;如 Excel、XML、RSS、Word 等&#xff09;导出查询结果或单个 Issue 的详细信息。这一功能特别适用于 JIRA Server 和 JIRA Data Center 环境&#xff0c;方便用户将数据导出并进一步分析或分享。…

SQL UNION 和 UNION ALL 区别

一、区别1&#xff1a;取结果的交集 1、union: 对两个结果集进行并集操作, 不包括重复行,相当于distinct, 同时进行默认规则的排序; 2、union all: 对两个结果集进行并集操作, 包括重复行, 即所有的结果全部显示, 不管是不是重复; 二、区别2&#xff1a;获取结果后的操作 1…

python flask中使用or查询和and查询,还有同时使用or、and的情况

在 Flask 中处理数据库查询时&#xff0c;通常会结合使用 ORM 工具&#xff0c;例如 SQLAlchemy。以下是 or 查询、and 查询以及两者同时使用的示例。 文章目录 基础准备1. 使用 or_ 查询2. 使用 and_ 查询3. 同时使用 or_ 和 and_4. 更加复杂的嵌套查询 基础准备 假设有一个…

ue5 运行时大纲视图中的数据获取方法

大纲视图需要treeview控件的树形结构展示&#xff0c;创建一个treeview需要两个要素&#xff1a; 1、Item&#xff1a;我称之为一组数据&#xff0c;就类似于一个actor中都包含哪些组件&#xff0c;或者是一个类与类中的成员。 2、treeview控件&#xff1a;实现树形结构的类&…

ArcGIS10.2 许可License点击始终启动无响应的解决办法及正常启动的前提

1、问题描述 在ArcGIS License Administrator中&#xff0c;手动点击“启动”无响应&#xff1b;且在计算机管理-服务中&#xff0c;无ArcGIS License 或者License的启动、停止、禁止等均为灰色&#xff0c;无法操作。 2、解决方法 ①通过cmd对service.txt进行手动服务的启动…

three.js+WebGL踩坑经验合集(1):THREE.Line无故消失的元凶

在项目开发过程中&#xff0c;笔者两次遇到同事的一个提问&#xff0c;我场景中的Line在相机旋转到某些角度或者移动到某些位置的时候会无故消失。由于业务场景复杂&#xff0c;所以这两位同事都是先花费了大量时间排查业务问题&#xff0c;然后才找我求助。这个问题抽象出来的…

微信小程序-点餐(美食屋)02开发实践

目录 概要 整体架构流程 &#xff08;一&#xff09;用户注册与登录 &#xff08;二&#xff09;菜品浏览与点餐 &#xff08;三&#xff09;订单管理 &#xff08;四&#xff09;后台管理 部分代码展示 1.index.wxml 2.list.wxml 3.checkout.wxml 4.detail.wxml 小结优点 概要…