30

在哪里可以找到“自适应”pthread 互斥锁的文档?符号 PTHREAD_MUTEX_ADAPTIVE_NP 是在我的系统上定义的,但是我可以在网上找到的唯一文档没有说明自适应互斥锁是什么,或者何时适合使用。

那么......它是什么,我应该什么时候使用它?

作为参考,我的 libc 版本是:

GNU C Library (Ubuntu EGLIBC 2.15-0ubuntu10.5) stable release version 2.15, by Roland McGrath et al.
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.6.3.
Compiled on a Linux 3.2.50 system on 2013-09-30.
Available extensions:
    crypt add-on version 2.1 by Michael Glad and others
    GNU Libidn by Simon Josefsson
    Native POSIX Threads Library by Ulrich Drepper et al
    BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<http://www.debian.org/Bugs/>.

并且“uname -a”给出

Linux desktop 3.2.0-55-generic #85-Ubuntu SMP Wed Oct 2 12:29:27 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
4

3 回答 3

70

PTHREAD_MUTEX_ADAPTIVE_NP是我在以 glibc 贡献者的身份工作时发明的,旨在使 LinuxThreads 更可靠、性能更好。LinuxThreads 是 glibc 的NPTL 库的前身,最初由 Xavier Leroy 作为独立库开发,他也是著名的OCaml的创建者之一。

自适应互斥体以基本未修改的形式在 NTPL 中幸存下来:代码几乎相同,包括估计器平滑的魔法常数和相对于估计器的最大自旋。

在 SMP 下,当您去获取一个互斥体并看到它被锁定时,简单地放弃并调用内核进行阻塞可能不是最佳选择。如果锁的所有者只持有几条指令的锁,那么等待这些指令的执行,然后通过原子操作获取锁会更便宜,而不是通过系统调用花费数百个额外的周期.

内核开发人员非常了解这一点,这也是我们在 Linux 内核中为快速临界区设置自旋锁的原因之一。(当然,其他原因之一是不能休眠的代码,因为它在中断上下文中,可以获取自旋锁。)

问题是,你应该等多久?如果你一直旋转直到获得锁,那可能是次优的。用户空间程序不像内核代码那样写得很好(咳)。他们可能有很长的关键部分。他们也不能禁用抢占;有时,由于上下文切换,关键部分会爆炸。(POSIX 线程现在提供了实时工具来处理这个问题:您可以将线程放入实时优先级和 FIFO 调度等,以及配置处理器亲和性。)

我想我们尝试了固定的迭代次数,但后来我有了这个想法:我们为什么要猜测,什么时候可以测量。为什么我们不实现锁定持续时间的平滑估计器,类似于我们对 TCP 重传超时 (RTO) 估计器所做的事情。每次我们在锁上旋转时,我们都应该测量它实际上需要多少次旋转才能获得它。此外,我们不应该永远旋转:我们可能最多只能旋转当前估计值的两倍。当我们进行测量时,只需几条指令,我们就可以指数级平滑它:取前一个值和新值的一小部分,然后将它们加在一起,这与将它们的差值的一小部分加到后面是一样的对估算者:比如说,estimator += (new_val - estimator)/8旧值和新值之间的 1/8 到 7/8 混合。

您可以将其视为看门狗。假设估算器告诉您,锁平均需要 80 次旋转才能获得。那么,您可以确信,如果您已经执行了 160 次自旋,那么一定是出了问题:锁的所有者正在执行一些异常长的情况,或者可能遇到了页面错误或被抢占。在这一点上,等待线程减少它的损失并调用内核来阻塞。

没有测量,您就无法准确地做到这一点:没有“一刀切”的价值。比如说,200 次旋转的固定限制在一个程序中是次优的,因为它的关键部分非常短,以至于几乎总是可以在等待 10 次旋转后获取锁。每次出现异常等待时间时,互斥锁功能都会消耗 200 次迭代,而不是在 20 次时很好地放弃并节省周期。

这种自适应方法是专门的,因为它不适用于所有程序中的所有锁,因此它被打包为一种特殊的互斥锁类型。例如,对于长时间锁定互斥锁的程序来说,它不会很好地工作:时间太长以至于浪费了更多的 CPU 时间来旋转较大的估计值,而不是进入内核。这种方法也不适合单处理器:除了试图获得锁的线程之外的所有线程都在内核中挂起。该方法也不适用于公平很重要的情况:它是一种机会锁定。无论有多少其他线程一直在等待,无论等待多长时间,或者它们的优先级是什么,一个新线程都可以出现并抢夺锁。

如果您的代码表现良好且关键部分较短且竞争激烈,并且您希望在 SMP 上获得更好的性能,那么自适应互斥锁可能值得一试。

于 2014-08-06T19:50:11.580 回答
6

那里提到了该符号:

http://elias.rhi.hi.is/libc/Mutexes.html

“LinuxThreads 只支持一种互斥体属性:互斥体类型,对于“快速”互斥体,PTHREAD_MUTEX_ADAPTIVE_NP,对于“递归”互斥体,PTHREAD_MUTEX_RECURSIVE_NP,对于“定时”互斥体,PTHREAD_MUTEX_TIMED_NP,或者对于“错误检查”互斥体的 PTHREAD_MUTEX_ERRORCHECK_NP。正如 NP 后缀所表明的那样,这是对 POSIX 标准的非可移植扩展,不应在可移植程序中使用。

互斥锁类型确定如果线程尝试使用 pthread_mutex_lock 锁定它已经拥有的互斥锁会发生什么。如果互斥锁属于“快速”类型,pthread_mutex_lock 只会永远挂起调用线程。如果互斥锁属于“错误检查”类型,则 pthread_mutex_lock 立即返回错误代码 EDEADLK。如果互斥锁属于“递归”类型,则对 pthread_mutex_lock 的调用会立即返回并返回成功代码。拥有互斥锁的线程锁定它的次数记录在互斥锁中。在互斥锁返回解锁状态之前,拥有线程必须调用 pthread_mutex_unlock 相同的次数。

默认互斥锁类型为“定时”,即 PTHREAD_MUTEX_TIMED_NP。

编辑:更新了 jthill 找到的信息(谢谢!)

可以在此处找到有关互斥体标志和 PTHREAD_MUTEX_ADAPTIVE_NP 的更多信息:

“PTHRED_MUTEX_ADAPTIVE_NP 是一种新的互斥体,旨在以牺牲公平性甚至 CPU 周期为代价实现高吞吐量。这个互斥体不会将所有权转移给等待线程,而是允许竞争。此外,通过 SMP 内核,锁定操作使用旋转重试锁定,以避免立即取消调度的成本。”

这基本上暗示了以下几点:在需要高吞吐量的情况下,可以实现这种互斥锁,由于它的本质,需要从线程逻辑中额外考虑。您将必须设计一种算法,该算法可以使用这些属性来实现高吞吐量。从内部(而不是“从内核”)负载平衡的东西,执行顺序并不重要。

有一本非常好的关于 linux/unix 多线程编程的书,它的名字让我无法理解。如果我找到它,我会更新。

于 2013-11-08T16:25:05.110 回答
1

给你。当我读到它时,它是一个非常简单的互斥体,除了使无争用情况快速运行之外,它不关心任何事情。

于 2013-11-11T23:23:07.473 回答