深层次探讨mutex与semaphore之间的区别(中)

简介: 关于Linux内核中的mutex机制,一篇很重要的文档来自内核源码中的Documentation/mutex- design.txt,由Ingo molnar同学起头,标题是"Generic Mutex Subsystem",这篇文档开宗名义,直接将1楼中最后一个问题给端了出来...
关于Linux内核中的mutex机制,一篇很重要的文档来自内核源码中的Documentation/mutex- design.txt,由Ingo molnar同学起头,标题是"Generic Mutex Subsystem",这篇文档开宗名义,直接将1楼中最后一个问题给端了出来(因此我估计这个问题此前已经有很多人骚扰过Ingo等同学了):

"Why on earth do we need a new mutex subsystem, and what's wrong with semaphores?" 前面已经讲过,当struct semaphore中的成员变量为1,就可以用来实现mutex这种东西,而且内核也明确定义了DEFINE_SEMAPHORE宏将count初始化为 1,信号量上的DOWN与UP操作就更不用说了,在内核中也都有很好的实现,难道这种binary semaphore机制还不能满足我们的要求吗,干嘛还非得弄一个新的mutex机制出来呢?

下面是Ingo同学对此的解释,他说“firstly, there's nothing wrong with semaphores. But if the simpler mutex semantics are sufficient for your code, then there are a couple of advantages of mutexes”,就是说,信号量在Linux中的实现是没任何问题的(上来先安抚一下大家躁动的心情),但是mutex的语义相对来说要较信号量要来得 简单,所以如果你的代码若只是想对某一共享资源进行互斥访问的话,那么使用这种简化了的mutex机制可以带来如下的一坨好处。这句话字面上的理解 是,mutex将binary semaphore的实现简化了(the simper mutex),因此如果单纯从互斥的角度,用mutex会有很多好处。 其实后面我们会看到,在内核源码中,相对于semaphore的DOWN和UP实现,因为后期引入的特别针对binary semaphore的性能优化,也就是现在看到的mutex机制,其实现代码要更为复杂。

接下来Ingo列出的一大堆使用mutex的好处,在这个帖子中我们将一条一条地来看,再结合内核源码,看看事实是否的确象他说的那样:

- 'struct mutex' is smaller on most architectures: E.g. on x86, 'struct semaphore' is 20 bytes, 'struct mutex' is 16 bytes. A smaller structure size means less RAM footprint, and better CPU-cache utilization.


这条最好验证,尤其还是x86平台,找个简单的内核模块,打印一下sizeof就可以了。在我的x86-64 32位Linux系统(内核版本2.6.37)上, struct semaphore的大小是16字节,而struct mutex的大小则是20字节,另两台x86-64 64位Linux系统(内核版本3.x)上的结果则是,struct semaphore的大小是24字节,而struct mutex的大小则是32字节。这里不妨看一下struct mutex在内核中的定义:


  1. struct mutex {
  2.         /* 1: unlocked, 0: locked, negative: locked, possible waiters */
  3.         atomic_t count;
  4.         spinlock_t wait_lock;
  5.         struct list_head wait_list;
  6. #if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
  7.         struct task_struct *owner;
  8. #endif
  9. #ifdef CONFIG_DEBUG_MUTEXES
  10.         const char *name;
  11.         void *magic;
  12. #endif
  13. #ifdef CONFIG_DEBUG_LOCK_ALLOC
  14.         struct lockdep_map dep_map;
  15. #endif
  16. };
可以看到stuct mutex的定义其实比semaphore要来得复杂,里面有一些条件编译选项在里面。因为我们实际使用当中很少会使用它的调试功能,但是SMP现在则很普遍,我上面测试用的Linux环境都是多处理器系统。所以,mutex的定义实际上可简化为:

  1. struct mutex {
  2.         /* 1: unlocked, 0: locked, negative: locked, possible waiters */
  3.         atomic_t count;
  4.         spinlock_t wait_lock;
  5.         struct list_head wait_list;
  6.         struct task_struct *owner;
  7. };
对比一下前面struct semaphore的定义你会发现,struct mutex比semaphore多了一个owner指针,因此上面的结果也就不难理解了,指针在32位系统上是4字节,而64位系统则是8字节。我相信 Ingo同学肯定不会胡说八道,那么明显地,相对于Ingo当时写mutex-design.txt时的情形,Linux内核源码发生了变化,这个在 Linux的开发过程中实在是太正常不过的一件事了:文档总是远远落后于代码的更新--大家都忙着写code,而很少有人想着去更新文档。
所以接下来Ingo提到的tighter code的优势,估计对mutex而言也不复存在了... (他本人对mutex相对于semaphore在RAM footprint方面的优势不复存在的最新回复是:"Mutex got larger due to the adaptive spin-mutex performance optimization",因此我很自然地将这句话理解成,由于要实现所谓的“adaptive spin-mutex performance optimization",那么就不惜牺牲了"less RAM footprint, and better CPU-cache utilization",所以我们有理由期待接下来的spin-mutex performance optimization会给mutex带来性能上比较大的提升...)


下面我们来讨论一下mutex所做的性能优化,在将mutex的引入Linux内核这件事上,Ingo同学是带头大哥,喜欢围观Linux内核开发的同学对这厮肯定不会陌生,在我看来,这厮简直是牛逼得一塌糊涂,将kgdb引入内核也是这厮的杰作...
在mutex的性能提升方面,mutex-design.txt文档中有个具体的测试,采用了一个test-mutex的工具,因为google没找到这 个东西,所以我个人猜测是Ingo自己搞出来的东西,本来想趁这两天放假将最新版下的binary semaphore和mutex的性能测试一把的,结果这两天啥都没干成。我本来是想索要那个test-mutex程序的,但是Ingo只是建议采用 perf来做。我自己找了个sysbench,但是还没时间用,貌似这个是针对数据库的。之所以做这个测试,是我想知道采用mutex到底能比 binary semaphore能带来多大的性能提升。

按照Ingo的测试数据,"the mutex based kernel was 2.4 times faster than the semaphore based kernel, _and_ it also had 2.8 times less CPU utilization",因为事先看过mutex的实现源码,所以我对这个数据有点怀疑,这也是为什么我自己要做性能分析的原因。

semaphore和mutex的代码实现中都有fast path和slow path两条路径,所谓的fast path,就是当前的代码直接获得信号量,而所谓的slow path,则是当前代码没能第一时间获得信号量。semaphore和mutex在fast path上性能上的变化应该微乎其微,这个在metex-design.txt文档中也有说明。两者之间最大的差别来自对slow path的处理,先看semaphore,semaphore的slow path的调用链是down_interruptible-->__down_interruptible --> __down_common,  __down_common的代码实现为:


  1. static inline int __sched __down_common(struct semaphore *sem, long state,
  2.                                                                 long timeout)
  3. {
  4.         struct task_struct *task = current;
  5.         struct semaphore_waiter waiter;

  6.         list_add_tail(&waiter.list, &sem->wait_list);
  7.         waiter.task = task;
  8.         waiter.up = 0;

  9.         for (;;) {
  10.                 if (signal_pending_state(state, task))
  11.                         goto interrupted;
  12.                 if (timeout = 0)
  13.                         goto timed_out;
  14.                 __set_task_state(task, state);
  15.                 raw_spin_unlock_irq(&sem->lock);
  16.                 timeout = schedule_timeout(timeout);
  17.                 raw_spin_lock_irq(&sem->lock);
  18.                 if (waiter.up)
  19.                         return 0;
  20.         }

  21.  timed_out:
  22.         list_del(&waiter.list);
  23.         return -ETIME;

  24.  interrupted:
  25.         list_del(&waiter.list);
  26.         return -EINTR;
  27. }
相对mutex对slow path的处理,semaphore要简单多了,它的主要流程是设置当前进程状态为TASK_INTERRUPTIBLE,然后睡眠到一个等待队列中。所 以semaphore如果第一时间没有获得信号量,那么它接下来就会sleep。但是mutex的slow path呢,所有关于性能优化的代码都集中在该条路径中,所有它看起来比semaphore复杂许多...

相关文章
|
4月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
74 1
|
16天前
|
安全 Java 开发者
Lock锁和AQS之间的关系与区别:基于第一原理的深入分析
【11月更文挑战第26天】在Java并发编程中,锁(Lock)和队列同步器(AbstractQueuedSynchronizer,简称AQS)是两个核心概念。它们为多线程环境下的同步和互斥提供了强大的支持。本文将从第一原理出发,深入探讨Lock锁和AQS之间的关系与区别,同时分析它们的相关概念、业务场景、历史背景、功能点以及底层原理,并使用Java代码进行模拟实现。
16 1
|
4月前
Semaphore原理
文章概述了Semaphore的工作原理及其使用场景:Semaphore是一种有效的限流工具,可以用来控制同一时刻访问共享资源的线程数量。它适用于需要对资源访问进行限流的场景,以避免资源过载或实现公平的资源分配。
Semaphore原理
|
7月前
|
算法 安全 Unix
【C++ 20 信号量 】C++ 线程同步新特性 C++ 20 std::counting_semaphore 信号量的用法 控制对共享资源的并发访问
【C++ 20 信号量 】C++ 线程同步新特性 C++ 20 std::counting_semaphore 信号量的用法 控制对共享资源的并发访问
192 0
|
7月前
|
安全 C++ 开发者
【C++多线程同步】C++多线程同步和互斥的关键:std::mutex和相关类的全面使用教程与深度解析
【C++多线程同步】C++多线程同步和互斥的关键:std::mutex和相关类的全面使用教程与深度解析
96 0
Semaphore使用及原理解读
Semaphore使用及原理解读
Semaphore使用及原理解读
|
Java 测试技术
Semaphore原理剖析
Semaphore原理剖析
149 0
|
缓存 Java
理论:第八章:线程是什么,有几种实现方式,它们之间的区别是什么,线程池实现原理,JUC并发包,ThreadLocal与Lock和Synchronize区别
理论:第八章:线程是什么,有几种实现方式,它们之间的区别是什么,线程池实现原理,JUC并发包,ThreadLocal与Lock和Synchronize区别
129 0
|
API C# Windows
C#多线程(4):进程同步Mutex类
C#多线程(4):进程同步Mutex类
263 0
C#多线程(4):进程同步Mutex类
|
安全 C# 数据安全/隐私保护
C#(四十三)之线程Mutex互斥
Mutex(互斥体): 排他性的使用共享资源称为线程间的互斥。 使用Mutex类要比使用monitor类消耗更多的系统资源,但他可以跨越多个应用程序,在多个应用程序间同步。
215 0
C#(四十三)之线程Mutex互斥