我的自旋锁实现是否正确和最佳?

我正在使用旋转锁来保护非常小的关键部分。 争用很less发生,所以自旋锁比普通的互斥锁更合适。

我现在的代码如下,并假定x86和GCC:

volatile int exclusion = 0; void lock() { while (__sync_lock_test_and_set(&exclusion, 1)) { // Do nothing. This GCC builtin instruction // ensures memory barrier. } } void unlock() { __sync_synchronize(); // Memory barrier. exclusion = 0; } 

所以我想知道:

  • 这段代码是否正确? 它是否正确确保相互排斥?
  • 它是否适用于所有x86操作系统?
  • 它也可以在x86_64上运行吗? 在所有操作系统上?
  • 这是最佳的吗?
    • 我已经看到使用比较和交换的自旋锁实现,但我不知道哪个更好。
    • 根据GCCprimefacesbuild立的文档( http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html )还有__sync_lock_release 。 我不是内存障碍方面的专家,所以我不确定是否可以使用这个来代替__sync_synchronize
    • 我正在优化的情况下,没有争议。

我根本不在意争论。 可能有1个,也许2个其他线程每隔几天试图locking一次旋转locking。

所以我想知道:

 * Is it correct? 

在上述情况下,我会说是的。

 * Is it optimal? 

这是一个加载的问题。 通过重新发明轮子,你也重塑了许多其他实现已经解决的问题

  • 我希望在没有尝试访问locking字的地方出现一个浪费循环。

  • 在解锁中使用完整的屏障只需要释放语义(这就是为什么你会使用__sync_lock_release,所以你会得到st1.rel在itanium上,而不是mf,或powerpc上的lwsync,…)。 如果你真的只关心x86或者x86_64,那么这里使用的或者没有的障碍types就不重要了(但是如果你在哪里跳转到intel的itanium,那么你就不需要这个了)。

  • 您没有在废物循环之前通常放置的暂停()指令。

  • 当有争用时,你想要什么 ,放弃,甚至是绝望的沉睡。 如果你真的需要这个购买你的performance,那么futex的build议可能是一个好的。 如果你需要这样的性能,那么你买了足够坏的代码来维护这个代码,你就有很多研究要做。

请注意,有评论说释放障碍不是必需的。 即使在x86上,也不是这样,因为释放屏障也可以作为编译器的指令,不对其他内存进行随机访问。 非常像如果你使用asm (“”:::“memory”)会得到什么。

 * on compare and swap 

在x86上,sync_lock_test_and_set将映射到具有隐含的锁前缀的xchg指令。 绝对是最紧凑的生成代码(尤其是如果你使用一个字节作为“locking字”而不是一个int),但不会比使用LOCK CMPXCHG更正确。 比较和交换的使用可以用于发烧友algorthims(比如,在失败的情况下将第一个“服务员”的元数据放入locking字的非零指针)。

对我看起来很好。 顺便说一句,这是教科书的实施,即使在争议的情况下,也更有效率。

 void lock(volatile int *exclusion) { while (__sync_lock_test_and_set(exclusion, 1)) while (*exclusion) ; } 

回答你的问题:

  1. 看起来对我好
  2. 假设操作系统支持GCC(并且GCC具有实现的function); 这应该适用于所有x86操作系统。 海湾合作委员会的文件build议,如果在特定的平台上不支持,将会产生一个警告。
  3. 这里没有什么特定的x86-64,所以我不明白为什么。 这可以扩展到涵盖GCC支持的任何体系结构,但在非x86体系结构中可能有更多的最佳实现方式。
  4. unlock()情况下使用__sync_lock_release()可能会稍微好一些; 因为这会减lesslocking并在单个操作中添加内存屏障。 但是,假设你的主张很less有争议; 这对我来说很好。

如果您使用的是最新版本的Linux,则可以使用futex – “快速用户空间互斥体”:

一个正确编程的基于futex的锁不会使用系统调用,除非锁被争用

在无可争议的情况下,您尝试使用自旋锁进行优化,futex的行为就像自旋锁一样,不需要内核系统调用。 如果锁有争用,则在内核中等待而不等待。

我不能评论正确性,但是在我看完问题之前,你的问题的标题就引发了一面红旗。 同步原语非常难以确保正确性…如果可能的话,最好使用精心devise/维护的库,可能是pthread或boost :: thread 。

我想知道下面的CAS实现在x86_64上是否是正确的。 在我的i7 X920笔记本电脑上(Fedora 13 x86_64,gcc 4.4.5)几乎快了两倍。

 inline void lock(volatile int *locked) { while (__sync_val_compare_and_swap(locked, 0, 1)); asm volatile("lfence" ::: "memory"); } inline void unlock(volatile int *locked) { *locked=0; asm volatile("sfence" ::: "memory"); } 

一个改进是build议使用TATAS (testing和testing和设置)。 使用CAS操作对于处理器而言是相当昂贵的,因此如果可能的话最好避免使用它们。 另一件事,确保你不会遭受优先倒置(如果一个优先级较高的线程尝试获取锁,而一个低优先级的线程试图释放锁,那么该怎么办?在Windows上,这个问题最终将由调度程序使用优先级提升,但是你可以明确地放弃你的线程的时间片,以防你没有成功获得你最近20次尝试的锁(例如..)

你的解锁程序不需要记忆障碍; 只要它在x86上alignment,排除的分配就是primefaces的。

在x86(32/64)的具体情况下,我不认为你需要在解锁码的内存围栏。 x86不会进行任何重新sorting,除非存储首先放入存储缓冲区,所以它们变得可见可以延迟其他线程。 如果一个线程执行了一个存储,然后从同一个variables中读取,它将从其存储缓冲区中读取,如果它尚未刷新到内存。 所以你只需要一个asm语句来防止编译器重新sorting。 从其他线程的angular度来看,一个线程持有锁的时间略长于必要的风险,但是如果你不关心哪个争用是不重要的话。 事实上, pthread_spin_unlock就像我的系统(linux x86_64)一样实现。

我的系统也使用lock decl lockvar; jne spinloop;来实现pthread_spin_lock lock decl lockvar; jne spinloop; lock decl lockvar; jne spinloop; 而不是使用xchg (这是什么__sync_lock_test_and_set使用),但我不知道是否有实际的性能差异。

有一些错误的假设。

首先,只有当资源被locking在另一个CPU上时,SpinLock才有意义。 如果资源被locking在相同的CPU上(这在单处理器系统中总是如此),则需要放松调度程序才能解锁资源。 你当前的代码将在单处理器系统上工作,因为调度程序会自动切换任务,但是浪费了资源。

在多处理器系统上,同样的事情会发生,但任务可能会从一个CPU迁移到另一个CPU。 简而言之,如果您保证您的任务将在不同的CPU上运行,则使用旋转locking是正确的。

其次,当解锁时,locking互斥锁的速度快(与自旋锁一样快)。 只有互斥锁已经locking,互斥锁(和解锁)才会很慢(很慢)。

所以,在你的情况下,我build议使用互斥体。