ACCESS_ONCE
宏定义如下:
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
从语法上讲,强制编译器每次使用x从内存中获取地址指针取其值
对于单线程程序而言这个没什么用,但是对于多线程,可以避免编译器的优化造成的错误
例如:
例程1:
static int should_continue;
static void do_something ( void );
...
while (should_continue )
do_something ( );
假设 do_something() 函数中并没有对变量 should_continue 做任何修改,那么,编译器完全有可能把它优化成
...
if (should_continue )
for (;; )
do_something ( );
对于多线程:如果这个线程在执行do_something() 的期间,另外一个线程改变了 should_continue 的值,那么上面的优化就是完全错误的了!更严重的问题是,编译器根本就没有办法知道这段代码是不是并发的,也就无从决定进行的优化是不是正确的!
解决办法:1) 给 should_continue 加锁,毕竟多个进程访问和修改全局变量需要锁是很自然的;2) 禁止编译器做此优化。加锁的方法有些过了,毕竟 should_continue 只是一个布尔,而且退一步讲,就算每次读到的值不是最新的 should_continue 的值也可能是无所谓的,大不了多循环几次,所以禁止编译器做优化是一个更简单也更容易的解决办法。
更改为:
...
while (ACCESS_ONCE (should_continue ) )
do_something ( );
例程2:
p = global_ptr;
if (p && p ->s && p ->s ->func )
p ->s ->func ( );
指针读取一次,但要dereference多次,那么编译器也有可能把它编译成
if (global_ptr && global_ptr ->s && global_ptr ->s ->func )
global_ptr ->s ->func ( );
这种情况下,另外的进程做了 global_ptr = NULL; 就会导致后一段代码 segfault,而前一段代码没问题
解决办法:
p = ACCESS_ONCE (global_ptr );
if (p && p ->s && p ->s ->func )
p ->s ->func ( );
例程3:
for (;; ) {
still_working = 1;
do_something ( );
}
假设 do_something() 定义是可见的,而且没有修改 still_working 的值,那么,编译器可能会把它优化成
still_working = 1;
for (;; ) {
do_something ( );
}
如果其它进程同时执行了:
for (;; ) {
still_working = 0;
sleep ( 10 );
if ( !still_working )
panic ( );
}
通过 still_working 变量来检测 wathcdog 是否停止了,并且等待10秒后,它确实停止了,panic()!经过编译器优化后,就算它没有停止也会 panic
解决办法:
for (;; ) {
ACCESS_ONCE (still_working ) = 1;
do_something ( );
总之,需要使用 ACCESS_ONCE() 的两个条件是:
- 在无锁的情况下访问全局变量;
- 对该变量的访问可能被编译器优化成合并成一次(上面第1、3个例子)或者拆分成多次(上面第2个例子)。
在我的实际应用中
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
static bool server_upp_data_transfer(Server *server) {
int i = 0;
while (ListMP_empty(server->list_free)) { /* wait */
if (i++ == 5) {
LOG_DEBUG("maybe arm is too busy. give up upp data");
return true;
}
Task_sleep(10000);
}
DataNode *node = (DataNode *)ListMP_getTail(server->list_free); /* get free node */
int *data = (int *)SharedRegion_getPtr(node->SR_buffer);
while(server->last == ACCESS_ONCE(buff_num))
Task_sleep(1);
if (ACCESS_ONCE(buff_num) == 0) { /* buffer1 busy */
memcpy(data, ad8568_buffer2, sizeof ad8568_buffer2);
server->last = buff_num;
} else { /* buffer2 busy */
memcpy(data, ad8568_buffer1, sizeof ad8568_buffer1);
server->last = buff_num;
}
/* make data node in busy list */
ListMP_putHead(server->list_busy, (ListMP_Elem *)node);
return true;
}