我使用自旋锁来保护一个非常小的关键部分。 争很少发生这样一个自旋锁是比常规的互斥体更合适。
我当前的代码如下,并假定x86和海合会:
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的工作吗? 在所有的操作系统?
- 它是最佳的?
- 我一直在使用比较并交换看到自旋锁实现,但我不知道哪一个更好。
- 根据海合会原子内建文件( http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html )还有
__sync_lock_release
。 我不是记忆障碍的专家,所以我不知道它是否没关系,我用这个来代替__sync_synchronize
。 - 我在优化中有没有竞争的情况下。
我根本就不关心争。 有可能是1,也许2其他线程试图每隔几天锁自旋锁。
Answer 1:
所以我想知道:
* Is it correct?
在提到的情况下,我会说是的。
* Is it optimal?
这是一个意味深长的问题。 通过重新发明轮子,你也重塑了很多问题,这些问题都已经解决了通过其他实现
我期望在失败时浪费的循环,你是不是尝试访问锁定字。
在解锁使用全屏障只需要有释放的语义(这就是为什么你会使用__sync_lock_release,这样你会得到关于安腾st1.rel而非MF,或PowerPC处理器的lwsync,...)。 如果你真的只在乎的x86或x86_64的类型,在这里或不使用不事尽可能多的障碍(但如果你在哪里做跳转到英特尔Itanium为HP-IPF端口,那么你不会想这一点)。
你没有那么你会浪费你的循环之前,通常把暂停()指令。
如果出现争你想要的东西 ,执行semop,或在绝望中甚至是愚蠢的睡眠。 如果你真的需要这买你的表现则futex的建议可能是一个好一个。 如果您需要的性能这给你买了坏到足以维持这个代码,你有大量的研究要做。
需要注意的是有一个评论说,没有需要发布的屏障。 这是不正确的,甚至在x86因为离屏障也作为编译器的指令,不推诿其他内存访问周围的“屏障”。 非常喜欢,如果你使用ASM你会得到什么(“” :::“内存”)。
* on compare and swap
在x86的sync_lock_test_and_set将映射到其中有一个隐含的锁前缀的XCHG指令。 绝对是最紧凑的生成代码(尤其是如果你使用一个字节为“锁定字”,而不是一个int),但不超过,如果你使用LOCK CMPXCHG少正确。 比较和交换的使用,可用于发烧友algorthims(就像把非零指针设定为所述第一“侍者”到失败锁定字元数据)。
Answer 2:
看起来好像没什么问题。 顺便说一句,这里是教科书的实现,更有效,即使在争辩情况。
void lock(volatile int *exclusion)
{
while (__sync_lock_test_and_set(exclusion, 1))
while (*exclusion)
;
}
Answer 3:
在回答你的问题:
- 看起来没给我
- 假设OS支持GCC(和GCC具有的功能实现); 这应该对所有的x86操作系统。 GCC的文档表明,如果他们不支持给定平台上的警告会产生。
- 没有什么具体的X86-64在这里,所以我不明白为什么不能。 这可以被扩展到覆盖GCC支持任何建筑,但有可能在非x86架构上实现这一目标的更优化的方式。
- 你可能会使用稍好一些
__sync_lock_release()
在unlock()
情况; 因为这将递减锁和在单一操作中添加的存储器屏障。 但是,假设你的断言,很少会发生竞争; 这对我来说很好。
Answer 4:
如果你在最近的Linux版本,你可以使用一个futex的 - “快速用户空间互斥体”:
一个正确编程的基于futex的锁将无法使用系统调用时锁定发生冲突,除了
在无争议的情况下,你想与你的自旋锁,以优化的futex的将表现就像一个自旋锁,而不需要内核系统调用。 如果锁争夺,等待发生在内核,而不忙等待。
Answer 5:
我不知道下面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");
}
Answer 6:
我不能评论的正确性,但你的问题的标题举着红旗之前,我甚至看问题的身体。 同步原语是过分地努力,以确保正确性......如果可能的话,你最好不要使用一个精心设计/维护库,或许并行线程或提高::线程 。
Answer 7:
一个改进是使用建议塔塔集团 (测试和检查并设置)。 使用CAS操作被认为是相当昂贵的处理器,所以它的更好,如果可能,以避免它们。 另一件事,确保你不会从优先级反转(遭受什么,如果有高优先级的线程试图同时具有低优先级的线程试图释放锁?在Windows例如这个问题的最终要解决获得锁使用优先级提升调度程序,但你可以明确地放弃你的线程的时间片的情况下,你没有在你过去的20次尝试获得锁成功(例如..)
Answer 8:
您的解锁过程不需要内存屏障; 分配给排除是原子只要DWORD在x86对齐。
Answer 9:
在86(32/64)的特定情况下,我不认为你需要的存储栅栏都在解锁代码。 86没有做任何的重新排序,除了商店首先放入存储缓冲区,因此它们变得可见可延迟其他线程。 而任何一家商店,然后一个线程从同一个变量读取将其存储缓冲区读取,如果它尚未被刷新到内存中。 因此,所有你需要的是一个asm
语句来防止编译器重新排序。 你运行一个线程从其他线程的角度比所需稍长持有锁的风险,但如果你不关心的论点是不应该的问题。 事实上, pthread_spin_unlock
实现像我的系统(Linux的x86_64的)上。
我的系统也实现pthread_spin_lock
使用lock decl lockvar; jne spinloop;
lock decl lockvar; jne spinloop;
而不是使用xchg
(这是什么__sync_lock_test_and_set
使用),但我不知道是否确实有一个性能上的差异。
Answer 10:
有几个错误的假设。
首先,自旋锁使得只有当的ressource被锁定在另一个CPU的意义。 如果的ressource锁定同一个CPU(总是在单处理器系统的情况下),你需要为了解锁的ressource放松调度。 您当前的代码将单处理器系统上工作,因为调度机器会自动切换任务,但它的ressource的浪费。
在多处理器系统中,同样的事情可以happends,但任务可以从一个CPU迁移到另一个。 总之,如果你garantee你的任务将不同的CPU上运行使用自旋锁是正确的。
其次,锁定互斥快(快自旋锁)时被解锁。 互斥锁(开锁)慢(很慢)仅当互斥锁已经锁定。
所以,你的情况,我建议使用互斥。
文章来源: Is my spin lock implementation correct and optimal?