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;我们可以利用大量的人类数据进行…

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场会…

开始步入达梦中级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. 资源文件…

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 小结优点 概要…

windows git bash 使用zsh 并集成 oh my zsh

参考了 这篇文章 进行配置&#xff0c;记录了自己的踩坑过程&#xff0c;并增加了 zsh-autosuggestions 插件的集成。 主要步骤&#xff1a; 1. git bash 这个就不说了&#xff0c;自己去网上下&#xff0c;windows 使用git时候 命令行基本都有它。 主要也是用它不方便&…

CDN、源站与边缘网络

什么是“源站” 源服务器 源服务器的目的是处理和响应来自互联网客户端的传入请求。源服务器的概念通常与边缘服务器或缓存服务器的概念结合使用。源服务器的核心是一台运行一个或多个程序的计算机&#xff0c;这些程序旨在侦听和处理传入的客户端请求。源服务器可以承担为网…

无人机 PX4 飞控 | PX4源码添加自定义参数方法并用QGC显示与调整

无人机 PX4 飞控 | PX4源码添加自定义参数方法并用QGC显示与调整 0 前言 之前文章添加了一个自定义的模块&#xff0c;本篇文章在之前的自定义模块中&#xff0c;添加两个自定义参数 使用QGC显示出来&#xff0c;并通过QGC调整参数值&#xff0c;代码实现参数更新 新增的参…

RabbitMQ 分布式高可用

文章目录 前言一、持久化与内存管理1、持久化机制2、内存控制1、命令行2、配置文件 3、内存换页4、磁盘控制 二、集群1、Erlang的分布式特性2、RabbitMQ的节点类型2.1、磁盘节点 (Disk Node)2.2、内存节点 (RAM Node) 3、构建集群3.1 普通集群3.2 镜像队列3.3、高可用实现方案3…

【JS|第28期】new Event():前端事件处理的利器

日期&#xff1a;2025年1月24日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

IDEA工具下载、配置和Tomcat配置

1. IDEA工具下载、配置 1.1. IDEA工具下载 1.1.1. 下载方式一 官方地址下载 1.1.2. 下载方式二 官方地址下载&#xff1a;https://www.jetbrains.com/idea/ 1.1.3. 注册账户 官网地址&#xff1a;https://account.jetbrains.com/login 1.1.4. JetBrains官方账号注册…

技术总结:FPGA基于GTX+RIFFA架构实现多功能SDI视频转PCIE采集卡设计方案

目录 1、前言工程概述免责声明 3、详细设计方案设计框图SDI 输入设备Gv8601a 均衡器GTX 解串与串化SMPTE SD/HD/3G SDI IP核BT1120转RGBFDMA图像缓存RIFFA用户数据控制RIFFA架构详解Xilinx 7 Series Integrated Block for PCI ExpressRIFFA驱动及其安装QT上位机HDMI输出RGB转BT…