1 什么是原子操作
原子操作,顾名思义,就是说像原子一样不可再细分不可被中途打断。一个操作是原子操作,意思就是说这个操作是以原子的方式被执行,要一口气执行完,执行过程不能够被系统的其他行为打断,是一个整体的过程,在其执行过程中,系统的其它行为是插不进来的。
我们现在假设有两个执行单元A和B,这两个执行单元要对共享变量X加1,X的初始值是1。当CPU实现加一的操作时,先从内存读取X的值,执行加一的操作,在将X的值写回内存。假如这两个执行单元A和B是顺序执行的,执行过程如图 1.1:
按照上图所示的流程,X变量的值最终变为3,经过了两次加一操作。但是实际的执行流程可能如图 1.2所示:
按照上述流程执行的最终结果X的值为2,这就是一个最简单的设置变量值的并发与竞争的例子,要解决这个问题就要保证X变量加一操作的三个步骤要作为一个整体运行,也就是作为一个原子存在。
在linux中提供了两种形式的原子操作,一种是对整数进行的操作,一种是对单独的位进行操作,并且Linux内核提供了这两种形式原子操作的API函数,接下来分别看一下这些函数。
2 整型原子操作
在Linux中有一个专门的atomic_t的结构体来完成整型数据的原子操作,此结构体定义在 include/linux/types.h 文件中,定义如下:
typedef struct {
int counter;
} atomic_t;
在使用原子操作函数之前,首先要定义一个atomic_t 的变量,如下所示:
atomic_t a; //定义 a
也可以在定义原子变量的时候给原子变量赋初值,如下所示:
atomic_t b = ATOMIC_INIT(0); //定义原子变量 b 并赋初值为 0
可以通过宏 ATOMIC_INIT 向原子变量赋初值。
原子变量有了,接下来就是对原子变量进行操作,比如读、写、增加、减少等等,Linux 内核提供的原子操作 API 函数如下所示:
原子变量和相应的 API 函数使用起来很简单,参考如下示例:
atomic_t v = ATOMIC_INIT(0); /* 定义并初始化原子变零 v=0 */
atomic_set(10); /* 设置 v=10 */
atomic_read(&v); /* 读取 v 的值,肯定是 10 */
atomic_inc(&v); /* v 的值加 1,v=11 */
3 位原子操作
位操作也是很常用的操作,Linux 内核也提供了一系列的原子位操作 API 函数,只不过原子位操作不像原子整型变量那样有个 atomic_t 的数据结构,原子位操作是直接对内存进行操作,API 函数如下所示:
函数 作用