【问题标题】:Mutex implementation and signaling互斥体实现和信令
【发布时间】:2013-02-11 23:11:45
【问题描述】:

当一个 mutex 已经被 T1 锁定,而 T2 试图锁定它时,T2 的流程是什么?

我认为它是这样的:

-T2 尝试锁定,失败,可能自旋锁有点,然后调用 yield...
-T2 计划执行几次,尝试锁定失败,产生...
- 最终 T1 解锁,T2 被安排执行并设法锁定互斥锁......

T1 解锁是否明确向调度程序或其他线程发出互斥锁已解锁的信号?或者它只是解锁,让调度程序在感觉合适时调度阻塞线程(又名调度程序没有阻塞线程的概念,也不将它们视为特殊)?

【问题讨论】:

  • I think 互斥原则保证没有2个或更多线程同时进入临界区。现在,您是否有一个线程队列试图锁定互斥锁,或者您只是忙于等待互斥锁空闲,这取决于您如何实现它以及您对互斥锁的需求是什么。例如,mutex 在 VxWorks 等 RTOS 中实现了优先级上限协议,而在通用操作系统 (GPOS) 中可能不需要。

标签: c++ c mutex implementation


【解决方案1】:

这取决于您的操作系统。我已经看到了旋转,使用yield 旋转,内核中的通用条件变量,用户态控制的调度和具有内核支持的专用锁定原语。

使用yield 旋转和旋转具有糟糕的性能。从理论上讲,用户态控制调度(参见Scheduler activations)应该有最好的性能,但据我所知,没有人让它在所有情况下都能正常工作。内核中的通用条件变量和具有内核支持的专用锁定原语的性能应该与 Linux 中的 futex 大致相同,后者是后者的最佳示例。

在某些情况下,旋转可以获得更好的性能。在 Solaris 中,内核中的某些锁定原语具有自适应模式,只要持有锁定的进程在不同的 cpu 上运行,锁定就会旋转。如果锁所有者休眠或被抢占,锁服务员也会进入休眠状态。在其他内核中,有一些锁的所有者在持有锁时不能被抢占或休眠,因此在这些情况下旋转也能很好地工作。但总的来说,特别是在用户态,旋转有如此可怕的退化情况(旋转过程旋转直到它被抢占让锁所有者运行),这对性能非常不利。请注意,像 futex 这样的专用锁定原语可以实现这样的优化,这是通用条件变量通常无法做到的。

【讨论】:

  • 不错的 A,但是“Spinning and spin with yield have bad performance”应该是合格的......我可以想象自旋锁速度很快的低争用和低工作量的场景。
  • 在没有争用的情况下,所有锁都(相当)有效。当竞争激烈时,设计良好的锁定方法也很有效。它还取决于可用处理器的数量——在单处理器系统上“旋转”等待是可怕的,因为当前持有锁的其他线程将无法运行,并且 T2 正在使用所有 CPU 来检查 T1 是否没有运行的人已经释放了锁——这对 CPU 时间的使用是非常无用的。
  • @Mats 我在考虑多核系统,但我同意你的单核系统。
【解决方案2】:

简而言之:是的,也许……

这是实现细节,如果不知道您在谈论哪个操作系统,就很难说。通常,解锁互斥体只会将等待线程标记为“可运行”,但不会(必然)在那时调用调度程序——即使调用了调度程序,也不意味着 T2 将成为下一个线程跑步。

在 Linux 中,代码进入mutex_unlock(),它检查是否有任何等待任务(通过检查锁计数是否小于零 - 它从 1 开始表示未锁定,单个锁请求将其变为零,进一步尝试锁定将使其为负数)。如果还有进一步的等待过程,它会调用“慢速路径解锁”,通过几个重定向函数来允许实现细节,最终在__mutex_unlock_common_slowpath - 再往下几行,调用wake_up_process最终以try_to_wake_up 结束 - 它基本上只是将任务排队为“准备运行”,然后调用调度程序(通过多层函数!)

【讨论】:

  • 所以你是说在linux线程上阻塞被标记为不可运行(所以sched在它被标记为可运行之前不会安排它)
  • 是的,没错。这假设您使用的是内核互斥体,这可能不是例如 pthread_mutex 实现的。
  • 很好的答案...顺便说一句,不用我打扰您...您能快速说出 futex 是否在某些重要方面与此不同。
  • 我快速浏览了futex 代码,它看起来很不一样,并且是内核中更大的功能......
【解决方案3】:

假设我们有以下场景:

 1. T1 got M1. M1 locked.
 2. T2 tries to get M1 and gets blocked as M1 is locked.
 3. T3 tries to get M1 and gets blocked as M1 is locked.
 4. ...some time later...
 5. T1 unlocks M1.*
 6. T2 got M1.
 7. T3 is unblocked and tries to get M1 but is blocked again as T2 got M1 first.

*系统调用,unlock,应该通知所有阻塞的任务/进程/线程,这些阻塞在互斥锁的锁上调用。然后它们被安排执行。这并不意味着他们被执行,因为可能已经有人在执行。正如其他人所说,这取决于实现方式。如果你真的想学好这个我推荐这个book

【讨论】:

    猜你喜欢
    • 2011-04-10
    • 2013-12-07
    • 2013-12-30
    • 1970-01-01
    • 2011-08-25
    • 2011-04-20
    • 2015-08-21
    • 2023-04-09
    • 2011-06-24
    相关资源
    最近更新 更多