2016-12-07 86 views
1

在我的程序中,我试图从多个线程更新一个值。我知道如何使用互斥体(pthread_mutex_lock(), pthread_mutex_unlock())来做到这一点,但我刚刚了解了gcc的原子构建,所以我想尝试一下。如何使用gcc atomic builtins?

shared value A; 
void each_working_thread() { 
    thread local variable B; 
    if (is_valid(A,B)) 
     __sync_sub_and_fetch(A,B); 
    else 
     throw error; 
} 

其中is_valid()是返回布尔型的常量函数。

这是否正确,或者在is_valid()期间A的值可能被另一个线程更新?

回答

1

我认为is_valid(A,B)可根据A.改变

在这种情况下,这是不是安全的 - 考虑以下序列:

  • 线程1调用is_valid(A, B),并得到真正的
  • 线程2调用is_valid(A, B)并且变为真
  • 线程1调用__sync_sub_and_fetch(A, B)变化A,并且A的新值表示is_valid(A, B)现在是错误的。
  • 线程2调用__sync_sub_and_fetch(A, B)即使它不应该因为is_valid(A, B)现在是错误的。

您可能会感兴趣的比较交换操作。在这种情况下,你可以写这样的:

int oldValueOfA, newValueOfA; 
do { 
    oldValueOfA = A; 
    __sync_synchronize(); // memory barrier; makes sure A doesn't get accessed after this line 
    if (!is_valid(oldValueOfA, B)) 
     throw error; 
    newValueOfA = oldValueOfA - B; 
} while(!__sync_bool_compare_and_swap(&A, oldValueOfA, newValueOfA)); 

你不能让两个操作原子,但你可以检测如果他们不是原子然后你可以再次尝试。

这是有效的,因为如果A仍然不包含oldValueOfA,__sync_bool_compare_and_swap什么也不做,并返回false。否则,它将其设置为newValueOfA并返回true。因此,如果它返回true,则在您忙于拨打is_valid时,您知道其他人没有更改A。如果它返回false,那么你还没有做任何事情,所以你可以自由地回去重试。

请注意is_valid可能会被调用几次;这是相关的,如果它有副作用(如在屏幕上打印消息)。在这种情况下,你应该只使用一个互斥量。

+0

感谢您的解释。所以在这种情况下我必须使用互斥锁?如果'is_valid'只是'return A> B;'或者是一个相当复杂的函数,需要数百条指令,那么有什么区别吗? – bbvan

+0

@bbvan如果'is_valid'只是'return A> B;',你认为我描述的东西还是可以发生的吗?如果不是,为什么不呢?事实上,你问*建议*你没有真正想过我的解释。 – immibis

+0

是的,我认为没有区别...但是我希望有一些像'sync_if_cmpgt_then_sub'这样的神奇的原子操作... – bbvan

0

这可能不是线程安全的,具体取决于is_valid究竟是什么。

该具体内建的点只是执行原子扣除。

考虑以下代码

A = A - B; 

,因为它需要至少2个原子阶段这不是线程安全的 - 在A - B,然后指派回A.这样做的内置解决了一个场景。