【转】深层次探讨mutex与semaphore之间的区别(下)

原文网址:http://blog.chinaunix.net/uid-23769728-id-3173282.html

这篇博文很长,虽然这是下篇,但还没结束,benchmark方面的东西正在进行中,另外还有一些问题我自己也在和别人讨论...所以我想除了还有“结束语”篇(其实这篇我基本写完了,但是还没最终盖棺论定,有些问题尚需要进一步讨论)之外,还应该会有个“实际性能测试”篇...理想是美好的,但是现实时间总是不够用滴。因此,我想说,如果这篇博文最后烂尾了,请网友们--把我埋在,埋在春天里。。。
======================================================================

mutex_lock的slow path的调用链为:
mutex_lock --> __mutex_lock_slowpath --> __mutex_lock_common,所有的性能优化的代码又都集中在__mutex_lock_common中,这个函数有点长,等下我们不妨肢解来慢慢看。。。

mutex_lock在slow path当中优化的基本原理是:拥有mutex lock的进程总是会在尽可能短的时间里释放。基于此,mutex_lock的slow path部分会尽量避免进入睡眠状态,它试图通过短暂的spin来等待拥有互斥锁的进程释放它。__mutex_lock_common的主体结构是两个 for循环,中间加入对能否再次获得锁的判断逻辑。

/*
* Lock a mutex (possibly interruptible), slowpath:
*/
static inline int __sched
__mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
                    struct lockdep_map *nest_lock, unsigned long ip)
{
        struct task_struct *task = current;
首先,我们能够达到这里,表明当前进程在一次争夺互斥锁的战争中失败了,此时几方争夺的互斥锁mutex中的owner即为成功获得该锁的task_struct对象... 
先关闭内核可抢占性,是因为在后续的处理中,我们不希望被别的进程抢占出处理器,因为对当前进程而言,1st priority的事情是获得锁。高优先级进程在此时出现也不用抢占当前进程,因为当前进程接下来要么睡眠(那么就无需被抢占了),要么获得锁而再次打开 可抢占性。
        preempt_disable();
内核配置选项,目前是内核默认的配置
        #ifdef CONFIG_MUTEX_SPIN_ON_OWNER        
源码中下面是一段很重要的注释,核心要点是mutex优化所基于的现实基础:一个获得互斥锁的进程极大的可能会在很短的时间内释放掉它。所以不同于 semaphore的实现,mutex在第一次没获得锁的情形下,如果发现拥有该锁的进程正在别的处理器上运行且锁上没有其他等待者(也即只有当前进程在 等待该锁),那么当前进程试图spin(说白了就是忙等待),这样有极大的概率是可以免去两次进程切换的开销,而重新获得之前竞争但未能成功的互斥锁 ----性能提升处。理论上,为了获得锁而进行的spin,其时间长短不应该超过两次进程切换的时间开销,否则此处优化将没有意义。

下面是第一个for循环, 循环中有两个break,一个直接return 0
       for (;;) {
                struct task_struct *owner;

/*
                 * If there‘s an owner, wait for it to either
                 * release the lock or go to sleep.
                 */
                owner = ACCESS_ONCE(lock->owner);
                if (owner && !mutex_spin_on_owner(lock, owner))
                        break;

if (atomic_cmpxchg(&lock->count, 1, 0) == 1) {
                        lock_acquired(&lock->dep_map, ip);
                        mutex_set_owner(lock);
                        preempt_enable();
                        return 0;
                }

/*
                 * When there‘s no owner, we might have preempted between the
                 * owner acquiring the lock and setting the owner field. If
                 * we‘re an RT task that will live-lock because we won‘t let
                 * the owner complete.
                 */
                if (!owner && (need_resched() || rt_task(task)))
                        break;

/*
                 * The cpu_relax() call is a compiler barrier which forces
                 * everything in this loop to be re-loaded. We don‘t need
                 * memory barriers as we‘ll eventually observe the right
                 * values at the cost of a few extra spins.
                 */
                arch_mutex_cpu_relax();
        } //the first for loop
先看第一个break,if条件里owner基本上都会满足,如果owner=NULL则说明当前拥有锁的进程可能已经释放了锁,所以可以立刻退出该循 环。if条件中的mutex_spin_on_owner()是个非常有意思的函数,它通过per-jiffies的方式来确保可以在极短的时间里 break那个while循环。这段代码设计是如此地富有想象力,它通过if (need_resched())使得函数能在jiffies级别上跳出while循环,但是代码优化的性能提升则体现在owner_running中, 因为拥有锁的进程在极短的时间(肯定是低于jiffies这个级别的,可能在us级甚至更低)释放锁,如果通过if (need_resched())退出循环,则基本说明了本次优化的失败,事实上还导致了性能的倒退(因为即便在HZ=1000的系统中,jiffies的级别也是非常粗糙的,现代处理器的进程切换的开销可能只在几个us或者几十个us,如果让一个进程为了获得mutex lock而去spin几个jiffies,那么这简直就是暴敛天物了,如果这个时间让当前进程睡眠,那么其他进程就可以获得CPU资源,而1个jiffies可以抵得上几十上百个进程切换的时间开销,根本就不需要在乎两次进程切换的时间开销。但是IBM的Paul同学认为,如果让一个进程在获得mutex lock的情形下运行几个jiffies再释放lock,那么这可能是个bug。我不认为这是对的,mutex lock不同于spin lock,不应该对mutex_lock与mutex_unlock之间的代码执行时间做什么限定)。代码在mutex_spin_on_owner()中通过 while循环来密切关注拥有锁的进程运行情况,一旦从while中跳出来,说明当前进程已经释放锁(通过owner_running),或者当前拥有锁 的进程运行的时间够长(可能为几个jiffies),最后返回前检查lock->owner,如果是NULL,源码注释中也讲得很清 楚,"which is a sign for heavy contention",在当前进程还没来得及下手前,lock已经被他人横刀夺爱了,此种情形下最好去sleep了,否则spin的时间就足以抵得上一 次进程切换的代价。。。

中间的return 0是个非常理想的情况,当前进程spin的过程中,锁的拥有者已经释放了锁,这个最简单,二次获得锁成功而直接返回。

接下来的break是当前进程在对方先行抢占到了锁但是还没来得及设定owner的时候抢占了它,或者当前进程是个实时进程,此时需要进入后半段处理。

在第二个for循环之前,从代码明显看到设计者已经在为当前进程进入sleep状态做最后的准备(如果代码进入到第二个for循环,实际上意味着本次优化 的失败,从性能的角度,这条路径上的性能肯定没有semaphore来得高,至少是没什么优势,因为你前面毕竟spin了一下,最后才sleep,但是 semaphore根本就不spin,第一次没拿到锁的话直接就sleep了),不过它在进入第二个for循环之前还是要做了个atomic_xchg动 作,主要是对第一个for循环中的两个break进行处理,看看是否足够幸运能再次获得锁。

第二个for循环的代码已经和semaphore的slow path实现基本一样了,所以我们看到对mutex的优化集中在第一个for循环之中,而且有很大的概率在那里会重新获得锁。

我们看到对mutex的优化其实遵循了代码优化的一般原则,即集中优化整个代码执行中出现的hot-spot(引申到高概率spot)。因为在实际使用当中,大多数情况 下,mutex_lock与mutex_unlock之间的代码都比较简短,使得获得锁的进程可以很快释放锁(因此,从性能优化的角度,这个也可以作为使 用mutex的一条一般原则)。如果系统中大部分拥有互斥锁的进程在mutex_lock与unlock之间执行时间比较长,那么相对于使用 semaphore,我相信使用mutex会使得系统性能降低:因为很大的概率,mutex都经过一段spin(虽然这段时间极短)之后最终还是进入 sleep,而semaphore则直接进入sleep,没有了spin的过程。

时间: 2024-10-10 06:07:13

【转】深层次探讨mutex与semaphore之间的区别(下)的相关文章

mutex与semaphore的区别

网摘1:Mutex 的发音是 /mjuteks/ ,其含义为互斥(体),这个词是Mutual Exclude的缩写.Mutex在计算机中是互斥也就是排他持有的一种方式,和信号量-Semaphore有可以对比之处.有人做过如下类比:    * Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个.一般的用法是用于串行化对critical section代码的访问,保证这段代码不会被并行的运行.    * Semaphore是一件可以容纳N人的房间,如果人不满就可以进去

一文看懂 Mutex vs Semaphore vs Monitor vs SemaphoreSlim

C#开发者(面试者)都会遇到Mutex,Semaphore,Monitor,SemaphoreSlim这四个与锁相关的C#类型,本文期望以最简洁明了的方式阐述四种对象的区别. 什么叫线程安全? 教条式理解 如果代码在多线程环境中运行的结果与 单线程运行结果一样,其他变量值也和预期是一样的,那么线程就是安全的: 线程不安全就是不提供数据访问保护,可能出现多个线程先后修改数据造成的结果是脏数据. 实际场景理解 两个线程都为集合增加元素,我们错误的理解即使是多线程也总有先后顺序吧,集合的两个位置先后塞

清晰易懂的进程与线程之间的区别

进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握.最近,我读到一篇材料,发现有一个很好的类比,可以把它们解释地清晰易懂. 1.计算机的核心是CPU,它承担了所有的计算任务.它就像一座工厂,时刻在运行. 2.假定工厂的电力有限,一次只能供给一个车间使用.也就是说,一个车间开工的时候,其他车间都必须停工.背后的含义就是,单个CPU一次只能运行一个任务. 3.进程就好比工厂的车间,它代表CPU所能处理的单个任务.任一时刻,CPU总是运行一个进程,其他进程处

外发外协外包三者之间的区别与联系

1.应当贯彻顾客为中心原则,通过建立体系来控制过程,确保产品质量.不要化精力在符合标准要求上.如果您 的目标.产品的要求不切合顾客要求的话,就是符合了ISO 9001标准要求,不能满足顾客要求的,符合顾客要求了,顾客满意了,不注意质量成本,经济效益,也就不一定能赚到钱的. 2.7.4条采购要求是,如果您从质量管理体系外得到各种影响产品要求符合性的资源(过程也作为一种资源)的话,就必须对这些产品或者活动(也就是过程) 加以控制的."采购"英文是purchasing,"外包过程&

socket,tcp,http三者之间的区别和原理

http.TCP/IP协议与socket之间的区别下面的图表试图显示不同的TCP/IP和其他的协议在最初OSI模型中的位置: 7 应用层 例如HTTP.SMTP.SNMP.FTP.Telnet.SIP.SSH.NFS.RTSP.XMPP.Whois.ENRP 6 表示层 例如XDR.ASN.1.SMB.AFP.NCP 5 会话层 例如ASAP.TLS.SSH.ISO 8327 / CCITT X.225.RPC.NetBIOS.ASP.Winsock.BSD sockets 4 传输层 例如TC

Android培训准备资料之UI一些相似控件和控件一些相似属性之间的区别

这一篇博客主要收集五大布局中的一些相似控件和控件一些相似属性之间的区别 ImageView ImageButton Button 三者有啥区别? (1)Button继承自TextView,ImageView继承自View,ImageButton继承自ImageView                                              (2)Button支持android:text属性,而ImageButton和ImageView不支持,但是ImageView和ImageB

JavaScript中this和$(this)之间的区别

jQuery中this和$(this)之间的区别: this返回的是当前对象的html对象,而$(this)返回的是当前对象的jQuery对象 举个正确的Demo实例: $("#textbox").hover( function() { this.title = "Test"; }, fucntion() { this.title = "OK”; } ); 以上的this为html元素即元素textbox,该元素有title属性,因此以上的程序没有错误.如

mysql 中execute、executeQuery和executeUpdate之间的区别

在用纯JSP做一个页面报警功能的时候习惯性的用executeQuery来执行SQL语句,结果执行update时就遇到问题,语句能执行,但返回结果出现问题,另外还忽略了executeUpdate的返回值不是结果集ResultSet,而是数值!特收藏如下一篇文章: JDBCTM中Statement接口提供的execute.executeQuery和executeUpdate之间的区别 Statement 接口提供了三种执行 SQL 语句的方法:executeQuery.executeUpdate 和

VS中生成、清理项目、调试、开始执行(不调试)、Debug 和 Release等之间的区别

一.生成和重新生成 "生成"的时候只对你改动过的文件重新生成没有改动过的文件不会重新生成: "重新生成"是对所有的文件都重新生成. 以cpp为例当你只改动某些.cpp之类的文件的时候可以用生成省了编译没有改动的那些些文件的时间:但是改动了某些.h之类的文件最好用重新生成,因为有可能能有些文件包含.h文件也需要重新编译 选择生成或生成解决方案,将只编译自上次生成以来更改过的那些些项目文件和组件 注意 如果解决方案中包括多个项目,则生成命令将变成生成解决方案. 选择重新