首页 > 其他分享 >自旋锁(Spinlock)和互斥锁(Mutex)的区别

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

时间:2024-09-18 10:04:36浏览次数:11  
标签:中断 interrupts 互斥 死锁 线程 自旋 Mutex Spinlock

自旋锁(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 都使用了中断安全的自旋锁来保护临界区,确保不会在中断过程中发生死锁。

标签:中断,interrupts,互斥,死锁,线程,自旋,Mutex,Spinlock
From: https://www.cnblogs.com/lihaoxiang/p/18417980

相关文章

  • 【C#Mutex】 initiallyOwned错误引起的缺陷
    临界区只能对同一个进程的不同线程同步,互斥量可以跨进程同步。典型应用场景:两个exe会操作同一个注册表项。错误代码封装类publicclassCMutexHelp:IDisposable{publicCMutexHelp(){s_mutex.WaitOne();}privatestaticMutexs_mu......
  • C#笔记13 线程同步概念及其实现,详解lock,Monitor,Mutex代码用法
    同步的概念在我们学会在C#中使用线程之后,我们拥有了把一个程序中的不同代码段在不同线程中运行的能力,可以说此时我们已经能够做到让他们分别执行,异步执行。对于我们的桌面端程序,使用多线程可以让我们在后台进行操作的时候保持用户界面的响应。对于服务器应用程序,多线程可以......
  • dotnet 测试 Mutex 的 WaitOne 是否保持进入等待的顺序先进先出
    本文记录我测试dotnet里面的Mutex锁,在多线程进入WaitOne等待时,进行释放锁时,获取锁执行权限的顺序是否与进入WaitOne等待的顺序相同。测试的结果是Mutex的WaitOne是乱序的,不应该依赖Mutex的WaitOne做排队顺序以下是测试程序代码vartaskList=newList<Task>();......
  • 最简单C++线程和互斥锁使用示例
    std::thread是C++11标准库中引入的一个类,用于表示一个独立的执行线程。而std::mutex是C++11中提供的一种互斥锁,用于在多个线程间同步对共享数据的访问,以避免数据竞争和条件竞争。下面将分别介绍std::thread和std::mutex的基本使用,并通过一个示例展示它们的结合使用......
  • 【Linux修行路】多线程——互斥量
    目录⛳️推荐一、多线程模拟抢票二、加锁——互斥量2.1pthread_mutex_init——初始化互斥量2.2pthread_mutext_destroy——销毁一个互斥量2.3pthread_mutex_lock——加锁2.4pthread_mutex_trylock——非阻塞的申请锁2.4pthread_mutex_unlock——解锁2.5定义一个......
  • Springboot实战——黑马点评之互斥锁
    Springboot黑马点评(3)——优惠券秒杀【还剩Redisson的最后两节没测试后续补上】另外,后期单独整理一份关于分布式锁笔记1优惠券秒杀实现1.1用户-优惠券订单设计1.1.1全局ID生成器使用数据库自增ID作为订单ID存在问题1.1.2考虑全局唯一ID生成逻辑时间戳(Long类型......
  • 线程(函数接口、同步、互斥、条件变量)
    线程Thread1.什么是线程1.1概念线程是一个轻量级的进程,为了提高系统的性能引入线程。线程和进程是参与统一的调度。在同一个进程中可以创建的多个线程,共享进程资源。(Linux里同样用task_struct来描述一个线程)1.2进程和线程区别相同点:都为系统提供了并发执行的......
  • io 互斥
    概念互斥:多个线程在访问临界资源时,同一时间只能一个线程访问临界资源:一次仅允许一个线所使用的资源临界区:指的是一个访问共享资源的程序片段互斥锁(mutex):通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临......
  • 【计算机组成原理】五、中央处理器:3.指令流水线(互斥、同步)
    5.指令流水线文章目录5.指令流水线5.1基本概念5.2性能指标5.3指令流水线影响因素5.3.1结构相关(**资源冲突**):**互斥**5.3.2==数据相关(**数据冲突**)==:**同步**5.3.3控制相关(**控制冲突**)5.4流水线分类5.5流水线的多发技术5.6==五段式流水线==对指令执行的优化5.......
  • C++学习随笔——lock_guard和mutex的使用方法
    std::mutex和std::lock_guard是C++标准库中用于多线程同步的工具,主要用于防止多个线程同时访问共享资源,导致数据竞争问题。 std::mutex是一个用于互斥锁的类,提供了锁定(lock)和解锁(unlock)的功能。使用方法:#include<iostream>#include<thread>#include<mutex>std::......