自旋锁(Spinlock)和互斥锁(Mutex)的区别

news/2024/9/19 17:32:21/文章来源:https://www.cnblogs.com/lihaoxiang/p/18417980

自旋锁(Spinlock)和互斥锁(Mutex)的区别

自旋锁(Spinlock)互斥锁(Mutex)都是用于多线程或多进程环境中同步共享资源的机制,但它们的工作方式和使用场景存在显著的不同。

1. 自旋锁(Spinlock)

  • 原理:当一个线程试图获取自旋锁时,如果锁已经被其他线程占有,它会一直循环检查(自旋)锁的状态,直到锁被释放。线程在自旋过程中不会被挂起,而是持续占用 CPU 资源进行忙等待。

  • 适用场景

    • 自旋锁适合用于短临界区,即锁的持有时间非常短的情况下,避免线程在等待期间发生上下文切换的开销。
    • 通常用于内核中断上下文实时要求非常高的场景,因为自旋锁不会引起调度器的干预。
  • 优点

    • 自旋锁的实现非常简单,开销低,在锁持有时间很短的情况下,自旋锁避免了线程被挂起和唤醒的调度开销。
  • 缺点

    • 自旋锁在持有锁的时间较长时效率低,因为它会一直消耗 CPU 资源进行忙等待。
    • 不能在发生上下文切换的场景中使用,比如不能让持有自旋锁的线程进行睡眠

2. 互斥锁(Mutex)

  • 原理:互斥锁使用阻塞机制。如果一个线程试图获取互斥锁时发现锁已经被其他线程持有,它会被挂起,并放入等待队列中,等待锁释放时被唤醒。此时,线程不占用 CPU 资源。

  • 适用场景

    • 互斥锁适用于锁持有时间较长的临界区,因为挂起和唤醒线程的开销相比自旋锁的忙等待开销更低。
    • 适合用于应用程序中的线程同步,尤其是那些涉及 I/O 操作或长时间计算的临界区。
  • 优点

    • 互斥锁在长时间持有锁的情况下效率高,因为线程在等待时被挂起,不占用 CPU。
  • 缺点

    • 互斥锁的上下文切换开销较高,获取和释放锁需要操作系统调度器的参与,适合锁持有时间较长的场景。

3. 区别总结

特性 自旋锁(Spinlock) 互斥锁(Mutex)
等待方式 忙等待(自旋) 阻塞(线程挂起,等待唤醒)
CPU 使用效率 在锁持有时间短时效率高,长时间等待会浪费 CPU 等待时不占用 CPU,适合长时间持有锁的情况
适用场景 短临界区,内核中断上下文,实时性要求高的场景 长临界区,用户态多线程或多进程环境
上下文切换 无上下文切换,不支持线程睡眠 可能导致上下文切换,支持睡眠
系统开销 无调度器开销,适合短时间临界区 可能涉及调度器的参与,开销较高

在中断中使用自旋锁如何避免死锁

在中断处理程序中使用自旋锁时,可能会遇到死锁问题。如果处理不当,持有自旋锁的线程被中断服务例程(ISR)再次尝试获取相同的自旋锁,导致死锁情况。以下是避免在中断中使用自旋锁导致死锁的策略:

1. 中断上下文下的自旋锁死锁问题

假设线程 A 正在持有自旋锁,并且此时线程 A 的执行被硬件中断打断。此时中断处理程序(ISR)也试图获取相同的自旋锁,由于自旋锁已经被线程 A 持有,而线程 A 此时处于等待中断处理完成的状态,因此中断处理程序无法获取锁,只能自旋等待。而线程 A 由于处于中断处理的等待状态,无法继续执行,这样就产生了死锁。

2. 解决方案:禁用中断

为了避免上述死锁问题,禁用中断是一个常见的解决方案。这样,当某个线程获取了自旋锁后,不会在持有自旋锁的过程中被中断打断,中断处理程序就不会尝试获取相同的自旋锁,从而避免死锁。

在自旋锁的实现中,有一个特殊版本,称为中断安全的自旋锁(Spinlock with Interrupt Disable)。它在获取自旋锁时会禁用中断,确保在持有锁期间不会发生中断。

2.1 禁用中断获取自旋锁

以下是如何在中断安全的环境中使用自旋锁的伪代码:

void acquire_spinlock_with_interrupts_disabled(spinlock_t* lock) {disable_interrupts();  // 禁用中断while (test_and_set(lock)) {// 自旋等待}
}void release_spinlock_with_interrupts_enabled(spinlock_t* lock) {*lock = 0;             // 释放锁enable_interrupts();    // 恢复中断
}
  • 禁用中断:在获取自旋锁之前禁用中断,确保中断处理程序不会在自旋锁持有期间试图获取相同的锁。
  • 恢复中断:在释放自旋锁之后,重新启用中断。

通过这种方式,线程在持有锁的期间不会被中断打断,也就避免了死锁的发生。

2.2 使用递归中断屏蔽计数

有时我们在多层调用中禁用中断,可能需要防止错误地启用过早的中断恢复。我们可以使用一个递归计数器来跟踪中断的禁用层次,确保只有在最外层的调用释放锁后,才真正恢复中断。

int interrupt_disable_counter = 0;void disable_interrupts() {if (interrupt_disable_counter == 0) {// 禁用中断}interrupt_disable_counter++;
}void enable_interrupts() {interrupt_disable_counter--;if (interrupt_disable_counter == 0) {// 启用中断}
}

通过这种方式,可以避免多层嵌套的函数调用中错误恢复中断的情况。

3. 自旋锁使用注意事项

  • 避免长时间持有自旋锁:自旋锁不应该持有太长时间,因为它会导致 CPU 资源的浪费。长时间的锁定应该使用互斥锁而不是自旋锁。

  • 在适当的上下文使用:自旋锁不能与那些可能导致睡眠的操作混合使用。例如,在内核态下,持有自旋锁时不要调用可能会阻塞或休眠的函数。

  • 适合 SMP 环境:自旋锁在单处理器系统(SMP)中没有太多意义,因为在单处理器上自旋锁的忙等待会浪费 CPU 时间,而无法给其他线程机会。因此,自旋锁通常用于多处理器系统中。

4. 总结

  • 自旋锁适合用于短临界区,特别是涉及硬件中断或多处理器系统的场景,但自旋锁在持有锁时会导致忙等待。
  • 在中断处理程序中使用自旋锁时,需要注意死锁问题。通过在获取自旋锁时禁用中断,可以避免中断上下文重新获取同一自旋锁导致的死锁。
  • 如果临界区较长或者存在可能的阻塞情况,互斥锁可能是更好的选择,因为它可以阻塞线程而不是让线程自旋等待。

参考代码示例

spinlock_t lock;void interrupt_handler() {acquire_spinlock_with_interrupts_disabled(&lock);// 临界区:处理中断相关操作release_spinlock_with_interrupts_enabled(&lock);
}void task() {acquire_spinlock_with_interrupts_disabled(&lock);// 临界区:执行任务release_spinlock_with_interrupts_enabled(&lock);
}

在这个例子中,interrupt_handlertask 都使用了中断安全的自旋锁来保护临界区,确保不会在中断过程中发生死锁。

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

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

相关文章

(装)K8s 总体架构及目录结构

原文:https://www.guoshaohe.com/cloud-computing/kubernetes-source-read/12491. kubernetes 总体架构目前想对kubernetes 1.21 的源码进行一次阅读分析,本篇文章是对k8s的整体架构进行一个大致分析,先要搞清楚每个组件的功能和任务,在根据不同的组件进行深度的源码分析。…

[C++ Daily] calloc 与 malloc

转载 C++ 面试突破 - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台 Kind BorglQI的评论

贼好用!五分钟搭建一个美观且易用的导航页面!

Pintree 是一个开源项目,旨在将浏览器书签导出成导航网站。通过简单的几步操作,就可以将书签转换成一个美观且易用的导航页面。大家好,我是 Java陈序员。 今天,给大家介绍一个贼好用的导航网站搭建工具,只需通过几步操作,就能搭建出个性化导航网站!关注微信公众号:【Ja…

26.删除有序数组中的重复项 Golang实现

题目描述:给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以…

Hadoop(十一)HDFS 读写数据流程

HDFS读写数据流程 一、写数据流程1、客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在 2、NameNode返回是否可以上传 3、客户端请求第一个Block上传到哪几个DataNode服务器上 4、NameNode返回3个DataNode节点,分…

产品经理的日常工作

杜工库 声明 欢迎转载,但请保留文章原始出处:) 博客园:https://www.cnblogs.com/chenxiaomeng/ 如出现转载未声明 将追究法律责任~谢谢合作

Hadoop(十一)HDFS 写数据流程

HDFS写数据流程 一、写数据流程1、客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在 2、NameNode返回是否可以上传 3、客户端请求第一个Block上传到哪几个DataNode服务器上 4、NameNode返回3个DataNode节点,分别…

windows定时执行python程序

先写个 bat 脚本,执行 Python 程序 :: 根据实际情况修改 Python 解释器的路径 set PYTHON=D:\python\python.exe set CODE_DIR=D:\code\projectcd /d "%CODE_DIR%" :: 启动 Python 程序 echo Running main.py... %PYTHON% main.py > nohup.log 2>&1echo A…

Prometheus修改数据存储位置

Prometheus修改数据存储位置 Prometheus的数据存储位置可以通过配置文件中的 --storage.tsdb.path 参数来指定。默认情况下,数据存储在Prometheus安装目录下的 data 文件夹中。要修改数据存储位置,可以在Prometheus启动命令中添加或修改该参数。 步骤 1:修改 Prometheus 启动…

AIoTengine智能物联引擎

新一代数智基础设施,依托AIoT2.0技术,实现感知、认知、决策和执行的一体化融合,打造云边协同的分布式智能物联引擎,驱动行业数智化革新。

WPF打包独立运行的程序

方案一:使用Costura.Fody插件将自己写的程序打包成一个可以独立运行的EXE文件 第1步:安装Costura.Fody 首先用Visual Studio 2017打开你的解决方案,依次找到“工具”---“NuGet包管理” - “管理解决方案的NuGet程序包”,到了这一步会打开NuGet-解决方案页面,在浏览选项下面…

物联网平台推荐:ThingsKit

ThingsKit物联网平台 随着物联网技术的不断进步,企业和开发者对于一个可靠、高效且易于使用的物联网平台的需求日益增长。在这样的背景下,ThingsKit物联网平台以其卓越的性能和丰富的功能,成为了市场上的一股清流。 一、ThingsKit物联网平台概述 ThingsKit是一个功能强大、易…