gpt4 book ai didi

c++ - 自旋锁与信号量

转载 作者:IT老高 更新时间:2023-10-28 11:52:35 27 4
gpt4 key购买 nike

信号量和自旋锁之间的基本区别是什么?

我们什么时候在自旋锁上使用信号量?

最佳答案

自旋锁和信号量的区别主要有四点:

1.它们是什么
自旋锁是锁的一种可能实现,即通过忙等待(“自旋”)实现的一种。信号量是锁的泛化(或者,反过来说,锁是信号量的特例)。通常,但不一定,自旋锁仅在一个进程内有效,而信号量也可用于在不同进程之间进行同步。

锁用于互斥,即 线程一次可以获取锁并继续执行代码的“关键部分”。通常,这意味着修改由多个线程共享的某些数据的代码。
信号量有一个计数器,并允许它自己被 获取。一个或几个线程,取决于您发布给它的值,以及(在某些实现中)取决于它的最大允许值是什么。

就此而言,可以将锁视为最大值为 1 的信号量的特殊情况。

2.他们做什么
如上所述,自旋锁是一种锁,因此是一种互斥(严格来说是 1 对 1)机制。它通过重复查询和/或修改内存位置来工作,通常以原子方式。这意味着获取自旋锁是一个“忙碌”的操作,它可能会长时间(可能永远!)消耗 CPU 周期,而它实际上“什么也没有”。
这种方法的主要动机是上下文切换的开销相当于旋转数百(或数千)次,因此如果可以通过燃烧几个循环旋转来获取锁,这总体上很可能是更有效率。此外,对于实时应用程序,阻塞并等待调度程序在将来某个很远的时间返回它们可能是 Not Acceptable 。

相比之下,信号量要么根本不旋转,要么只旋转很短的时间(作为避免系统调用开销的优化)。如果无法获取信号量,它将阻塞,将 CPU 时间分配给准备运行的不同线程。这当然可能意味着在您的线程再次被调度之前经过几毫秒,但如果这没有问题(通常不是),那么它可能是一种非常有效的、CPU 保守的方法。

3.它们在拥塞情况下的行为
自旋锁或无锁算法“通常更快”,或者它们仅对“非常短的任务”有用(理想情况下,任何同步对象都不应持有超过绝对必要的时间),这是一种常见的误解。
一个重要的区别是不同方法在拥塞情况下的表现。

设计良好的系统通常拥塞很少或没有拥塞(这意味着并非所有线程都尝试在完全相同的时间获取锁)。例如,通常不会编写获取锁的代码,然后从网络加载半兆字节的 zip 压缩数据,解码和解析数据,最后修改共享引用(将数据附加到容器等)。在释放锁之前。相反,人们只会为了访问共享资源的目的而获取锁。
由于这意味着临界区外的工作比临界区内的工作要多得多,因此线程在临界区内的可能性自然相对较低,因此很少有线程同时争用锁。当然,两个线程会时不时地尝试同时获取锁(如果这不会发生,您就不需要锁!),但这与其说是“健康”系统中的规则,不如说是异常(exception).

在这种情况下,自旋锁的性能大大优于信号量,因为如果没有锁拥塞,获取自旋锁的开销仅为几十个周期,而上下文切换需要数百/数千个周期,丢失需要 10-2000 万个周期时间片的剩余部分。

另一方面,考虑到高拥塞,或者如果锁被持有很长时间(有时你就是情不自禁!),自旋锁会燃烧疯狂数量的 CPU 周期而一事无成。
在这种情况下,信号量(或互斥量)是更好的选择,因为它允许不同的线程在此期间运行有用的任务。或者,如果没有其他线程可以做一些有用的事情,它允许操作系统降低 CPU 速度并减少热量/节约能源。

此外,在单核系统上,自旋锁在锁拥塞的情况下效率非常低,因为自旋线程将浪费其全部时间等待不可能发生的状态更改(直到调度释放线程,这是等待线程运行时不会发生!)。因此,考虑到任何数量的争用,在最佳情况下(假设释放线程是下一个被调度的线程),获取锁需要大约 1 1/2 个时间片,这不是很好的行为。

4. 如何实现
现在信号量通常会包裹 sys_futex在 Linux 下(可选带有在几次尝试后退出的自旋锁)。
自旋锁通常使用原子操作实现,而不使用操作系统提供的任何东西。过去,这意味着使用编译器内部函数或不可移植的汇编器指令。同时,C++11 和 C11 都将原子操作作为语言的一部分,因此除了编写可证明正确的无锁代码的一般困难之外,现在可以在完全可移植和(几乎)无痛的方式。

关于c++ - 自旋锁与信号量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/195853/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com