原 子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中, 能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源 互斥的原因。
在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。
在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中 的原子性。
原子整数操作最常见的用途就是实现计数器。使用复杂的锁机制来保护一个单纯的计数器是很笨拙的,所以,开发者最好使用atomic_inc()和atomic_dec()这两个相对来说轻便一点的操作。
还可以用原子整数操作原子地执行一个操作并检查结果。一个常见的例子是原子的减操作和检查。
int atomic_dec_and_test(atomic_t *v)
这个函数让给定的原子变量减1,如果结果为0,就返回1;否则返回0
原子操作(1) - 用汇编实现原子操作
“最轻量级的锁”,通常也叫”原子操作”,之所以加引号是因为他们在汇编级别并不是原子操作,是用多条指令完成的,这些操作大多都是利用CPU支持的汇编指令.在某些构架过时的CPU体系结构上,它们应该是用比较重量级的线程内锁实现的吧(我的猜测).
最常见的原子操作有Compare and Exchange,Self Increase/Decrease等等
80486 CPU相关指令:
LOCK:这是一个指令前缀,在所对应的指令操作期间使此指令的目标操作数指定的存储区域锁定,以得到保护。
XADD:先交换两个操作数的值,再进行算术加法操作。多处理器安全,在80486及以上CPU中支持。
CMPXCHG:比较交换指令,第一操作数先和AL/AX/EAX比较,如果相等ZF置1,第二操作数赋给第一操作数,否则ZF清0,第一操作数赋给AL/AX/EAX。多处理器安全,在80486及以上CPU中支持。
XCHG:交换两个操作数,其中至少有一个是寄存器寻址.其他寄存器和标志位不受影响.
80486以上都支持这四个操作,因此当今几乎100%CPU都支持这两个指令,也能由此用标准C和C++写出一系列几乎可以跨平台的”原子操作”函数和Lock-Free数据结构和算法.
64位平台也有一系列的相关指令,当然他们也有以上提到的指令,其下的64位原子操作函数应该和32位的分开(要问为什么?我现在告诉你恐怕你印象不深,接着看这一系列吧),而道理完全一样.因此,不存在跨CPU体系结构的问题)
“原子操作”函数:
由以上提供的几个汇编指令,我们可以做出以下实现,这些都是最常用的原语.
比较后交换
long __stdcall CompareExchange(long volatile*Destination,long Exchange,long Comperand)
{
__asm
{
mov ecx, Destination;
mov edx, Exchange;
mov eax, Comperand;
lock cmpxchg [ecx], edx;
}
}
交换
long __stdcall Exchange(long volatile* Target,long Value)
{
__asm
{
mov ecx, Target;
mov edx, Value;
label:
lock cmpxchg [ecx], edx;//加
jnz short label;
}
}
自减
long __stdcall Decrement(long volatile* Addend)
{
__asm
{
mov ecx, Addend;
mov eax, 0FFFFFFFFh;//-1
lock xadd [ecx], eax; //加-1
dec eax;
}
}
自增
long __stdcall Increment(long volatile* Addend)
{
__asm
{
mov ecx, Addend;
mov eax, 1;
lock xadd [ecx], eax; //加
inc eax;
}
}
相加后交换
long __stdcall ExchangeAdd(long volatile* Addend,long Value)
{
__asm
{
mov ecx, Addend;
mov eax, Value;
lock xadd [ecx], eax;
}
}
原子操作(2) - 泛型后的原子操作
32位的数据类型有4种,但是上面的只支持long,怎么办?手工hack?太土了,当然是C++的大规模杀伤性武器template了.
同时把几个不跨平台的地方抽出来用macro表示.
目前模板还没有得到concept的支持,所以只能用boost.type_traits低级的手动判断,要求只有32位的整数类型才能实例化这些函数.
#include
#include
#define CALL_METHOD __stdcall
#define VOLATILE volatile
template<typename T>
T CALL_METHOD compare_exchange32(T VOLATILE*Destination,T exchange32,T Comperand)
{
BOOST_STATIC_ASSERT(sizeof(T) == 4 && boost ;::is_integral<T>::value);
__asm
{
mov ecx, Destination;
mov edx, exchange32;
mov eax, Comperand;
lock cmpxchg [ecx], edx;
}
}
template<typename T>
T CALL_METHOD exchange32(T VOLATILE* Target,T Value)
{
BOOST_STATIC_ASSERT(sizeof(T) == 4 && boost::is_integral<T>::value);
__asm
{
// mov ecx, Target;
// mov edx, Value;
//label:
// lock cmpxchg [ecx], edx;//加
// jnz short label;
mov ecx, Target;
mov eax, Value;
xchg [ecx],eax;
}
}
template<typename T>
T CALL_METHOD decrement32(T VOLATILE* Addend)
{
BOOST_STATIC_ASSERT(sizeof(T) == 4 && boost::is_integral<T>::value);
__asm
{
mov ecx, Addend;
mov eax, 0FFFFFFFFh;//-1
lock xadd [ecx], eax; //加-1
dec eax;
}
}
template<typename T>
T CALL_METHOD increment32(T VOLATILE* Addend)
{
BOOST_STATIC_ASSERT(sizeof(T) == 4 && boost::is_integral<T>::value);
__asm
{
mov ecx, Addend;
mov eax, 1;
lock xadd [ecx], eax; //加
inc eax;
}
}
template<typename T>
T CALL_METHOD exchange_add32(T VOLATILE* Addend,T Value)
{
BOOST_STATIC_ASSERT(sizeof(T) == 4 && boost::is_integral<T>::value);
__asm
{
mov ecx, Addend;
mov eax, Value;
lock xadd [ecx], eax;
}
}
原子操作(3) - 原子数类
根据上面的5个函数就能做出一个原子操作的整数数字类,这将是下一节中,我的最轻量级锁的基础和原型,他不依赖于操作系统,当然,所以你也可以不叫他是锁,只是一种类似锁的机制.
一切细节看源码中穿插的注释.
#ifndef __ATOM_VALUE_H__
#define __ATOM_VALUE_H__
#include "atom.hpp"
#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>
template<typename T>
class atomic_value32
{
//恩,用boost.type_traits来保证是位的整数类型
BOOST_STATIC_ASSERT(sizeof(T) == 4 && boost::is_integral<T>::value);
private:
volatile T value_;
public:
atomic_value32(T v = 0)
:value_(v){}
atomic_value32(atomic_value32& v){//??? 这里留给大家,我不给出源码了
}
//需要这么一个转型符号,因为大部分时候我们将atomic_value32<T>当作一个T使用
operator T(){return exchange_add32(&value_,0);}
//赋值
atomic_value32& operator=(T v){exchange32(&value_, v);return *this;}
atomic_value32& operator=(atomic_value32& v){exchange32(&value_, v);return *this;}
//比较并交换,好像没有什么operator与之对应,就直接拿出来了
T compare_exchange(T to_exchange, T to_compare)
{return compare_exchange32<T>(&value_, to_exchange, to_compare);}
//只提供前置,后置似乎没什么必要,我也懒得去实现了:)
T operator++(){return increment32(&value_);}
T operator--(){return decrement32(&value_);}
//千万不能返回引用,因为线程安全考虑,
T operator+=(T add){return exchange_add32(&value_,add);}
T operator+=(atomic_value32& add){return exchange_add32(&value_,add);}
T operator-=(T add){return exchange_add32(&value_,-add);}
T operator-=(atomic_value32& add){return exchange_add32(&value_,-add);}
//6个比较符号
bool operator==(T rhs){return operator T()==rhs;}
bool operator==(atomic_value32& rhs){return operator T()==rhs.operator T();}
bool operator<(T rhs){return operator T()<rhs;}
bool operator<(atomic_value32& rhs){return operator T()<rhs.operator T();}
bool operator!=(T rhs){return !this->operator ==(rhs);}
bool operator!=(atomic_value32& rhs){return !this->operator ==(rhs);}
bool operator>=(T rhs){return !this->operator <(rhs);}
bool operator>=(atomic_value32& rhs){return !this->operator <(rhs);}
bool operator>(T rhs){return ((*this)!=(rhs)) && !((*this)<(rhs));}
bool operator>(atomic_value32& rhs){return ((*this)!=(rhs)) && !((*this)<(rhs));}
bool operator<=(T rhs){return !((*this)>(rhs));}
bool operator<=(atomic_value32& rhs){return !((*this)>(rhs));}
};
#endif//__ATOM_VALUE_H__