- 浏览: 529660 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
GGGGeek:
看完了博主的博文,如果没猜错的话应该是浙大吧?很多优秀的人因为 ...
转《D君的故事》 以时刻警示自己 -
游牧民族:
楼主写的不错,学习了,最近对爬虫比较感兴趣,也写了些爬虫相关的 ...
通用爬虫框架及heritrix爬虫介绍 -
jimmee:
jerome_s 写道ice 你怎么看? 粗略的看了一下ice ...
MessagePack, Protocol Buffers和Thrift序列化框架原理和比较说明 -
jerome_s:
ice 你怎么看?
MessagePack, Protocol Buffers和Thrift序列化框架原理和比较说明 -
jimmee:
nk_tocean 写道照着做了,但是不行啊,还是乱码.先确认 ...
hive编写udf处理非utf-8数据
之前一直没有怎么关注过这个问题,前些日子在面试一家公司的时候,面试官提到了pthread_cond_wait/pthread_cond_signal的实现,当时答的不是很好,回来就查了nptl的代码。前天,水木上又有人问到了信号量和互斥锁的问题,我想还是对它们的区别与实现总结一下。
首先了解一些信号量和线程互斥锁的语义上的区别:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
援引CU上一篇帖子的内容:
“信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞在那里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”
也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
两者之间的区别:
作用域
信号量: 进程间或线程间(linux仅线程间)
互斥锁: 线程间
上锁时
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一。一句话,信号量的value>=0。
互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源。如果没有锁,获得资源成功,否则进行阻塞等待资源可用。一句话,线程互斥锁的vlaue可以为负数。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
接下来,我们需要分析一下信号量和线程互斥锁的实现机制。
在Linux下,信号量和线程互斥锁的实现都是通过futex系统调用。
futex(快速用户区互斥的简称)是一个在Linux上实现锁定和构建高级抽象锁如信号量和POSIX互斥的基本工具。它们第一次出现在内核开发的2.5.7版;其语义在2.5.40固定下来,然后在2.6.x系列稳定版内核中出现。
Futex 是fast userspace mutex的缩写,意思是快速用户空间互斥体。Linux内核把它们作为快速的用户空间的锁和信号量的预制构件提供给开发者。Futex非常基础,借助其自身的优异性能,构建更高级别的锁的抽象,如POSIX互斥体。大多数程序员并不需要直接使用Futex,它一般用来实现像NPTL这样的系统库。
Futex 由一块能够被多个进程共享的内存空间(一个对齐后的整型变量)组成;这个整型变量的值能够通过汇编语言调用CPU提供的原子操作指令来增加或减少,并且一个进程可以等待直到那个值变成正数。Futex 的操作几乎全部在应用程序空间完成;只有当操作结果不一致从而需要仲裁时,才需要进入操作系统内核空间执行。这种机制允许使用 futex 的锁定原语有非常高的执行效率:由于绝大多数的操作并不需要在多个进程之间进行仲裁,所以绝大多数操作都可以在应用程序空间执行,而不需要使用(相对高代价的)内核系统调用。
----------------------------------------------------------------
插播一段关于x86原子操作指令的说明:
cmpxchg 比较交换指令,其语义为:
Intel白皮书上的说明如下:
(* Accumulator = AL, AX, EAX, or RAX depending on whether a byte, word, doubleword, or
quadword comparison is being performed *)
IF accumulator = DEST
THEN
ZF ← 1;
DEST ← SRC;
ELSE
ZF ← 0;
accumulator ← DEST;
FI;
使用此原子操作可以实现自旋锁,之前有一篇文章中描述了实现:
关于smp下的原子操作的一些说明:
原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源互斥的原因。在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。
在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
当然,并不是所有的指令前面都可以加lock前缀的,只有ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG,DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, 和 XCHG指令前面可以加lock指令,实现原子操作。
----------------------------------------------------------------
广告回来了,我们继续。
futex保存在用户空间的共享内存中,并且通过原子操作进行操作。在大部分情况下,资源不存在争用的情况下,进程或者线程可以立刻获得资源成功,实际上就没有必要调用系统调用,陷入内核了。实际上,futex的作用就在于减少系统调用的次数,来提高系统的性能。
线程互斥锁pthread_mutex_t的实现原理:
信号量sem_t的实现原理(直接从glibc/nptl/DESIGN-sem.txt中摘的):
对比,pthread_mutex_unlock()和sem_post()的实现,我们发现一个不同点,sem_post()无论如何都会调用futex_wake(),进行系统调用。但是pthread_mutex_unlock()却符合futex的初衷,只有在需要仲裁的时候才调用futex_wake()。那么什么是仲裁条件呢?
前面说过信号量和线程互斥锁语义上的区别在于信号量的value>=0,而线程互斥锁的value可以为负数。
对于lock操作,这两个倒是没有多少差别。信号量只要value>0就可以获得资源,线程互斥锁需要value=1。
但是对于unlock操作,这两个就有一些差别了。信号量和线程互斥锁,都会增加对应的value。如果加1后,value为1,对于线程互斥锁来讲,实际上表明资源可用,并且之前没有其他的线程在等待这个资源;否则说明还有其他线程在等待这个资源,需要调用futex系统调用唤醒它们。但是对于信号量,由于value必须>=0。那么加1后,即使value为1,也无法判定现在没有其他的进程或线程正在等待资源,所以必须调用futex系统调用。例如:
感兴趣的同学可以使用strace跟踪一下,进行验证。要注意忽略程序运行初始化的那个futex_wake ;-)
参考:
http://www.eetop.cn/blog/html/04/343504-14125.html
http://javadino.blog.sohu.com/99256728.html
http://javadino.blog.sohu.com/99256835.html
http://javadino.blog.sohu.com/99256921.html
文章出处:DIY部落(http://www.diybl.com/course/6_system/linux/Linuxjs/20090901/173322.html)
首先了解一些信号量和线程互斥锁的语义上的区别:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
援引CU上一篇帖子的内容:
“信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞在那里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”
也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
两者之间的区别:
作用域
信号量: 进程间或线程间(linux仅线程间)
互斥锁: 线程间
上锁时
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一。一句话,信号量的value>=0。
互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源。如果没有锁,获得资源成功,否则进行阻塞等待资源可用。一句话,线程互斥锁的vlaue可以为负数。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
接下来,我们需要分析一下信号量和线程互斥锁的实现机制。
在Linux下,信号量和线程互斥锁的实现都是通过futex系统调用。
futex(快速用户区互斥的简称)是一个在Linux上实现锁定和构建高级抽象锁如信号量和POSIX互斥的基本工具。它们第一次出现在内核开发的2.5.7版;其语义在2.5.40固定下来,然后在2.6.x系列稳定版内核中出现。
Futex 是fast userspace mutex的缩写,意思是快速用户空间互斥体。Linux内核把它们作为快速的用户空间的锁和信号量的预制构件提供给开发者。Futex非常基础,借助其自身的优异性能,构建更高级别的锁的抽象,如POSIX互斥体。大多数程序员并不需要直接使用Futex,它一般用来实现像NPTL这样的系统库。
Futex 由一块能够被多个进程共享的内存空间(一个对齐后的整型变量)组成;这个整型变量的值能够通过汇编语言调用CPU提供的原子操作指令来增加或减少,并且一个进程可以等待直到那个值变成正数。Futex 的操作几乎全部在应用程序空间完成;只有当操作结果不一致从而需要仲裁时,才需要进入操作系统内核空间执行。这种机制允许使用 futex 的锁定原语有非常高的执行效率:由于绝大多数的操作并不需要在多个进程之间进行仲裁,所以绝大多数操作都可以在应用程序空间执行,而不需要使用(相对高代价的)内核系统调用。
----------------------------------------------------------------
插播一段关于x86原子操作指令的说明:
cmpxchg 比较交换指令,其语义为:
int CompareAndExchange(int *ptr, int old, int new) { int actual = *ptr; if (actual == old) *ptr = new; return actual; }
Intel白皮书上的说明如下:
(* Accumulator = AL, AX, EAX, or RAX depending on whether a byte, word, doubleword, or
quadword comparison is being performed *)
IF accumulator = DEST
THEN
ZF ← 1;
DEST ← SRC;
ELSE
ZF ← 0;
accumulator ← DEST;
FI;
使用此原子操作可以实现自旋锁,之前有一篇文章中描述了实现:
void lock(lock_t *lock) { while (CompareAndExchange(&lock->flag, 0, 1) == 1) ; // spin } void unlock(lock_t *lock) { lock->flag = 0; }
关于smp下的原子操作的一些说明:
原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源互斥的原因。在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。
在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
当然,并不是所有的指令前面都可以加lock前缀的,只有ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG,DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, 和 XCHG指令前面可以加lock指令,实现原子操作。
----------------------------------------------------------------
广告回来了,我们继续。
futex保存在用户空间的共享内存中,并且通过原子操作进行操作。在大部分情况下,资源不存在争用的情况下,进程或者线程可以立刻获得资源成功,实际上就没有必要调用系统调用,陷入内核了。实际上,futex的作用就在于减少系统调用的次数,来提高系统的性能。
线程互斥锁pthread_mutex_t的实现原理:
pthread_mutex_lock: atomic_dec(pthread_mutex_t.value); if(pthread_mutex_t.value!=0) futex(WAIT) else success pthread_mutex_unlock: atomic_inc(pthread_mutex_t.value); if(pthread_mutex_t.value!=1) futex(WAKEUP) else success
信号量sem_t的实现原理(直接从glibc/nptl/DESIGN-sem.txt中摘的):
sem_wait(sem_t *sem) { for (;;) { if (atomic_decrement_if_positive(sem->count)) break; futex_wait(&sem->count, 0) } } sem_post(sem_t *sem) { n = atomic_increment(sem->count); // Pass the new value of sem->count futex_wake(&sem->count, n + 1);
对比,pthread_mutex_unlock()和sem_post()的实现,我们发现一个不同点,sem_post()无论如何都会调用futex_wake(),进行系统调用。但是pthread_mutex_unlock()却符合futex的初衷,只有在需要仲裁的时候才调用futex_wake()。那么什么是仲裁条件呢?
前面说过信号量和线程互斥锁语义上的区别在于信号量的value>=0,而线程互斥锁的value可以为负数。
对于lock操作,这两个倒是没有多少差别。信号量只要value>0就可以获得资源,线程互斥锁需要value=1。
但是对于unlock操作,这两个就有一些差别了。信号量和线程互斥锁,都会增加对应的value。如果加1后,value为1,对于线程互斥锁来讲,实际上表明资源可用,并且之前没有其他的线程在等待这个资源;否则说明还有其他线程在等待这个资源,需要调用futex系统调用唤醒它们。但是对于信号量,由于value必须>=0。那么加1后,即使value为1,也无法判定现在没有其他的进程或线程正在等待资源,所以必须调用futex系统调用。例如:
#include <stdio.h> #include <semaphore.h> #include <pthread.h> sem_t sem_a; void *task1(); int main(void) { int ret=0; pthread_t thrd1; pthread_t thrd2; sem_init(&sem_a,0,1); ret=pthread_create(&thrd1,NULL,task1,NULL); //创建子线程 ret=pthread_create(&thrd2,NULL,task1,NULL); //创建子线程 pthread_join(thrd1,NULL); //等待子线程结束 pthread_join(thrd2,NULL); //等待子线程结束 } void *task1() { int sval = 0; sem_wait(&sem_a); //持有信号量 sleep(5); //do_nothing sem_getvalue(&sem_a,&sval); printf("sem value = %d\n",sval); sem_post(&sem_a); //释放信号量 }上面sem的value初始化为1,但是有两个线程争用资源。那么第一个线程获得资源成功,当它unlock的时候,sem的value变为1。但是,这个时候,实际上还有一个线程在等待资源。因此,必须要进行futex_wake()系统调用,唤醒等待资源的线程。
感兴趣的同学可以使用strace跟踪一下,进行验证。要注意忽略程序运行初始化的那个futex_wake ;-)
参考:
http://www.eetop.cn/blog/html/04/343504-14125.html
http://javadino.blog.sohu.com/99256728.html
http://javadino.blog.sohu.com/99256835.html
http://javadino.blog.sohu.com/99256921.html
文章出处:DIY部落(http://www.diybl.com/course/6_system/linux/Linuxjs/20090901/173322.html)
发表评论
-
[转载]并发之痛 Thread,Goroutine,Actor
2017-04-06 19:21 531转自 http://jolestar.com/pa ... -
docker 说明
2016-11-30 22:21 0作者:Honglin Feng链接:h ... -
docker aufs
2016-11-30 00:04 0Docker镜像 典型的Linux文件系统由bootfs和 ... -
docker 目录结构
2016-11-29 23:41 0root@ubuntu:/var/lib/docker# ... -
OS X 项目占用处理
2016-09-15 15:01 0解决步骤1、启动终端2、将移动硬盘加载成可写状态3、在终端中 ... -
Mac OSX 10.10 Yosemite编译OpenJDK 8
2016-04-03 18:14 3438编译时间:2016-04-03 系统版本:Mac OS ... -
物理IO与逻辑IO
2016-03-19 21:30 1979IO性能对于一个系统的影响是至关重要的。一个系统经 ... -
A tcpdump Tutorial and Primer[reproduced]
2014-12-24 22:29 1113tcpdump is the premier network ... -
Spy on Yourself with tcpdump[转载]
2014-12-24 20:13 593As a network administrator, y ... -
linux c时间操作相关函数
2014-12-11 23:00 5861. linux c的时间操作的函数关系图如下: ... -
c内存操作感悟(2)
2014-12-10 20:52 1090不从分配的地址开始访问, 希望跳过一些字节, 怎么处理? ... -
c内存操作感悟(1)
2014-12-08 21:38 1036直接使用c, 有个好处, 自己可以完全控制内存啊,一切脑海 ... -
多线程程序中操作的原子性[转载]
2014-12-06 10:49 11060. 背景 原子操作就是不可再分的操作。在多线程程序中原子 ... -
6. 内存屏障[转载]
2014-11-26 00:07 666原文地址 作者:Martin Thompson 译者: ... -
5.合并写(write combining)[转载]
2014-11-25 21:54 685原文地址 译者:无叶 ... -
4. 内存访问模型的重要性[转载]
2014-11-25 21:53 988在高性能的计算中,我 ... -
3. Java 7与伪共享的新仇旧恨[转载]
2014-11-25 21:45 820原文:False Shareing && J ... -
2. 伪共享(False Sharing)[转载]
2014-11-25 21:40 803作者:Martin Thompson 译者:丁一 缓存 ... -
1. CPU缓存刷新的误解[转载]
2014-11-25 21:28 950原文地址 作者:Mechanical Sympathy ... -
整理一下准备编写的笔记目录
2013-12-31 21:21 1346工作6年,主要从事分布式服务器端开发(3年),做过 ...
相关推荐
互斥锁、条件变量、信号量是系统为实现多线程(多进程)访问共享资源或共同协作的同步机制
多线程 教程 各种锁 半成品的CAS 临界区 信号量 事件 互斥锁 队列
多个线程共享数据的时候,如果数据不进行保护,那么可能出现数据不一致现象,使用锁,信号量、条件锁 互斥锁 1. 互斥锁,是使用一把锁把代码保护起来,以牺牲性能换取代码的安全性,那么Rlock后 必须要relase 解锁...
本文实例讲述了Python多线程操作之互斥锁、递归锁、信号量、事件。分享给大家供大家参考,具体如下: 互斥锁: 为什么要有互斥锁:由于多线程是并行的,如果某一线程取出了某一个数据将要进行操作,但它还没有那么...
主要介绍了Java编程中的互斥锁,信号量和多线程等待机制实例详解,简单介绍了互斥锁和信号量的区别,需要的朋友可以了解下。
Linux多线程编程中互斥锁、条件变量和信号量
VC++MFC多线程同步实例,信号量,互斥锁,事件,临界资源
C++线程模板类,锁模板类,使用非常方便,并且支持windows和linux跨平台使用。
信号量的运用环境与互斥锁一样,但是信号量比互斥锁增加灵活,互斥锁只有两个状态(开锁和解锁),而信号量本质上是一个计数器,它内部有一个变量计数信号值,可以保护一个资源可以同时被1个或者2个或者3个线程同时...
四、条件变量与互斥锁、信号量的区别 55 第六章 共享内存 56 一、什么是共享内存区 56 二、mmap 56 三、posix共享内存函数 60 四、ftruncate和fstat函数 62 五、共享内存区的写入和读出 64 六、程序例子 65
并通过多进程合并排序来显示差异其他两个问题是现实世界中两种不同类型的问题,它们被建模为具有并行处理的计算机程序,需要谨慎使用线程,互斥量,信号量和条件变量。 相关解决方案的README.md中提到了每种解决方案...
使用互斥锁和共享内存实现的非阻塞FIFO,另外代码中有包含信号量的实现。个人测试稳定,有一些注释,一起学习。如有问题,欢迎讨论。
linux任务和信号量的使用详细使用例子
四、条件变量与互斥锁、信号量的区别 55 第六章 共享内存 56 一、什么是共享内存区 56 二、mmap 56 三、posix共享内存函数 60 四、ftruncate和fstat函数 62 五、共享内存区的写入和读出 64 六、程序例子 65
互斥量的实现与进程中的信号量(无名信号量)是类似的,当然,信号量也可以用于线程,区别在于初始化的时候,其本质都是P/V操作。编译时,记得加上-lpthread或-lrt哦。 有关进程间通信(消息队列)见:进程间通信之...
在《秒杀多线程系列》的前十五篇中介绍多线程的相关概念,多线程同步互斥问题《秒杀多线程第四篇一个经典的多线程同步问题》及解决多线程同步互斥的常用方法——关键段、事件、互斥量、信号量、读写锁。为了让大家...
window下的多线程,信号量、互斥锁、事件和临界区
linux多线程编程基础,进程间的通信,进程信号量处理,互斥锁
1) 互斥(Mutex), 信号量(Semaphore), 事件(AutoResetEvent/ManualResetEvent)2) 线程池 除了以上的这些对象之外实现线程同步的还可以使用Thread.Join方法。这种方法比较简单,当你在第一个线程运行时想等待第二个...