我有一个生产者一消费者模型,我需要生产者在数据可用时设置一个标志。我怀疑我可以在没有锁定共享标志的情况下逃脱,因为:
- 生产者在设置之前从不检查值
- 偶尔缺少对标志的更新不是问题(尽管我也可以使用原子操作来避免这种情况?)。
所以我的问题是,我该如何实现呢?我对 volatile 关键字的理解,以及像 __sync_synchronize() 这样的东西充其量是微不足道的,所以假设我知道的很少。具体来说,我希望能够确保及时在另一个线程中看到对标志的更改。
编辑:我在 Linux 上使用 GCC。
使用两个变量:
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);
}
不需要锁或原子。
这是单生产者、单消费者循环环形缓冲区的标准实现。
以便携式方式执行此操作实际上是不可能的。
但是,您可以使用各种编译器内部函数来实现它。
例如,对于 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:s 只支持 compare_and_swap。您也可以使用该 intrisic 来实现它:
/* 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'会给你很多参考文档
原子操作意味着(非常短暂地)阻塞其他原子操作,直到第一个操作完成。
我假设我们正在谈论线程,因此我们应该考虑互斥锁(但这也适用于进程和信号量)。互斥体(或信号量)可以在不尝试获取锁的情况下检查(读取)。
如果互斥体(信号量)的状态是已经被锁定,则继续进行其他操作,稍后再试。
如果没有锁,两个线程将永远不会同步,并且消费者将永远旋转等待一个永远不会改变的值(因为该值被缓存,因此内存放置/获取永远不会发生)。
所以总而言之,你必须 a) 确保内存是从生产者那里写入的,b) 确保内存是由消费者读取的。这正是锁的作用,这就是你应该使用它们的原因。如果你对锁死了,你可以使用像 sleep 这样的函数,它可以保证唤醒后的缓存状态。