我已经阅读了一些帖子,说比较和交换保证原子性,但是我仍然无法得到它是怎么回事。这是比较和交换的通用伪代码:
int CAS(int *ptr,int oldvalue,int newvalue)
{
int temp = *ptr;
if(*ptr == oldvalue)
*ptr = newvalue
return temp;
}
这如何保证原子性?例如,如果我使用它来实现互斥锁,
void lock(int *mutex)
{
while(!CAS(mutex, 0 , 1));
}
这如何防止2个线程同时获取互斥锁?任何指针都会非常感激。
答案 0 :(得分:20)
"一般伪代码"不是CAS(比较和交换)实现的实际代码。特殊硬件指令用于激活CPU中的特殊原子硬件。例如,在x86中,可以使用LOCK CMPXCHG
(http://en.wikipedia.org/wiki/Compare-and-swap)。
例如,在gcc中,内置了__sync_val_compare_and_swap()
- 它实现了特定于硬件的原子CAS。有关此操作的描述来自Paul E. McKenney的新书(Is Parallel Programming Hard, And, If So, What Can You Do About It?,2014),第4.3和34页;原子操作",第31-32页。
如果您想了解更多关于在原子操作之上构建更高级别同步并在自动旋转中保存系统免受自旋锁和烧录cpu周期的更多信息,您可以在Linux中阅读有关futex
机制的内容。关于futexes的第一篇论文是由Ulrich Drepper 2011撰写的Futexes are tricky;另一个是LWN文章http://lwn.net/Articles/360699/(历史文章是Fuss, Futexes and Furwocks: Fast Userland Locking in Linux,2002年)
Ulrich描述的互斥锁仅使用原子操作来快速路径" (当互斥锁未被锁定且我们的线程是唯一想要锁定它的线程时),但是如果互斥锁被锁定,线程将使用futex(FUTEX_WAIT ...)进入休眠状态(它将使用互斥锁变量标记)原子操作,通知解锁线程"有人正在等待这个互斥锁",因此解锁器将知道他必须使用futex唤醒它们(FUTEX_WAKE,...)
答案 1 :(得分:3)
它如何阻止两个线程获取锁定?好吧,一旦任何一个线程成功,*mutex
将是1
,因此任何其他线程的CAS都将失败(因为它被调用期望值0
)。通过在0
中存储*mutex
来释放锁定。
请注意,这是CAS的一种奇怪用法,因为它基本上要求违反ABA。通常你只使用普通的原子交换:
while (exchange(mutex, 1) == 1) { /* spin */ }
// critical section
*mutex = 0; // atomically
或者如果你想要稍微复杂并存储有关哪个线程有锁的信息,你可以使用atomic-fetch-and-add做一些技巧(参见例如Linux内核自旋锁代码)。
答案 2 :(得分:0)
您无法在C中实现CAS。这是在组装时在硬件级别完成的。