Linux中的spinlock和mutex

Linux中的spinlock和mutex

作者

digoal

日期

2016-11-09

标签

PostgreSQL , Linux , spin lock , mutex , 自旋锁 , 抢占锁


背景

最近在压测PostgreSQL同步流复制时,遇到一个mutex锁的瓶颈问题。

具体见 《PostgreSQL 同步流复制锁瓶颈分析》

PG是以backend process睡眠,然后通过sender唤醒的方式来处理同步等待的问题。

转一篇文章,了解一下spinlock, mutex。

http://www.aichengxu.com/view/2456962

spinlock (自旋锁)

自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁)。

自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被争用(已经被持有)的自旋锁,那么这个任务就会一直进行忙循环——旋转——等待锁重新可用。

要是锁未被争用,请求它的内核任务便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。

事实上,自旋锁的初衷就是:

在短期间内进行轻量级的锁定。一个进程去获取被争用的自旋锁时,请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长(等待时CPU被独占)。如果需要长时间锁定的话, 最好使用信号量(睡眠,CPU资源可出让)。

简单的说,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。另外自旋锁不允许任务睡眠(持有自旋锁的任务睡眠会造成自死锁——因为睡眠有可能造成持有锁的内核任务被重新调度,而再次申请自己已持有的锁),它能够在中断上下文中使用。

死锁:假设有一个或多个内核任务和一个或多个资源,每个内核都在等待其中的一个资源,但所有的资源都已经被占用了。这便会发生所有内核任务都在相互等待,但它们永远不会释放已经占有的资源,于是任何内核任务都无法获得所需要的资源,无法继续运行,这便意味着死锁发生了。自死琐是说自己占有了某个资源,然后自己又申请自己已占有的资源,显然不可能再获得该资源,因此就自缚手脚了。

spinlock特性:

防止多处理器并发访问临界区,

1、非睡眠(该进程/LWP(Light Weight Process)始终处于Running的状态)

2、忙等 (cpu一直检测锁是否已经被其他cpu释放)

3、短期(低开销)加锁

4、适合中断上下文锁定

5、多cpu的机器才有意义(需要等待其他cpu释放锁)

信号量与互斥量

Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。

信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。

信号量基本使用形式为:

static DECLARE_MUTEX(mr_sem); //声明互斥信号量

if(down_interruptible(&mr_sem))

//可被中断的睡眠,当信号来到,睡眠的任务被唤醒

//临界区

up(&mr_sem);

struct semaphore数据类型,down(struct semaphore * sem)和up(struct semaphore * sem)是占用和释放

struct mutex数据类型,mutex_lock(struct mutex *lock)和mutex_unlock(struct mutex *lock)是加锁和解锁

竞争信号量与互斥量时需要进行进程睡眠和唤醒,代价较高,所以不适于短期代码保护,适用于保护较长的临界区

互斥量与信号量的区别

1. 互斥量用于线程的互斥,信号量用于线程的同步

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别

互斥:

是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的

同步:

是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

2. 互斥量值只能为0/1,信号量值可以为非负整数

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问

3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到

特性:

1、睡眠 (系统会将CPU切换给其他的进程/LWP运行。)

2、必须进程上下文(可调度)

3、长期加锁

信号量和自旋锁区别

虽然听起来两者之间的使用条件复杂,其实在实际使用中信号量和自旋锁并不易混淆。注意以下原则:

如果代码需要睡眠——这往往是发生在和用户空间同步时——使用信号量是唯一的选择。由于不受睡眠的限制,使用信号量通常来说更加简单一些。

如果需要在自旋锁和信号量中作选择,应该取决于锁被持有的时间长短。理想情况是所有的锁都应该尽可能短的被持有,但是如果锁的持有时间较长的话,使用信号量是更好的选择。

另外,信号量不同于自旋锁,它不会关闭内核抢占,所以持有信号量的代码可以被抢占。这意味者信号量不会对影响调度反应时间带来负面影响。

自旋锁和信号量的选择

需求 建议的加锁方法
低开销加锁 优先使用自旋锁
短期锁定 优先使用自旋锁
长期加锁 优先使用信号量
中断上下文中加锁 使用自旋锁
持有锁是需要睡眠、调度 使用信号量

以上有部分内容转自 http://www.linuxidc.com/Linux/2011-03/33741.htm

以下内容转自 http://blog.sina.com.cn/s/blog_0001988f0101f42l.html

spinlock mutex语义上是一样的,都是对一临界区加锁保护,

区别是mutex得不到锁会睡眠,因此不能在中断上下文中使用。

另外,解锁的一定是上锁的那个 semaphore 得不到锁会睡眠,也不能用在中断中, 上锁的不一定负责解锁 。

rwlock 很好理解了,可多个读,只有一个写者,同样会引起睡眠

最重要的就是只有spinlock 可以用在中断上下文中.

至于wait_queue,不是同步手段,是内核管理sleeping进程的一种手段

什么是等待队列?

在软件开发中任务经常由于某种条件没有得到满足而不得不进入睡眠状态,然后等待条件得到满足的时候再继续运行,进入运行状态。这种需求需要等待队列机制的支持。

而且semaphore(我不知道所有的实现是不是这样)中也用到了wait_queue_head


spinlock和信号量sem的区别

重点:死循环/睡眠

spinlock只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。sem则会导致调用睡眠。然后应用上就是前者可以在中断处理中使用,后者不行。

信号量是针对使用时间比较长的共享资源,而自旋锁的则一般时间较短.一般的申请锁被其它保存则循环不止的等待.

  • Spinlock不断的检查等待的对象是否就绪,该进程/LWP始终处于Running的状态
  • Sem使该进程/LWP进入Wait转台,系统会将CPU切换给其他的进程/LWP运行。

由于这个根本特性的不同,导致了以下用法上的不同:

1. Spinlock 只适用于短暂的等待,因为没有进程切换所以对于短暂等待他的效率会比较高。但是对于长时间等待,由于它的CPU占用是100% 等的越长越不合算。

2. Spinlock只能在多个CPU的系统上用,因为等待者占据了100%的CPU,只由另外一个CPU上的进程才能解锁。

两者都是用于Linux内核互斥。避免并发,防止竞争,对系统公共资源或者共有数据进行合理保护的。SpinLock的出现是因为Symmetric Multi-Processor的出现,如果是UniProcessor,用简单的DisableIRQ就可以满足其要求。Spin Lock是通过Poll方式的,其可以说是一个Test and Set or Test andClear的模型的延伸。而Semaphore则是传统的IPC,是通过Sleep and Wake up方式实现的。通过SpinLock and Semaphore两者的实现机制则,我们可以很明晰的看出两者的应用场合。

(在高并发的地方,轮询比睡眠更高效)

Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。对于N=1的情况,称为binarysemaphore。一般的用法是,用于限制对于某一资源的同时访问。

Binarysemaphore与Mutex的差异:

在有的系统中Binarysemaphore与Mutex是没有差异的。在有的系统上,主要的差异是mutex一定要由获得锁的进程来释放。而semaphore可以由其它进程释放(这时的semaphore实际就是个原子的变量,大家可以加或减),因此semaphore可以用于进程间同步。Semaphore的同步功能是所有系统都支持的,而Mutex能否由其他进程释放则未定,因此建议mutex只用于保护criticalsection。而semaphore则用于保护某变量,或者同步。

另一个概念是spin lock,这是一个内核态概念。spinlock与semaphore的主要区别是spin lock是busywaiting,而semaphore是sleep。对于可以sleep的进程来说,busywaiting当然没有意义。对于单CPU的系统,busywaiting当然更没意义(没有CPU可以释放锁)。因此,只有多CPU的内核态非进程空间,才会用到spin lock。Linuxkernel的spinlock在非SMP的情况下,只是关irq,没有别的操作,用于确保该段程序的运行不会被打断。其实也就是类似mutex的作用,串行化对critical
section的访问。但是mutex不能保护中断的打断,也不能在中断处理程序中被调用。而spinlock也一般没有必要用于可以sleep的进程空间。

spinlock是多CPU下的同步机制,在获取锁时,如果失败,它不会挂起当前的执行过程。与之相对的,mutex和semaphore等同步机制,如果获取mutex或semaphore失败,它会挂起当前的执行过程,而在mutex或semaphore退出是,唤醒相应的过程。

不同的同步机制,是为了解决不同的问题。在单cpu上,不可能有spinlock,因为当前只能有一个活动的执行路径。而mutex或者semaphore则可以挂起当前的线程或者进程,CPU这时可以做其他的事情,等到挂在mutex或者semaphore上的进程被唤醒时,再继续执行被挂起的路径。

可以想象,spinlock的设计并不是不能支持挂起当前执行过程的操作。只是,在内核中,挂起当前的执行过程,就必须先能够标识这个过程。内核线程,或者进程,都有相应的结构;但是,哪些不属于这两种的执行过程,就必须使用spinlock。还有,如果一组进程在执行过程中,要求必须是同步执行,不能被打断,这种情况下spinlock也是必须的。

说了这么多,主要的意思就是说,不能的同步机制适用于不同的场景,解决不同的问题。没有一种同步机制可以解决所有的问题,这也是为什么linux里面不断有新的同步机制被引入内核。当新的问题出现时,现有的机制不能解决,或者不能很好解决这个问题时,就有必要引入新的机制来解决这个问题。

锁是一种协议,是有共享临界区的执行过程之间达成的协议。有了api,并不能保证程序的正确,还需要正确的,合理的使用api。尤其是在多cpu情况下,锁的使用,对性能有很大的影响。最好的办法就是不使用锁,每个CPU都是完全独立的运行。这也要看具体的应用,如果数据之间没有关联,当然可以独立地去处理;反之,则必须要锁来保护。

比如用multicore实现网络包的转发时,当然可以把某个流绑定到某个CPU上,假设流与流直接是完全独立的,这种情况下,每个CPU都可以独立的处理属于自己的流,不需要和其他CPU共享数据。但是,由于不同的流,流量是不同的,这样,CPU的能力没有被充分的利用,资源的使用也不平衡。这样的设计并不能很好的解决问题。

选择哪种设计方案,要综合的去考虑,没有最完美的解决方案,因为很多需求是相互冲突的,一个折衷的方案就是最后的选择。

时间: 2024-08-30 12:45:52

Linux中的spinlock和mutex的相关文章

在Linux中使用线程

我并不假定你会使用Linux的线程,所以在这里就简单的介绍一下.如果你之前有过多线程方面的编程经验,完全可以忽略本文的内容,因为它非常的初级. 首先说明一下,在Linux编写多线程程序需要包含头文件pthread.h.也就是说你在任何采用多线程设计的程序中都会看到类似这样的代码: #include 当然,进包含一个头文件是不能搞定线程的,还需要连接libpthread.so这个库,因此在程序连接阶段应该有类似这样的指令: gcc program.o -o program -lpthread.h>

LINUX 中的mmap浅析

原创LINUX系统编程水平有限,参考UNIX系统编程手册 LINUX 中的mmap浅析 一.mmap基本原理和分类 在LINUX中我们可以使用mmap用来在进程虚拟地址空间中分配创建一片虚拟内存地址映射 其可以是 1.文件映射    使用文件内容初始化内存 2.匿名映射    初始化全为0的内存空间(calloc也可以) 下面配图来自UNIX系统编程手册 而对于是否共享又分为 1.私有映射(MAP_PRIVATE)    多进程间数据共享,修改不反应到磁盘实际文件,    私有写时复制实现 2.

linux中PHP dirname(

  在php 中dirname() 函数返回路径中的目录部分,__FILE__而当前运行文件的完整路径和文件名.如果用在被包含文件中,则返回被包含的文件名.这是一个魔法变量(预定义常量),在windows中没有问题但在linux中路径出现的问题,下面我们一起来看看路径问题解决方法. 近期在给wordpress开发模板功能时发现,直接使用include("文件名")的形式调用其他php代码片段时会出现路径错误.之前服务器环境一直都是iis,未曾出现过类似的BUG,但换成linux服务器后

linux中的"瑞士军刀"

linux中的"瑞士军刀" busybox 俗称linux中的瑞士军刀,它类似于linux系统中bash 的一个缩微版,常用于嵌入式设备中,例如你的android手机中等等.busybox作为一个开源的应用,它的解析命令行的应用是值得学习的 http://busybox.net/

Linux中让显示器不休眠?

Linux中让显示器不休眠? 我们可以使用setterm命令: 操作如下: setterm -blank 0setterm -blank n (n为等待时间) setterm命令的其它选项: setterm: Argument error, usage setterm&http://www.aliyun.com/zixun/aggregation/37954.html">nbsp; [ -term terminal_name ]  [ -reset ]  [ -initialize

Linux中如何运行多个X窗口?

Linux中如何运行多个X窗口? startx默认以display :0.0起第一个X,通过传递参数给Xserver可以起多个X: # startx -- :1.0# startx -- :2.0... 然后用Ctrl-Alt-F7/F8...切换

linux 中配置apache 网站 编码设置为gb2312 浏览乱码

问题描述 linux 中配置apache 网站 编码设置为gb2312 浏览乱码 httpd.conf 添加了 AddDefaultCharset GB2312? 网页添加了 浏览器浏览的时候默认编码格式还是utf-8 显示乱码 解决方案 将环境变量LANG设置LANG="zh_CN GBK"

Linux中提示No such file or directory解决方法

  问题描述 解决方法 分析原因,可能因为我平台迁移碰到权限问题我们来进行权限转换 1)在Windows下转换: 利用一些编辑器如UltraEdit或EditPlus等工具先将脚本编码转换,再放到Linux中执行.转换方式如下(UltraEdit):File-->Conversions-->DOS->UNIX即可. 2)方法 用vim打开该sh文件,输入: [plain] :set ff 回车,显示fileformat=dos,重新设置下文件格式: [plain] :set ff=uni

Linux中如何切换到X桌面?

Linux中如何切换到X桌面? 如果你是以图形登录方式登录linux,那么点击登录界面上的session(任务)即可以选择gnome和kde.如果你是以文本方式登录,那执行switchdesk gnome或switchdesk kde,然后再startx就可以进入gnome或kde. (或者vi ~/.xinitrc,添加或修改成exec gnome-session 或exec startkde,然后用startx启动x)