互斥锁和自旋锁的区别(转)

本文对比了自旋锁与互斥锁的特点及适用场景。自旋锁适合短期锁定,避免线程睡眠,但在无法快速获取锁时可能降低CPU效率;互斥锁适合长期锁定,线程在未获取锁时进入睡眠状态。文章还讨论了两种锁的加锁原理及其在不同处理器架构下的应用。

转自https://blog.youkuaiyun.com/susidian/article/details/51068858

自旋锁(Spin lock)

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远 高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:
    1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。
    2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。

因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。

 

两种锁的加锁原理

互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销

自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

 

互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而自旋锁则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

两种锁的区别

互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。

两种锁的应用

互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

1 临界区有IO操作

2 临界区代码复杂或者循环量大

3 临界区竞争非常激烈

4 单核处理器

至于自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的服务器。

转载于:https://www.cnblogs.com/wangshaowei/p/8921389.html

### 互斥锁自旋锁区别 #### 工作原理 互斥锁是一种基于操作系统的同步机制,用于保护共享资源。当一个线程尝试获取互斥锁时,如果锁已经被其他线程持有,该线程会进入阻塞状态,等待锁被释放。一旦锁被释放,操作系统会唤醒等待的线程并允许它获取锁[^2]。 自旋锁的工作原理则不同。当一个线程尝试获取自旋锁而锁已被其他线程持有时,线程不会进入阻塞状态,而是在一个循环中不断地检查锁是否被释放。这种忙等待的方式意味着线程在等待期间仍然占用CPU资源。 #### 实现方式 互斥锁属于**sleep-waiting**类型的锁。线程在等待锁时会被阻塞,并释放CPU资源,直到锁被释放后由操作系统内核唤醒。这种方式减少了CPU的浪费,但存在线程上下文切换的开销[^3]。 自旋锁属于**busy-waiting**类型的锁。线程在等待锁时不会释放CPU资源,而是持续在CPU上忙等待,并反复检查锁的状态。如果锁很快被释放,自旋锁的效率较高;但如果锁被长时间持有,则会导致CPU资源的浪费[^3]。 #### 开销 互斥锁由于线程在等待锁时会被阻塞唤醒,因此涉及到线程的上下文切换,这会带来一定的性能开销。然而,这种方式可以有效地减少CPU资源的浪费,尤其是在锁被长时间持有的情况下[^4]。 自旋锁在获取锁时不会引起线程的阻塞切换,因此在低竞争短期占用临界资源的情况下,其开销可能更低。然而,如果锁被长时间持有,线程在自旋过程中会持续占用CPU资源,导致CPU资源的浪费[^4]。 #### 适用场景 互斥锁适用于临界区资源访问时间较长或存在阻塞操作的情况。因为互斥锁允许线程在等待锁时被阻塞,从而释放CPU资源,避免CPU资源的浪费[^3]。 自旋锁适用于临界区资源访问时间短且线程竞争不激烈的情况。由于自旋锁在等待锁时不会释放CPU资源,如果能在短时间内获得锁,则效率较高[^3]。 #### 底层实现 互斥锁依赖于操作系统提供的原语或系统调用实现,通常涉及内核态的调度机制等待队列。线程在等待锁时会被放入阻塞队列中,并在锁释放时被唤醒,这会引起线程上下文切换的开销[^4]。 自旋锁依赖于硬件提供的原子操作(如CAS指令)实现。自旋锁不会涉及线程的阻塞唤醒,因此不会引起线程上下文切换,但在等待锁期间线程会持续占用CPU资源[^4]。 #### 优先级反 在优先级反问题上,互斥锁可以通过优先级继承机制来缓解。当高优先级线程等待低优先级线程持有的锁时,低优先级线程的优先级会被临时提升,以减少高优先级线程的等待时间[^5]。 自旋锁在优先级反问题上可能会出现问题,因为它无法通过优先级继承机制来缓解。为了解决这个问题,通常需要配合禁用抢占或中断等机制[^5]。 ### 示例代码 以下是一个简单的C++示例,展示了如何使用互斥锁保护共享资源: ```cpp #include <iostream> #include <thread> #include <mutex> std::mutex mtx; // 定义一个互斥锁 int count = 0; // 被保护的共享资源 void safe_increment() { mtx.lock(); // 上锁 ++count; std::cout << "Count is now " << count << std::endl; mtx.unlock(); // 解锁 } int main() { std::thread t1(safe_increment); std::thread t2(safe_increment); t1.join(); t2.join(); return 0; } ``` 以下是一个简单的C++示例,展示了如何使用自旋锁保护共享资源: ```cpp #include <atomic> #include <thread> #include <iostream> std::atomic<bool> lock(false); // 定义一个自旋锁 void spinlock_lock() { while (lock.exchange(true, std::memory_order_acquire)) { // 自旋等待 } } void spinlock_unlock() { lock.store(false, std::memory_order_release); } int main() { int count = 0; auto increment = [&]() { spinlock_lock(); ++count; std::cout << "Count is now " << count << std::endl; spinlock_unlock(); }; std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); return 0; } ``` ### 总结 互斥锁自旋锁都是用于实现多线程同步的机制,确保临界资源的互斥访问。它们都可以通过原子操作来保证线程安全。互斥锁适用于长期占用临界资源的情况,而自旋锁适用于短期占用临界资源的情况。互斥锁在获取锁时会导致线程阻塞唤醒,而自旋锁会在循环中等待,期间线程会一直占用CPU资源,但不会进入阻塞状态。由于互斥锁涉及到线程的阻塞唤醒,它的开销相对较高。在高并发场景下,频繁的线程切换会导致性能下降。自旋锁在获取锁时不会引起线程的阻塞切换,因此在低竞争短期占用临界资源的情况下,自旋锁的开销可能更低[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值