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.rhkb.cn/news/7695.html

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

相关文章

Neural networks 神经网络

发展时间线 基础概念 多层神经网络结构 神经网络中一个网络层的数学表达 TensorFlow实践 创建网络层 神经网络的创建、训练与推理 推理 推理可以理解为执行一次前向传播 前向传播 前向传播直观数学表达 前向传播直观数学表达的Python实现 前向传播向量化实现 相关数学知识…

浅谈Redis

2007 年&#xff0c;一位程序员和朋友一起创建了一个网站。为了解决这个网站的负载问题&#xff0c;他自己定制了一个数据库。于2009 年开发&#xff0c;称之为Redis。这位意大利程序员是萨尔瓦托勒桑菲利波(Salvatore Sanfilippo)&#xff0c;他被称为Redis之父&#xff0c;更…

在Qt中实现点击一个界面上的按钮弹窗到另一个界面

文章目录 步骤 1&#xff1a;创建新窗口类步骤 2&#xff1a;设计窗口的 UI步骤 3&#xff1a;设计响应函数 以下是一个完整的示例&#xff0c;展示在Qt中如何实现在一个窗口中通过点击按钮弹出一个新窗口。 步骤 1&#xff1a;创建新窗口类 假设你要创建一个名为 WelcomeWidg…

【大数据】机器学习----------强化学习机器学习阶段尾声

一、强化学习的基本概念 注&#xff1a; 圈图与折线图引用知乎博主斜杠青年 1. 任务与奖赏 任务&#xff1a;强化学习的目标是让智能体&#xff08;agent&#xff09;在一个环境&#xff08;environment&#xff09;中采取一系列行动&#xff08;actions&#xff09;以完成一个…

攻防世界bad_python

文件名pyre.cpython-36.pyc&#xff0c;说明是在python3.6环境下编译的&#xff0c;要把pyc反编译成py 但是显示失败了&#xff0c;结合题的名字文件的应该是文件头部被破坏 把第一行改为33 0D 0D 0A 0C 63 4A 63 61 02 00 00 E3 00 00 00 之后就能反编译了&#xff0c;得到源…

网络安全 | F5-Attack Signatures详解

关注&#xff1a;CodingTechWork 关于攻击签名 攻击签名是用于识别 Web 应用程序及其组件上攻击或攻击类型的规则或模式。安全策略将攻击签名中的模式与请求和响应的内容进行比较&#xff0c;以查找潜在的攻击。有些签名旨在保护特定的操作系统、Web 服务器、数据库、框架或应…

Linux的常用指令的用法

目录 Linux下基本指令 whoami ls指令&#xff1a; 文件&#xff1a; touch clear pwd cd mkdir rmdir指令 && rm 指令 man指令 cp mv cat more less head tail 管道和重定向 1. 重定向&#xff08;Redirection&#xff09; 2. 管道&#xff08;Pipes&a…

Qt监控系统辅屏预览/可以同时打开4个屏幕预览/支持5x64通道预览/onvif和rtsp接入/性能好

一、前言说明 在监控系统中&#xff0c;一般主界面肯定带了多个通道比如16/64通道的画面预览&#xff0c;随着电脑性能的增强和多屏幕的发展&#xff0c;再加上现在监控摄像头数量的增加&#xff0c;越来越多的用户希望在不同的屏幕预览不同的实时画面&#xff0c;一个办法是打…

【BUUCTF】October 2019 Twice SQL Injection1及知识点整理

打开题目页面&#xff0c;是一个登陆界面 先进行注册&#xff0c;尝试使用SQL注入看看会返回什么 跳转到登陆界面&#xff0c;我们用刚才注册的登录 显示的界面如下 在这个页面输入SQL注入语句&#xff0c;发现将单引号转义为了\&#xff0c;其他关键字并没有过滤 查看给出的源…

vue3 获取百度天气

获取百度应用key 需要开通百度天气api&#xff0c;进入 控制台 | 百度地图开放平台&#xff0c; 1、创建应用 2、填写名称 3、勾选上天气、百度地图逆地理编码 4、会得到一个key vue获取天气 应该用的是接口获取&#xff0c;这里会有跨域的问题&#xff0c;vue上用的是pro…

[极客大挑战 2019]BuyFlag1

题目 查看页面源码代码 有个pay.php文件打开查看 查看页面源代码&#xff0c;下面是主要代码 <!--~~~post money and password~~~ if (isset($_POST[password])) {$password $_POST[password];if (is_numeric($password)) {echo "password cant be number</br>…

【C++高并发服务器WebServer】-7:共享内存

本文目录 一、共享内存1.1 shmget函数1.2 shmat1.3 shmdt1.4 shmctl1.5 ftok1.6 共享内存和内存映射的关联1.7 小demo 二、共享内存操作命令 一、共享内存 共享内存允许两个或者多个进程共享物理内存的同一块区域&#xff08;通常被称为段&#xff09;。由于一个共享内存段会称…

电力场效应晶体管(电力 MOSFET),全控型器件

电力场效应晶体管&#xff08;Power MOSFET&#xff09;属于全控型器件是一种电压触发的电力电子器件&#xff0c;一种载流子导电&#xff08;单极性器件&#xff09;一个器件是由一个个小的mosfet组成以下是相关介绍&#xff1a; 工作原理&#xff08;栅极电压控制漏极电流&a…

Spring Boot整合JavaMail实现邮件发送

一. 发送邮件原理 发件人【设置授权码】 - SMTP协议【Simple Mail TransferProtocol - 是一种提供可靠且有效的电子邮件传输的协议】 - 收件人 二. 获取授权码 开通POP3/SMTP&#xff0c;获取授权码 授权码是QQ邮箱推出的&#xff0c;用于登录第三方客户端的专用密码。适用…

PHP防伪溯源一体化管理系统小程序

&#x1f50d; 防伪溯源一体化管理系统&#xff0c;品质之光&#xff0c;根源之锁 &#x1f680; 引领防伪技术革命&#xff0c;重塑品牌信任基石 我们自豪地站在防伪技术的前沿&#xff0c;为您呈现基于ThinkPHP和Uniapp精心锻造的多平台&#xff08;微信小程序、H5网页&…

飞牛 fnOS 安装8852be网卡驱动并成功连接

飞牛fnos安装8852be网卡驱动 本人使用的是迷你主机 由于debian内核不识别8852be的网卡&#xff0c;所以需要自行安装网卡驱动 为此搜索了一堆教程 最后折腾过程以及代码如下&#xff0c;建议看完一遍再食用 fnos版本&#xff1a;0.8.36 debian内核版本&#xff1a;6.6.38-tri…

Linux通过docker部署京东矩阵容器服务

获取激活码 将京东无线宝app升级到最新版,然后打开首页,点击号 选择添加容器矩阵,然后获取激活码 运行容器 read -p "请输入你的激活码: " ACTIVECODE;read -p "请输入宿主机的缓存路径: " src;docker rm -f cmatrix;docker run -d -it --name cmatrix …

SQL基础、函数、约束(MySQL第二期)

p.s.这是萌新自己自学总结的笔记&#xff0c;如果想学习得更透彻的话还是请去看大佬的讲解 目录 SQL通用语法SQL数据类型SQL语句分类DDL数据库操作表操作-查询&创建典例表操作-修改字段表操作-改名&删除 DMLDML-插入(添加)数据DML-更新(修改)数据DML-删除数据 DQL基本…

速通 AI+Web3 开发技能: 免费课程+前沿洞察

AI 正以前所未有的速度重塑各行各业&#xff0c;从生成式模型到大规模数据处理&#xff0c;AI 逐渐成为核心驱动力。与此同时&#xff0c;Web3 去中心化技术也在重新定义信任、交易和协作方式。当这两大前沿技术相遇&#xff0c;AI Web3 的融合已不再是理论&#xff0c;而是未…

国产编辑器EverEdit - 输出窗口

1 输出窗口 1.1 应用场景 输出窗口可以显示用户执行某些操作的结果&#xff0c;主要包括&#xff1a; 查找类&#xff1a;查找全部&#xff0c;筛选等待操作&#xff0c;可以把查找结果打印到输出窗口中&#xff1b; 程序类&#xff1a;在执行外部程序时(如&#xff1a;命令窗…