自旋锁与互斥锁

自旋锁(spinlock)与互斥锁(mutex)是并发编程中两个重要的概念。它们的主要作用是:对共享资源加锁以阻止数据的并发访问,从而保证数据一致性。但是它们也有一些不同点。本文主要介绍这些不同点,并说明我们什么时候该用自旋锁,什么时候该用互斥锁。

理论基础

理论上,当一个线程尝试去获取一个互斥锁,但由于该互斥锁已经被其它线程获取而没有成功时,它会立刻进入休眠状态,从而让出CPU时间,允许其它线程运行。它将持续休眠直到最终被唤醒,唤醒的条件是之前获取到该互斥锁的线程释放了互斥锁;

对比一下,当一个线程尝试去获取一个自旋锁,但由于该自旋锁已经被其它线程获取而没有成功时, 它将会反复获取它(在英文中叫polling),直到最终成功。因此在获取自旋锁的时候,该线程不会让出CPU时间,其它线程将不能运行,当然,操作系统不会允许一个线程一直阻塞整个系统的运行,在某个线程花完了它的CPU运行总时间后,它会强制切换到另外线程执行。(注:线程一次使用的CPU总时间的最大上限可以通过ulimit -t查看,单位为秒)

问题

对于互斥锁,使线程进入休眠以及唤醒线程都是比较昂贵的操作,需要相当多的CPU指令,花费较长的CPU时间。如果A线程获取到该互斥锁后,只是持有了很短的一段时间就释放,那么B线程在获取互斥锁的过程中,B线程进入休眠以及被唤醒花费的CPU时间可能超过了B线程休眠的时间,甚至可能超过了采用的自旋锁时polling的时间;

另一方面,对于自旋锁,反复获取自旋锁将会花费很多CPU时间,如果A线程持有该锁的时间过长,那么尝试获取该锁的B线程将会占用很多CPU时间,在这种情况下,让B线程进入休眠反而会更好些。

解决方案

不要在单CPU单核的系统上使用自旋锁,因为当A线程获取自旋锁polling的时候,可能会阻塞仅有的CPU导致其它所有的线程都不能运行,而其它线程不能运行意味着需要释放该自旋锁的B线程也得不到CPU时间不能运行,那么该自旋锁将不会被释放。也就是说,自旋锁浪费了仅有的系统CPU时间而没有获得任何好处(能获取到锁的条件是polling耗费掉其CPU总时间后,系统切换到B线程,B线程释放锁后,A线程再次执行时才获得锁)。如果A线程在不能获取到锁的时候就立刻进入休眠,B线程可能就可以立刻执行,有可能B线程会立刻释放掉锁,在A线程被唤醒后,就可以继续执行。

在多CPU多核的系统上,如果大量的锁被持有的时间都很短,那么浪费在不断使线程进入休眠和唤醒线程的时间将会很多,有可能显著的降低系统性能。当采用自旋锁的时候,线程可以有机会利用它们可以使用的CPU时间片,在短暂的阻塞后立刻进行后续的工作,从而最大限度的提高CPU的使用率。

实践策略

因为在多数情况下程序员不知道到底是使用自旋锁还是互斥锁更好(因为不能提前知道程序运行的系统CPU核数),操作系统也不可能知道某段代码被优化为运行在单核或多核环境下,大多数操作系统也并不能严格区分互斥锁还是自旋锁。事实上,多数现代操作系统均采用混合的互斥锁和混合的自旋锁,这是为什么呢?

混合的互斥锁的行为在开始时类似于在多核系统上的自旋锁:如果A线程不能获取锁,它不会被立刻休眠,因为该互斥锁可能会立刻被释放,所以互斥锁开始时表现会类似于自旋锁。只有在A线程等待了一段足够长的时间后还没有获得该互斥锁时才会被休眠。如果同样的程序运行在一个单核系统上,那么互斥锁将不会自旋,因为这没有任何好处。

混合的自旋锁的行为在开始时和一般的自旋锁一样,但是为了避免浪费过多的CPU时间,它可能会采用一个折中的策略:系统不会让线程进入休眠(因为使用自旋锁时你肯定不希望休眠发生),但是会决定什么时候停止线程让其他线程运行(要么立刻,要么在一段固定时间后),因此增加了自旋锁解锁的机率。(单纯的线程切换通常没有使线程进入休眠再唤醒线程代价那么昂贵)

总结

如果不确定,那么使用互斥锁,这通常是更好的选择。在能够获得益处的情形下,大多数的现代操作系统将允许互斥锁自旋(polling)一段时间。采用自旋锁在某些时候会提高性能,但是只是在某些特定的条件下。事实上,你是在怀疑而不是告诉我,你目前没有任何项目采用自旋锁可能获益。你可能会采用你自己定义的“锁对象”,它在内部要么使用自旋锁,要么使用互斥锁(例如,使用何种类型的锁在创建时是可配置的),初始时,在所有的地方都使用互斥锁,如果在某些地方使用自旋锁可能会有帮助,那么请试一试,并比较二者的结果,在得出结论前,一定要确保比较的时候要测试了单核和多核系统,如果你的程序是跨平台的,也请测试各种操作系统。

原文:http://www.pixelstech.net/article/1397962421-Practice-of-using-spinlock-instead-of-mutex

自旋锁与互斥锁

时间: 2024-10-27 10:32:31

自旋锁与互斥锁的相关文章

自旋锁代替互斥锁的实践

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

自旋锁和互斥锁的区别

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

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

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