自旋锁与互斥锁之抉择

自旋锁和互斥锁是多线程编程中的两个重要概念。他们都能用来锁定一些共享资源,以阻止影响数据一致性的并发访问。但是他们之间确实存在区别,那么这些区别是什么?

1    理论

理论上,当一个线程试图获取一个被锁定的互斥锁时,该操作会失败然后该线程会进入睡眠,这样就能马上让另一个线程运行。当持有互斥锁的线程释放该锁之后,进入睡眠状态的线程就会被唤醒。但是,当一个线程试图获取一个自旋锁而没有成功时,该线程会不断地重试,直到最终成功为止;因此该线程不会将运行权交到其他线程手中(当然,一旦当前线程的时间片超时,操作系统会强行切换到另一个线程)。

2    问题

互斥锁的问题在于:让线程睡眠和唤醒线程都是极为耗时的操作,完成这些操作需要大量CPU指令,因此也就需要耗费不少时间。如果只是锁定互斥锁很短一段时间,那么让线程睡眠和唤醒线程所花的时间可能会超过线程实际上睡眠的时间,甚至有可能会超过线程在自旋锁上轮询锁浪费的时间(如果使用自旋锁)。另一方面,在自旋锁上进行轮询会浪费CPU时间,如果自旋锁被锁定较长的时间,可能会浪费大量的CPU时间,这时让线程睡眠可能是一个更好的选择。

3    解决方法

在一个单核系统中使用自旋锁是行不通的,因为只要自旋锁轮询在阻塞当前CPU,那么就没有其他线程能够运行,既然没有其他线程能够运行,那么该锁也就不会被唤醒,对,我们进入死锁了。最好情况下,自旋锁仅仅浪费那些对系统没有任何用处的CPU时间。相反,如果使用互斥锁,线程A进入睡眠,那么另外一个线程B就能够立即运行,线程B有可能会释放锁,唤醒线程A,使线程A继续运行。

在一个多核系统,如果大量的锁只持有很短一段时间,那么让线程睡眠和唤醒线程所浪费的时间有可能会极大地降低运行时性能。相反,如果使用自旋锁,线程就有机会利用完全时间片(总是阻塞很短一段时间,然后立即运行),获得更高的吞吐量。

4    实践

由于大部分情况下,程序员不能预先知道使用互斥锁好还是使用自旋锁好(例如:因为不知道目标系统的CPU核心数量),同时操作系统也不知道某个片段的代码是否已经为单核或多核环境优化过,因此大部分系统不严格区分这两种锁。实际上,大部分现代操作系统都提供混合互斥锁和混合自旋锁。那么,什么是混合互斥锁和混合自旋锁?

在一个多核系统,混合互斥锁开始时会表现得像自旋锁。即如果一个线程A不能获取到互斥锁,那么线程A不会立即进入睡眠状态,因为该锁可能马上就被释放了,因此该互斥锁开始表现得像自旋锁。只有当一段固定的时间后,线程A还不能获取到该互斥锁,线程A才会进入睡眠状态。如果相同的程序运行在单核系统下,该互斥锁就不会表现出自旋锁的行为。

一个混合自旋锁开始时会表现得像一个普通的自旋锁,但为了避免浪费CPU时间,它提供了一个back-off策略。通常,混合自旋锁不会使线程进入睡眠状态(因为当你使用自旋锁时,你不希望发生这种情况),但是它能停止某个线程(立即或者一段固定的时间后),然后让另一个线程运行,以提高自旋锁的闲置率(一个纯粹的线程切换通常比使线程进入睡眠然后唤醒它效率更高,起码目前如此)。

5    总结

如果你不知道该使用哪一个,那么使用互斥锁,因为大部分现代操作系统都允许他们先自旋一小段时间(提前是该自旋有益), 所以互斥锁通常是更好的选择。有时,使用自旋锁会提升性能,在某些特定情况下,你可能会觉得使用自旋锁更好。这时候,使用你自己的锁对象,该锁对象内部使用自旋锁或者互斥锁实现(这个行为可以通过配置修改),开始时全部使用互斥锁,之后,如果你觉得某个地方使用自旋锁更好,那么修改它,然后比较下结果,但是在下结论之前,一定要记得在单核和多核环境下进行测试。

时间: 2024-10-29 09:19:15

自旋锁与互斥锁之抉择的相关文章

自旋锁与互斥锁

自旋锁(spinlock)与互斥锁(mutex)是并发编程中两个重要的概念.它们的主要作用是:对共享资源加锁以阻止数据的并发访问,从而保证数据一致性.但是它们也有一些不同点.本文主要介绍这些不同点,并说明我们什么时候该用自旋锁,什么时候该用互斥锁. 理论基础 理论上,当一个线程尝试去获取一个互斥锁,但由于该互斥锁已经被其它线程获取而没有成功时,它会立刻进入休眠状态,从而让出CPU时间,允许其它线程运行.它将持续休眠直到最终被唤醒,唤醒的条件是之前获取到该互斥锁的线程释放了互斥锁: 对比一下,当一

自旋锁代替互斥锁的实践

原文地址 译文地址 译者:小鱼儿 校对:梁海舰 自旋锁和互斥锁是多线程程序中的重要概念. 它们被用来锁住一些共享资源, 以防止并发访问这些共享数据时可能导致的数据不一致问题. 但是它们的不同之处在哪里? 我们应该在什么时候用自旋锁代替互斥锁? 理论分析 从理论上说, 如果一个线程尝试加锁一个互斥锁的时候没有成功, 因为互斥锁已经被锁住了, 这个未获取锁的线程会休眠以使得其它线程可以马上运行. 这个线程会一直休眠, 直到持有锁的线程释放了互斥锁, 休眠的线程才会被唤醒. 如果一个线程尝试获得一个自

自旋锁和互斥锁的区别

POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套API.线程同步是并行编程中非常重要的通讯手段,其中最典型的应用就是用 Pthreads提供的锁机制(lock)来对多个线程之间的共享临界区(Critical Section)进行保护(另一种常用的同步机制是barrier). Pthreads提供了多种锁机制: Mutex(互斥量):pthread_mutex_t Spin lock(自旋锁): pthread_spin_t Condition Variable(

Linux 同步方法剖析--内核原子,自旋锁和互斥锁

在学习 Linux® 的过程中,您也许接触过并发(concurrency).临界段(critical section)和锁定,但是如何在内核中使用这些概念呢?本文讨论了 2.6 版内核中可用的锁定机制,包括原子运算符(atomic operator).自旋锁(spinlock).读/写锁(reader/writer lock)和内核信号量(kernel semaphore). 本文还探讨了每种机制最适合应用到哪些地方,以构建安全高效的内核代码. 本文讨论了 Linux 内核中可用的大量同步或锁定

阻塞锁,非阻塞锁,自旋锁,互斥锁

1.阻塞锁 多个线程同时调用同一个方法的时候,所有线程都被排队处理了.让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态. public class Lock{ private boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ //当其他线程进来,即处

Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等(转)

Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁 / 非公平锁 可重入锁 / 不可重入锁 独享锁 / 共享锁 互斥锁 / 读写锁 乐观锁 / 悲观锁 分段锁 偏向锁 / 轻量级锁 / 重量级锁 自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释. 公平锁 / 非公平锁 公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公

C# lock 语法糖实现原理--《.NET Core 底层入门》之自旋锁,互斥锁,混合锁,读写锁

原文:C# lock 语法糖实现原理--<.NET Core 底层入门>之自旋锁,互斥锁,混合锁,读写锁 在多线程环境中,多个线程可能会同时访问同一个资源,为了避免访问发生冲突,可以根据访问的复杂程度采取不同的措施 原子操作适用于简单的单个操作,无锁算法适用于相对简单的一连串操作,而线程锁适用于复杂的一连串操作 原子操作 修改状态要么成功且状态改变,要么失败且状态不变,并且外部只能观察到修改前或者修改后的状态,修改中途的状态不能被观察到 .NET 中,System.Threading.Inte

synchronized与lock 对象锁、互斥锁、共享锁以及公平锁和非公平锁

synchronized与lock  都是用来实现线程同步的锁,synchronized对象锁,lock是一个接口,她的实现有reentrantlock互斥锁以及ReentrantReadWriteLock共享锁. 这里说明一下ReentrantReadWriteLock共享锁,所谓共享就是该锁提供读读锁共享,即可以多个线程共享一个读取锁,但是读写锁以及读读锁是互斥的. 看到网上有好多介绍介绍锁的种类的,有对象锁.互斥锁.共享锁以及公平锁和非公平锁,但是说明都不够详细了然,在这里用直白的说法记录

Java04 线程同步问题解决——线程锁(同步锁、互斥锁)

目录 [TOC] 写在最前: 可能有误,请大家批评指正 一.线程切换 Java中,如果要实现在一个线程间的线程切换,需要在线程中使用Thread.yield()即可让出CPU时间. 二.线程锁(也叫同步锁.互斥锁) 线程锁可以在有效缩小同步范围的同时,尽可能的保证并发效率 2.1 使用synchronized关键字对方法进行加锁 对整个线程处理加锁(严重影响效率,不常用) 2.1.1 语法 public synchronized void test(){ } 2.1.2 案例 package c