preemption,pthread_spin_lock和atomic built-in

时间:2014-03-13 17:03:31

标签: c linux multithreading pthreads atomic

根据这个问题here使用pthread_spin_lock对于锁定一个关键部分是危险的,因为调度程序可能会中断该线程,并且该资源上的其他线程可能会停止旋转

假设我决定从pthread_spin_lock切换到通过原子内置+ compare_and_swap idion实现的锁定:这个东西会改善还是我会遇到这个问题?

由于使用pthread似乎没有什么可以禁用抢占,如果我使用通过原子实现的锁或我可以查看的任何东西,我能做些什么吗?

我有兴趣锁定一个小的关键区域。

1 个答案:

答案 0 :(得分:5)

pthread_mutex_lock通常有一个快速路径,它使用原子操作来尝试获取锁。如果锁不是拥有的,这可能非常快。只有当锁已被保持时,线程才会通过系统调用进入内核。内核获取自旋锁,然后重新尝试获取互斥锁,以防它在第一次尝试后被释放。如果此尝试失败,则将调用线程添加到与互斥锁关联的等待队列,并执行上下文切换。内核还在互斥锁中设置了一个位,表示存在等待线程。

pthread_mutex_unlock也有快速路径。如果等待的线程标志清除,它可以简单地释放锁定。如果设置了该标志,则线程必须通过系统调用进入内核,以便可以唤醒等待的线程。同样,内核必须获取自旋锁,以便它可以操纵其线程控制数据结构。如果没有线程等待,内核就可以释放锁。如果有一个线程正在等待,它将被设置为可运行,并且互斥锁的所有权在没有被释放的情况下被转移。

这个小小的舞蹈有许多微妙的竞争条件,希望一切正常。

由于尝试获取锁定互斥锁的线程是上下文切换出来的,因此它不会阻止线程拥有互斥锁运行,从而使所有者有机会退出其临界区并释放互斥锁。

相反,尝试获取锁定自旋锁的线程只是旋转,消耗CPU周期。这有可能阻止拥有自旋锁的线程退出其关键部分并释放锁定。旋转线程可以在其时间片被消耗时被抢占,从而允许拥有该锁的线程最终重新获得控制权。当然,这对性能来说并不是很好。

实际上,在拥有锁的情况下线程不可能被抢占的情况下使用自旋锁。内核可以设置per-cpu标志以防止它从中断服务例程执行上下文切换(或者它可以提高中断优先级以防止可能导致上下文切换的中断,或者它可以完全禁用中断)。用户线程可以通过提高其优先级来防止自己被抢占(通过同一进程中的其他线程)。请注意,在单处理器系统中,防止当前线程被抢占消除了对自旋锁的需要。或者,在多处理器系统中,您可以将线程绑定到cpus(cpu affinity),以便它们不会相互抢占。

所有锁最终都需要一个原子基元(好的,有效的锁;有关反例,请参阅here)。如果它们具有高度竞争性,则互斥量可能效率低下,导致线程不断进入内核并进行上下文切换;特别是如果临界区小于内核开销。旋转锁可以更有效,但仅当主人不能被抢占且关键部分很短时。请注意,当线程尝试获取锁定的互斥锁时,内核仍必须获取自旋锁。

就个人而言,我会将原子操作用于共享计数器更新,以及用于更复杂操作的互斥锁。只有在进行性能分析后,我才会考虑用自旋锁替换互斥锁(并弄清楚如何处理抢占)。请注意,如果您打算使用condvars,则别无选择,只能使用互斥锁。