list::empty() 多线程行为?

list::empty() multi-threaded behavior?

我有一个列表,我希望不同的线程从中获取元素。为了避免在列表为空时锁定保护列表的互斥锁,我在锁定之前检查 empty()

即使对 list::empty() 的调用不是 100% 正确也没关系。我只想避免崩溃或中断并发 list::push()list::pop() 调用。

我可以安全地假设 VC++ 并且 Gnu GCC 有时只会出错 empty() 而不会更糟吗?

if(list.empty() == false){ // unprotected by mutex, okay if incorrect sometimes
    mutex.lock();
    if(list.empty() == false){ // check again while locked to be certain
         element = list.back();
         list.pop_back();
    }
    mutex.unlock();
}

有一个读取和写入(很可能是 std::listsize 成员,如果我们假设它是这样命名的话)在 方面不同步彼此。想象一下,一个线程调用 empty()(在你的外部 if() 中),而另一个线程进入内部 if() 并执行 pop_back()。然后您正在读取一个可能正在修改的变量。这是未定义的行为。

It's okay if the call to list::empty() isn't right 100% of the time.

不,这样不行。如果您在某种同步机制(锁定互斥锁)之外检查列表是否为空,那么您就会发生数据竞争。发生数据竞争意味着您有未定义的行为。具有未定义的行为意味着我们无法再对程序进行推理,并且您获得的任何输出都是 "correct".

如果您重视自己的理智,您将承受性能损失并在检查之前锁定互斥体。也就是说,该列表甚至可能不是适合您的容器。如果您可以让我们确切知道您用它做什么,我们也许可以建议一个更好的容器。

举个可能出错的例子:

足够聪明的编译器可以看到 mutex.lock() 不可能更改 list.empty() return 值,因此完全跳过内部 if 检查,最终导致 pop_back 在列表中,在第一个 if.

之后删除了最后一个元素

为什么它能做到? list.empty() 中没有同步,因此如果它被同时更改,将构成数据竞争。该标准规定程序不应存在数据竞争,因此编译器将认为这是理所当然的(否则它几乎不会执行任何优化)。因此,它可以假设一个单线程的角度来看非同步 list.empty() 并得出结论,它必须保持不变。

这只是可能破坏您的代码的几种优化(或硬件行为)之一。