我正在尝试使用C ++ 11原子基元来实现各种原子“ 线程计数器 ”。基本上,我有一个关键的代码部分。在此代码块中,任何线程都可以从内存中自由读取。但是,有时候,我想进行重置或清除操作,将所有共享内存重置为默认的初始化值。
这似乎是一个使用读写锁的好机会。 C ++ 11不包含开箱即用的读写互斥锁,但可能更简单。我认为这个问题是一个很好的机会,可以更熟悉C ++ 11原子基元。
所以我想了一会儿这个问题,在我看来,我所要做的就是:
每当线程进入临界区时,递增 a 原子计数器变量
每当线程离开临界区时,减少 原子计数器变量
如果某个帖子希望重置全部 变量为默认值,它必须原子等待计数器 为0,然后原子地将其设置为一些特殊的“清除标志” 值,执行清除,然后将计数器重置为0.
当然, 希望增加和减少计数器的线程也必须检查 清除旗帜。
因此,我刚才描述的算法可以用三个函数实现。在进入临界区之前,必须始终调用第一个函数increment_thread_counter()
。在离开临界区之前,必须始终调用第二个函数decrement_thread_counter()
。最后,只有iff 线程计数器== 0,才能从关键部分 之外调用函数clear()
。
这就是我提出的:
假设:
std::atomic<std::size_t> thread_counter
clearing_flag
设置为std::numeric_limits<std::size_t>::max()
...
void increment_thread_counter()
{
std::size_t expected = 0;
while (!std::atomic_compare_exchange_strong(&thread_counter, &expected, 1))
{
if (expected != clearing_flag)
{
thread_counter.fetch_add(1);
break;
}
expected = 0;
}
}
void decrement_thread_counter()
{
thread_counter.fetch_sub(1);
}
void clear()
{
std::size_t expected = 0;
while (!thread_counter.compare_exchange_strong(expected, clearing_flag)) expected = 0;
/* PERFORM WRITES WHICH WRITE TO ALL SHARED VARIABLES */
thread_counter.store(0);
}
据我所知,这应该是线程安全的。请注意,decrement_thread_counter
函数不应该要求任何同步逻辑,因为increment()
之前总是调用decrement()
。因此,当我们到达decrement()
时,thread_counter永远不能等于0或clearing_flag
。
无论如何,由于THREADING是HARD™,并且我不是无锁算法的专家,我不完全确定这个算法是无竞争条件的。
问题:此代码线程是否安全?这里有没有竞争条件?
答案 0 :(得分:6)
你有竞争条件;如果另一个线程更改了increment_thread_counter()
对clearing_flag
的测试和fetch_add
之间的计数器,则会发生错误。
我认为这个经典的CAS循环应该更好:
void increment_thread_counter()
{
std::size_t expected = 0;
std::size_t updated;
do {
if (expected == clearing_flag) { // don't want to succeed while clearing,
expected = 0; //take a chance that clearing completes before CMPEXC
}
updated = expected + 1;
// if (updated == clearing_flag) TOO MANY READERS!
} while (!std::atomic_compare_exchange_weak(&thread_counter, &expected, updated));
}