转自:http://www.csdn123.com/html/blogs/20130509/11141.htm

自旋锁是SMP架构中的一种low-level的同步机制。

当线程A想要获取一把自旋锁而该锁又被其它线程锁持有时,线程A会在一个循环中自旋以检测锁是不是已经可用了。对于自选锁需要注意:

由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间。

持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁。(在内核编程中,如果持有自旋锁的代码sleep了就可能导致整个系统挂起,最近刚解决了一个内核中的问题就是由于持有自旋锁时sleep了,然后导致所有的核全部挂起(是一个8核的CPU))

使用任何锁需要消耗系统资源(内存资源和CPU时间),这种资源消耗可以分为两类:

建立锁所需要的资源

当线程被阻塞时锁所需要的资源

Pthreads提供的与Spin Lock锁操作相关的API主要有:

int pthread_spin_destroy(pthread_spinlock_t *);

int pthread_spin_init(pthread_spinlock_t *, int);

int pthread_spin_lock(pthread_spinlock_t *);

int pthread_spin_trylock(pthread_spinlock_t *);

int pthread_spin_unlock(pthread_spinlock_t *);

1)初始化自旋锁

pthread_spin_init用来申请使用自旋锁所需要的资源并且将它初始化为非锁定状态。pshared的取值及其含义:

PTHREAD_PROCESS_SHARED:该自旋锁可以在多个进程中的线程之间共享。

PTHREAD_PROCESS_PRIVATE:仅初始化本自旋锁的线程所在的进程内的线程才能够使用该自旋锁。

2)获得一个自旋锁

pthread_spin_lock用来获取(锁定)指定的自旋锁. 如果该自旋锁当前没有被其它线程所持有,则调用该函数的线程获得该自旋锁.否则该函数在获得自旋锁之前不会返回。如果调用该函数的线程在调用该函数时已经持有了该自旋锁,则结果是不确定的。

3)尝试获取一个自旋锁

pthread_spin_trylock会尝试获取指定的自旋锁,如果无法获取则理解返回失败。

4)释放(解锁)一个自旋锁

pthread_spin_unlock用于释放指定的自旋锁。

5)销毁一个自旋锁

pthread_spin_destroy

用来销毁指定的自旋锁并释放所有相关联的资源(所谓的所有指的是由pthread_spin_init自动申请的资源)在调用该函数之后如果没有调用

pthread_spin_init重新初始化自旋锁,则任何尝试使用该锁的调用的结果都是未定义的。如果调用该函数时自旋锁正在被使用或者自旋锁未被初

始化则结果是未定义的。

互斥量和自旋锁的区别:

Pthreads提供的Mutex锁操作相关的API主要有:

pthread_mutex_lock (pthread_mutex_t *mutex);

pthread_mutex_trylock (pthread_mutex_t *mutex);

pthread_mutex_unlock (pthread_mutex_t *mutex);

Pthreads提供的与Spin Lock锁操作相关的API主要有:

pthread_spin_lock (pthread_spinlock_t *lock);

pthread_spin_trylock (pthread_spinlock_t *lock);

pthread_spin_unlock (pthread_spinlock_t *lock);

从实现原理上来讲,Mutex属于sleep-waiting类型的

锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和Core1上。假设线程A想要通过

pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞(blocking),Core0

会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin

lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。       如果大家去查阅Linux glibc中对pthreads API的实现NPTL(Native POSIX

Thread Library) 的源码的话(使用”getconf

GNU_LIBPTHREAD_VERSION”命令可以得到我们系统中NPTL的版本号),就会发现pthread_mutex_lock()操作如果

没有锁成功的话就会调用system_wait()的系统调用并将当前线程加入该mutex的等待队列里。而spin

lock则可以理解为在一个while(1)循环中用内嵌的汇编代码实现的锁操作(印象中看过一篇论文介绍说在linux内核中spin

lock操作只需要两条CPU指令,解锁操作只用一条指令就可以完成)。有兴趣的朋友可以参考另一个名为sanos的微内核中pthreds

API的实现:mutex.c spinlock.c,尽管与NPTL中的代码实现不尽相同,但是因为它的实现非常简单易懂,对我们理解spin

lock和mutex的特性还是很有帮助的。

对于自旋锁来说,它只需要消耗很少的资源来建立锁;随后当线程被阻塞时,它就会一直重复检查看锁是否可用了,也就是说当自旋锁处于等待状态时它会一直消耗CPU时间。

对于互斥锁来说,与自旋锁相比它需要消耗大量的系统资源来建立锁;随后当线程被阻塞时,线程的调度状态被修改,并且线程被加入等待线程队列;最后当锁可用

时,在获取锁之前,线程会被从等待队列取出并更改其调度状态;但是在线程被阻塞期间,它不消耗CPU资源。

因此自旋锁和互斥锁适用于不同的场景。自旋锁适用于那些仅需要阻塞很短时间的场景,而互斥锁适用于那些可能会阻塞很长时间的场景。

自旋锁与linux内核进程调度关系

如果临界区可能包含引起睡眠的代码则不能使用自旋锁,否则可能引起死锁。

那么为什么信号量保护的代码可以睡眠而自旋锁就不能呢?

先看下自旋锁的实现方法吧,自旋锁的基本形式如下:

spin_lock(&mr_lock);

//临界区

spin_unlock(&mr_lock);

跟踪一下spin_lock(&mr_lock)的实现

#define spin_lock(lock) _spin_lock(lock)

#define _spin_lock(lock) __LOCK(lock)

#define __LOCK(lock) \

do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)

注意到“preempt_disable()”,这个调用的功能是“关抢占”(在spin_unlock中会重新开启抢占功能)。从

中可以看出,使用自旋锁保护的区域是工作在非抢占的状态;即使获取不到锁,在“自旋”状态也是禁止抢占的。了解到这,我想咱们应该能够理解为何自旋锁保护

的代码不能睡眠了。试想一下,如果在自旋锁保护的代码中间睡眠,此时发生进程调度,则可能另外一个进程会再次调用spinlock保护的这段代码。而我们

现在知道了即使在获取不到锁的“自旋”状态,也是禁止抢占的,而“自旋”又是动态的,不会再睡眠了,也就是说在这个处理器上不会再有进程调度发生了,那么

死锁自然就发生了。  咱们可以总结下自旋锁的特点:

● 单处理器非抢占内核下:自旋锁会在编译时被忽略;

● 单处理器抢占内核下:自旋锁仅仅当作一个设置内核抢占的开关;

● 多处理器下:此时才能完全发挥出自旋锁的作用,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。

linux抢占发生的时间最后在了解下linux抢占发生的时间,抢占分为用户抢占和内核抢占。

用户抢占在以下情况下产生:

● 从系统调用返回用户空间

● 从中断处理程序返回用户空间

内核抢占会发生在:

● 当从中断处理程序返回内核空间的时候,且当时内核具有可抢占性;

● 当内核代码再一次具有可抢占性的时候。(如:spin_unlock时)

● 如果内核中的任务显式的调用schedule()

● 如果内核中的任务阻塞。

基本的进程调度就是发生在时钟中断后,并且发现进程的时间片已经使用完了,则发生进程抢占。通常我们会利用中断处理程序返回内核空间的时候可以进行内

核抢占这个特性来提高一些I/O操作的实时性,如:当I/O事件发生的是时候,对应的中断处理程序被激活,当它发现有进程在等待这个I/O事件的时候,它

会激活等待进程,并且设置当前正在执行进程的need_resched标志,这样在中断处理程序返回的时候,调度程序被激活,原来在等待I/O事件的进程

(很可能)获得执行权,从而保证了对I/O事件的相对快速响应(毫秒级)。可以看出,在I/O事件发生的时候,I/O事件的处理进程会抢占当前进程,系统

的响应速度与调度时间片的长度无关。

总结:(1)Mutex适合对锁操作非常频繁的场景,并且具有更好的适应性。尽管相比spin lock它会花费更多的开销(主要是上下文切换),但是它能适合实际开发中复杂的应用场景,在保证一定性能的前提下提供更大的灵活度。

(2)spin

lock的lock/unlock性能更好(花费更少的cpu指令),但是它只适应用于临界区运行时间很短的场景。而在实际软件开发中,除非程序员对自己

的程序的锁操作行为非常的了解,否则使用spin

lock不是一个好主意(通常一个多线程程序中对锁的操作有数以万次,如果失败的锁操作(contended lock

requests)过多的话就会浪费很多的时间进行空等待)。

(3)更保险的方法或许是先(保守的)使用 Mutex,然后如果对性能还有进一步的需求,可以尝试使用spin

lock进行调优。毕竟我们的程序不像Linux kernel那样对性能需求那么高(Linux Kernel最常用的锁操作是spin

lock和rw lock)。

linux posix 线程池_posix多线程有感--自旋锁相关推荐

  1. linux posix 线程池_linux多线程--POSIX Threads Programming

    linux多线程自己从接触很久也有不少实践,但总是觉得理解不够深刻,不够系统.借这篇文章试着再次系统学习一下linux多线程编程,理解编程的concept,细致看一下POSIX pthread API ...

  2. linux多线程:自旋锁

    目录 1.概述 2.spin lock 锁相关的API 2.1.初始化自旋锁 2.2.获得一个自旋锁 2.3.尝试获取一个自旋锁 2.4.释放(解锁)一个自旋锁 2.5.销毁一个自旋锁 3.Mutex ...

  3. linux下线程池实现

    linux下线程池实现 转自:http://blog.csdn.net/lmh12506/article/details/7753952 前段时间在github上开了个库,准备实现自己的线程池的,因为 ...

  4. linux 下线程池

    基本想法是这样的:       1.预创建的线程通过mutex休眠在线程池中.这样,通过unlock该mutex就可以唤醒该线程了:       2.出于简单性的目标,一个线程池内的所有线程的属性都是 ...

  5. 【二十四】springboot使用EasyExcel和线程池实现多线程导入Excel数据

      springboot篇章整体栏目:  [一]springboot整合swagger(超详细 [二]springboot整合swagger(自定义)(超详细) [三]springboot整合toke ...

  6. Linux并发与竞争介绍(原子操作、自旋锁、信号量、互斥体)

    目录 并发与竞争 并发与竞争简介 保护内容是什么 原子操作 原子操作简介 原子整形操作API函数(atomic_t 结构体) 原子位操作API 函数 自旋锁 自旋锁简介 自旋锁API函数 线程与线程 ...

  7. Linux ARM平台开发系列讲解(自旋锁) 3.3.1 Linux内核自旋锁描述

    1. 概述 原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形变量或位这么简单的临界区.举个最简单的例子,设备结构体变量就不是整型变量,我们对于结构体中成员变量的操作也要保 ...

  8. linux线程池实现多线程并发,基于Linux的多线程池并发Web服务器设计-电子设计工程.PDF...

    基于Linux的多线程池并发Web服务器设计-电子设计工程.PDF 第 卷 第 期 电子设计工程 年 月 基于 的多线程池并发 服务器设计 陈 涛 任海兰 武汉邮电科学研究院 湖北 武汉 摘要 时至今 ...

  9. linux动态线程池--原理,这儿的代码不完整

    本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关.另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量.文章的最后,我们 ...

最新文章

  1. XamarinAndroid组件教程设置动画的时长参数
  2. 排序的概念(选择排序1)
  3. tag+标签+php,ZBLOG PHP代码实现侧栏彩色标签TAG关键字样式方法
  4. STM32F7xx —— 96位唯一ID
  5. linux初学文档,51CTO博客-专业IT技术博客创作平台-技术成就梦想
  6. 讲述下 :LVM逻辑卷管理遇到的问题
  7. 传感器绕着世界坐标系旋转产生的疑惑
  8. 获得代理ippython_Python学习笔记六(免费获取代理IP)
  9. 【Kafka】Kafka生产者producer相关参数详解batch.size linger.ms 等参数
  10. 数字图像识别笔记(第三章-灰度变换与空间滤波)
  11. JavaScript学习(六十五)—数组知识点总结
  12. mangos服务器架构
  13. 找出数列中个数大于总数一半的元素(编程之美2.3)
  14. android map 底层实现原理,LinkedHashMap底层实现和原理(源码解析)
  15. 前端开发面试题-JavaScript(一)
  16. GJB150.5A-2009军用装备实验室温度冲击环境试验
  17. python机器学习教程_从零开始掌握Python机器学习:十四步教程
  18. 教你修改Win7系统的登录界面背景
  19. fx991计算器矩阵计算机,如何用卡西欧fx991计算器算矩阵
  20. 什么是DHCP(中继模式)

热门文章

  1. 交换机err-disabled状态解决方案
  2. 【JZOJ3422】水叮当的舞步
  3. 批量下载和改名21世纪英文报学生周报听力
  4. 计算机毕业设计Java养老院信息管理(系统+源码+mysql数据库+Lw文档)
  5. DbSchema注册机
  6. wxoauth微信网页授权代码
  7. Lesson10 Hadoop 完全分布式 群启动集群、集群的基本测试 及 集群的停止
  8. 教你ZIP文件如何解压读取、压缩下载【解答】
  9. 地图瓦片:矢量瓦片和栅格瓦片详解
  10. 如何成为二八定律中的“20%”