C语言陷阱与技巧19节,#define atomic_read(v) ((v)->counter + 0)在宏定义后面加0有什么好处?怎样自定义一个自己的锁?
发表于: 2019-04-28 08:29:31 | 已被阅读: 35 | 分类于: C语言
在阅读 Linux 内核源码时,发现了两个宏,相关的C语言代码如下,请看:
#define atomic_read(v) ((v)->counter + 0)
#define atomic64_read(v) ((v)->counter + 0)
typedef struct { volatile int counter; } atomic_t;
typedef struct { volatile long counter; } atomic64_t;
宏定义后 “+0”的技巧
首先,在 atomic_read() 宏定义后“+0”可以避免 atomic_read() 宏被当作“左值”。根据改宏的名字,应该能够知道它是“原子的”读取,而一个被读取的数据再做“左值”显然是不合适的,如果没有后面的 “+0”,下面这样误写的C语言代码,编译器是不会报错的:
if (atomic_read(v) = 32) {
...
}
当然,也可以使用下面这样的宏定义避免 atomic_read() 宏被当作左值:
#define atomic_read(v) (+(v)->counter)
即加上一个“正号”,不过这么做显然没有在后面“+0”好,相信读者应该明白,这里就不赘述了。
按照C语言标准,一个宏只要名字一样,参数类型一样,逻辑一样,出现重复的宏定义时完全没有问题的,不过出现重复代码对维护来说是一件很不好的事。在宏后面“+0”的另外一个好处就是可以尽可能的避免重复的宏定义。请看:
//atomic.h
#define atomic_read(v) ((v)->counter + 0)
#define atomic64_read(v) ((v)->counter)
//some source file that includes atomic.h
#define atomic_read(v) ((v)->counter) //redefinition error
#define atomic64_read(v) ((v)->counter) //no redefinition error
C语言程序开发中的原子操作
我们再来说说C语言程序开发中的“原子操作”。相信不少朋友都听说过“锁”的概念,它主要用于避免一些共享资源被多个线程并发访问时,出现数据错误的情况。而“原子操作”是锁的基石,或者换句话说,“锁”是依靠原子操作实现的。
众所周知,“原子”是组成万物的微小颗粒,一般认为原子已经足够小,无法再被分割。与之对应,C语言中的“原子操作”则是不能再被分割的指令。那么,原子操作的意义是什么呢?假设在某个C语言程序中定义了一个全局变量 i,如果有两个线程同时访问 i,并执行“加一”操作,如果 i 的初值为 0,我们当然希望这一过程是这样的:
不过,如果
原子操作的“陷阱”与“小技巧”
可能初学者会认为C语言程序中,如果代码只有一行,那必定是原子操作。这其实是一个较为致命的“陷阱”,大多数机器只能保证操作一个字是原子的,还有一部分机器则只能保证操作一个字节是原子的。
举个最简单的例子,请看下面这段C语言代码:
struct s{
long a;
double b;
char c[1024];
};
struct s s1, s2;
s1 = s2;
s 是一个相当大的结构体,所以 s1=s2; 虽然只有一行,它仍然不是原子操作。事实上,我们可以得到这段C语言程序的汇编代码:
避免出现上述“不期望”的结果出现的方法就是对 s1 和 s2 进行保护。常用的方法是使用锁,在赋值之前加锁,赋值完成后再解锁。
lock();
s1 = s2;
unlock();
互斥锁是使用最广泛的锁之一,但是互斥锁在加锁过程中可能会睡眠,这时操作系统可能会调度其他线程运行,这对于需要较长时间加锁的情况当然是好事,但是我们仅做了赋值操作,是不希望有这样的时间开销的,针对这种情况,一个小技巧是使用位操作,自定义一个轻量级的锁:
status char status = 0;
#define BIT_LOCK 0x01
#define BIT_UNLOCK() (status &= ~BIT_LOCK)
#define BIT_LOCK()\
do{\
while( status & BIT_LOCK);\
status |= BIT_LOCK;\
}while(0)
BIT_LOCK();
s1 = s2;
BIT_UNLOCK();
请读者思考一下,为什么在处理一些能够快速完成的工作时,使用 BIT_LOCK() 比使用互斥锁的效率反而更高呢?(可阅读我的 《Linux 学习》系列文章)
小结
本节我们通过Linux 内核中的一个宏定义知道了有时候“+0”这样看似无用的操作,也是能够提供非常不错的实用技巧的。另外,本节也讨论了原子操作,以及如何利用这一原理实现自己的锁。