程序中有关ACCESE_ONCE的理解

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. 在无锁的情况下访问全局变量;
  2. 对该变量的访问可能被编译器优化成合并成一次(上面第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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值