synchronized多线程编程的老生常谈了。所谓原子操作是指不会被
线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
定义
如果这个操作所处的层(layer)的更高层不能发现其内部实现与结构,那么这个操作是一个原子(atomic)操作。
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。
简介
在多进程(线程)访问共享资源时,能够确保所有其他的进程(线程)都不在同
一时间内访问相同的资源。原子操作(atomic operation)是不需要
synchronized,这是
Java多线程编程的老生常谈了。所谓原子操作是指不会被
线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。之所以要把它们排除在外是因为它们都比较大,而
JVM的设计规范又没有要求
读操作和赋值操作必须是原子操作(JVM可以试着去这么做,但并不保证)。
首先处理器会自动保证基本的内存操作的原子性。处理器保证从
系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的
内存地址。奔腾6和最新的处理器能自动保证
单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,
跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。
特性
原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断。在
单处理器系统(UniProcessor
指令系统中引入了test_and_set、test_and_clear等指令用于
临界资源互斥的原因。但是,在
对称多处理器(Symmetric Multi-Processor
计数值,
可能发生的情况是:
⒈ CPU A(CPU A上所运行的进程,以下同)从
内存单元把当前计数值⑵装载进它的
寄存器中;
⒉ CPU B从内存单元把当前计数值⑵装载进它的寄存器中。
⒊ CPU A在它的寄存器中将计数值递减为1;
⒋ CPU B在它的寄存器中将计数值递减为1;
⒍ CPU B把修改后的计数值⑴写回内存单元。
我们看到,内存里的计数值应该是0,然而它却是1。如果该计数值是一个
共享资源的
引用计数,每个进程都在递减后把该值与0进行比较,从而确定是否需要释放该共享资源。这时,两个进程都去掉了对该共享资源的引用,但没有一个进程能够释放它--两个进程都推断出:计数值是1,共享资源仍然在被使用。
硬件支持
原子性不可能由软件
单独保证--必须需要硬件的支持,因此是和架构相关的。在
x86 平台上,CPU提供了在指令执行期间对
多处理器环境中的原子性。
Linux
-----------------------------------------------------------
原子操作大部分使用
汇编语言实现,因为
c语言并不能实现这样的操作。
* 在x86的原子操作实现代码中,定义了LOCK宏,这个宏可以放在随后的
内联汇编指令之前。如果是
SMP,LOCK宏被扩展为lock指令;否则被定义为空 -- 单CPU无需防止其它CPU的干扰,锁内存总线完全是在浪费时间。
#else
*
typedef struct { volatile int counter; } atomic_t;
在所有支持的
体系结构上原子类型atomic_t都保存一个int值。在x86的某些处理器上,由于工作方式的原因,原子类型能够保证的可用范围只有24位。volatile是一个类型描述符,要求
编译器不要对其描述的对象作优化处理,对它的读写都需要从内存中访问。
* #define ATOMIC_INIT(i) { (i) }
用于在定义原子变量时,初始化为指定的值。如:
static atomic_t count = ATOMIC_INIT⑴;
* static __inline__ void atomic_add(int i,atomic_t *v)
将v指向的原子变量加上i。该函数不关心原子变量的新值,返回
void类型。
格式
在下面的实现中,使用了带有
C/C++表达式的
内联汇编代码,格式如下(参考《AT&T ASM Syntax》):
__asm__ __volatile__指示
编译器原封不动保留表达式中的
汇编指令系列,不要考虑优化处理。涉及的约束还包括:
⒈
等号约束(=):只能用于输出操作表达式约束,说明括号内的
左值表达式v->counter是write-only的。
⒉ 内存约束(m):表示使用不需要借助寄存器,直接使用内存方式进行输入或输出。
⒊
立即数约束(i):表示输入
表达式是一个立即数(整数),不需要借助任何寄存器。
⒋ 寄存器约束(r):表示使用一个
通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl和%edx/%dx/%dl中选取一个合适的。
相关程序
{
__asm__ __volatile__(
}
* static __inline__ int atomic_sub_and_test(int i,atomic_t *v)
----------------------------------------
从v 指向的
原子变量减去i,并测试是否为0。若为0,返回真,否则返回假。由于
x86的subl指令会在结果为0时设置
CPU的zero标志位,而且这个标志位是CPU私有的,不会被其它CPU影响。因此,可以执行一次
加锁的减操作,再根据CPU的zero标志位来设置本地变量c,并相应返回。
{
__asm__ __volatile__(
return c;
}
------------------------------------
#define atomic_read(v) ((v)->counter)
读取v指向的原子变量的值。
#define atomic_set(v,i) (((v)->counter) = (i))
设置v指向的原子变量的值为i。
static __inline__ void atomic_sub(int i,atomic_t *v)
从v指向的原子变量减去i。
static __inline__ void atomic_inc(atomic_t *v)
递增v指向的原子变量。
static __inline__ void atomic_dec(atomic_t *v)
递减v指向的原子变量。
static __inline__ int atomic_dec_and_test(atomic_t *v)
递减v指向的原子变量,并测试是否为0。若为0,返回真,否则返回假。
static __inline__ int atomic_inc_and_test(atomic_t *v)
递增v指向的原子变量,并测试是否为0。若为0,返回真,否则返回假。
static __inline__ int atomic_add_negative(int i,atomic_t *v)
将v指向的原子变量加上i,并测试结果是否为负。若为负,返回真,否则返回假。这个操作用于实现semaphore。