如何在C中实现无锁共享标志?

时间:2012-10-08 19:45:51

标签: c sync lock-free

我有一个生产者一个消费者模型,我需要生产者在数据可用时设置一个标志。我怀疑我可以在没有锁定共享标志的情况下离开,因为:

  • 生产者在设置
  • 之前从不检查该值
  • 偶尔缺少对标志的更新不是问题(尽管我可能也可以使用原子操作来避免这种情况吗?)。

所以我的问题是,我该如何实现呢?我对volatile关键字的理解,以及像__sync_synchronize()这样的事情充其量是微不足道的,所以假设我知之甚少。具体来说,我希望能够确保在另一个线程中及时看到对该标志的更改。

编辑:我在Linux上使用GCC。

4 个答案:

答案 0 :(得分:1)

使用两个变量:

volatile size_t elements_produced; // producer increments it when data is available
volatile size_t elements_consumed; // consumer increments it

elements_produced != elements_consumed时,

新数据正好可用。 如果你需要无限量,那么另外在更新时修改它。

produce(...) {
    elements_produced = (elements_produced + 1) % (max_elements_in_queue + 1);
}

consume(...) {
    elements_consumed = (elements_consumed + 1) % (max_elements_in_queue + 1);
}

不需要锁或原子。

这是单生成器,单用户循环环缓冲区的标准实现。

答案 1 :(得分:0)

原子操作意味着(永远如此简单地)阻止其他原子操作,直到完成第一个操作。

我假设我们正在谈论线程,因此我们应该考虑互斥体(但这也适用于进程和信号量)。可以检查(读取)互斥锁(或信号量)而不尝试获取锁定。

如果互斥锁(信号量)的状态是已经锁定,请继续执行其他操作,稍后再试。

答案 2 :(得分:0)

实际上不可能以便携的方式做到这一点。

但是,您可以使用各种编译器内在函数来实现它。

例如,对于x86(-64)上的gcc,可能至少是ARM:

static int queued_work;

static void inc_queued_work()
{
    (void)__sync_add_and_fetch( &queued_work, 1 );
}

/*
  Decrement queued_work if > 0.
  Returns 1 if queued_work was non-equal to 0 before
  this function was called.
*/
static int dec_queued_work()
{
    /* Read current value and subtract 1.
       If the result is equal to -1, add 1 back and return 0.
    */
    if( __sync_sub_and_fetch( &queued_work, 1 ) == -1 )
    {
        __sync_fetch_and_add( &queued_work, 1 );
        return 0;
    }
    return 1;
}

某些CPU:仅支持compare_and_swap。 您也可以使用该内在函数实现此目的:

/* Alternative solution using compare_and_swap  */
void inc_queued_work()
{
    do {
        int queued = queued_work;
        /* Try to write queued-1 to the variable. */
        if( __sync_bool_compare_and_swap( &queued_work,
                                         queued, queued+1 ) )
            return;
    } while( 1 );
}

int dec_queued_work()
{
    do {
        int queued = queued_work;
        if( !queued ) return 0;
        /* Try to write queued-1 to the variable. */
        if( __sync_bool_compare_and_swap( &queued_work, 
                                         queued, queued-1 ) )
            return queued;
    } while( 1 );
}

即使您有多个作家和读者,这些功能也应该有效。 将Google与朋友应用到'sync_add_and_fetch'将为您提供大量参考文档

答案 3 :(得分:-1)

没有锁定,两个线程将永远不会同步,并且消费者将永远旋转等待永远不会改变的值(因为该值被缓存,因此内存put / fetch将永远不会发生)。

总而言之,你必须a)确保从生产者那里写入内存,并且b)确保消费者读取内存。这正是锁的作用,这就是你应该使用它们的原因。如果您的死机设置为锁定,则可以使用睡眠等功能,这些功能可以在唤醒后保证缓存状态。