如题,如何用最小的代价来实现同步呢,即无锁编程(主要是使用原子操作替代锁来实现对共享资源的访问保护),
我们来看看下面的代码。
首先,我们需要是在这个函数内部对地址x中的值进行位测试并将其对应的位置位。 这里的关键是我们必须先读出*x的值,作位或运算之后,将得到的值对*x赋值。而在多线程环境中,在读出*x后也许有另外的线程对*x进行写操作,这样在给*x赋值的时候就会把对旧的值位或的结果赋给*x,造成错误。
这里boost是采用一个循环,将读出的值先保存,这样,读出后的值就为一个不变量,然后计算位或,当然也为不变量。接着用win32中的atom函数interlockedCompareExchange()来对比*x是否在被读出值后改变,如果没改变,则赋值;反之,则更新被保存的变量,再重复判断,直到第前面的情况发生。
也许有人会说,如果我这样写呢,
long const current=BOOST_INTERLOCKED_COMPARE_EXCHANGE(x,*x|value,*x);
是不是就可以避免读和写不一致。错!你这样还是读了共享变量,而且是2次!2次读出的值都可能不一样,使得可能被位或地值都不一致。而且是在进入atom函数后才是同步代码,而读共享变量值得时候是在函数外部,还需要压栈,完全可能不同步。
inline bool interlocked_bit_test_and_set(long* x,long bit)
{
long const value=1<<bit;
long old=*x;
do
{
long const current=BOOST_INTERLOCKED_COMPARE_EXCHANGE(x,old|value,old);
if(current==old)
{
break;
}
old=current;
}
while(true);
return (old&value)!=0;
}
以上的函数所用的方法保证了对一个共享值的读-针对读出的值作操作-将操作后的值写回,这3步能够保持同步。我可以类举出win32中interlockedExchangeAdd() <即i++>的实现方法:
inline void interlockedExchangeAdd(long* x)
{
long old=*x;
do
{
long const current=BOOST_INTERLOCKED_COMPARE_EXCHANGE(x,old + 1,old); //
比较并交换操作的作用是在一个原子操作内,先将目标值和老的值进行比较,如果相等就将新的值赋给目标变量。目的是为了防止将新的值赋给目标变量前,在读写操作之间目标变量遭到其他线程的修改。
if(current==old)
{
break;
}
old=current;
}
while(true);
}
以上就是lock-free编程中的经典的CAS(compare and swap)的实现,除此外Lock-Free编程还有三大优点:
虽然Lock-Free编程非常困难,但是它通常可以带来比基于锁编程更高的吞吐量。所以Lock-Free编程是大有前途的技术。它在线程中止、优先级倒置以及信号安全等方面都有着良好的表现。
但是CAS会出现ABA问题,这就不在我们的讨论范围内了。转个ABA的解决:
即定义额外的标识之类得方法来区别第一次A和第二次的A(不重用A),使得CAS能够察觉变化。