信号量和自旋锁之间的基本区别是什么?
什么时候在自旋锁上使用信号灯?
自旋锁和信号灯的主要区别在于四点:
1.他们是什么 一个 自旋锁 是一个可能实现的锁,即一个由忙等待(“旋转”)来实现。信号量是锁的概括(或者相反,锁是信号量的特例)。通常( 但不是必须) ,自旋锁仅在一个进程内有效,而信号量也可用于在不同进程之间进行同步。
锁用于互斥,即 一次一个 线程可以获取该锁并继续执行代码的“关键部分”。通常,这意味着修改一些由多个线程共享的数据的代码。 一个 信号 有一个计数器,并允许自己被收购 一个或多个 线程,这取决于你的价值张贴到它的东西,(在一些实现),这取决于它的最大允许值是什么。
就此而言,可以认为锁是信号量的一种特殊情况,最大值为1。
2.他们的工作 如上所述,自旋锁是一种锁,因此是一种互斥(严格从1到1)的机制。它通常通过原子方式反复查询和/或修改内存位置来工作。这意味着获取自旋锁是“忙碌”的操作,可能长时间(甚至永远!)消耗CPU周期,而实际上却没有实现“空无”。 这种方法的主要诱因是这样一个事实,即上下文切换的开销相当于旋转几百(或数千)次,因此,如果可以通过燃烧几个循环来获得锁,那么总体来说可能是更高效。同样,对于实时应用程序,阻塞并等待调度程序在将来某个遥远的时间返回到它们可能是不可接受的。
相比之下,信号量根本不旋转,或者仅旋转很短的时间(作为避免syscall开销的优化)。如果无法获取信号量,它将阻塞,从而将CPU时间浪费在准备运行的其他线程上。当然,这可能意味着在重新安排线程之前要经过几毫秒,但是如果这没问题(通常不是问题),那么它可能是一种非常有效的,CPU节约的方法。
3.它们在出现拥塞时的行为 常见的误解是自旋锁或无锁算法“通常更快”,或者仅对“非常短的任务”有用(理想情况下,不应将同步对象保留更长时间)绝对没有必要)。 一个重要的区别是 存在拥塞时 不同方法的行为。
设计良好的系统通常拥塞少或没有拥塞(这意味着并非所有线程都试图在同一时间获取锁)。例如,通常 不会 编写获取锁的代码,然后从网络加载半兆字节的zip压缩数据,对数据进行解码和解析,最后修改共享的引用(将数据附加到容器等)。释放锁之前。取而代之的是,仅出于访问 共享资源 的目的而获得锁。 由于这意味着在关键部分之外的工作要多于在关键部分内部的工作,因此自然而然地,在关键部分内部的线程的可能性相对较低,因此很少有线程同时争用锁。当然,不时有两个线程会尝试同时获取锁(如果 不可能的 话,您就不需要锁!),但这是一个例外,而不是“健康”系统中的规则。
在这种情况下,自旋锁的性能 大大 优于信号量,因为如果没有锁拥塞,则获取自旋锁的开销仅为十几个周期,而上下文切换则为数百/数千个周期,而丢失则为10-20百万个周期时间片的其余部分。
另一方面,由于拥塞程度很高,或者如果锁被长时间持有(有时您无能为力!),自旋锁将消耗大量的CPU周期,以至于什么也没做。 在这种情况下,信号灯(或互斥锁)是一个更好的选择,因为它允许不同的线程在这段时间内运行 有用的 任务。或者,如果没有其他线程有用,则它允许操作系统降低CPU的速度并减少热量/节约能源。
此外,单核系统上,一个自旋锁会在锁拥塞存在相当低效,作为纺丝线会浪费的状态改变是不可能发生的(直到释放线程调度,其中其完整的时间等待 ISN 等待线程正在运行时 不会发生 !)。因此,在有 任何 争用的情况下,在最佳情况下获取锁大约需要1 1/2个时间片(假设释放线程是正在计划的下一个线程),这不是很好的行为。
4.如何实现 如今,信号量通常会sys_futex在Linux下包装(可选地,自旋锁会在几次尝试后退出)。 自旋锁通常使用原子操作来实现,而不使用操作系统提供的任何操作。在过去,这意味着使用编译器内部函数或非便携式汇编程序指令。同时,C ++ 11和C11都将原子操作作为语言的一部分,因此,除了编写可证明正确的无锁代码的一般困难之外,现在有可能在完全可移植且(几乎)可移植的代码中实现无锁代码。无痛的方式。
sys_futex