通过引用传递值是线程安全的吗?

Is passing value by reference thread-safe?

提问人:mouse_00 提问时间:7/27/2023 最后编辑:mouse_00 更新时间:7/27/2023 访问量:124

问:

我有以下代码:

#include <thread>

void foo(int& value)
{
  // do nothing
}

int main()
{
  int value = 42;

  std::thread t1([&value]{ foo(value); });
  std::thread t2([&value]{ value = 100500; });

  t1.join();
  t2.join();

  return 0;
}

常识告诉我们这里没有数据竞争,因为通过引用传递值“必须”(???) 是线程安全的,但我无法根据 cppreference 或标准草案 N4950 对其进行验证。

  1. 如何验证没有基于 cppreference 或标准的数据争用?
  2. 通过引用传递值是线程安全操作吗?
C++ 多线程引用 传递 数据争

评论

1赞 Fureeish 7/27/2023
这个答案基本上用引文解释了一切。尽管如此,我还是想知道,为什么你会认为假设引用提供任何数据竞争保护是常识。实际上恰恰相反 - 值是共享的,并且(至少)一个线程会更改它。有了副本(没有参考),就不会有数据共享,因此不会有数据竞争。
1赞 paddy 7/27/2023
验证缺乏数据竞赛是很困难的。此代码看起来像数据竞赛,但实际上什么也没做。所以不是。如果使用,则数据竞赛。可以按引用传递,前提是引用的数据不会超出范围,直到所有线程都使用完该引用。在可见性方面,您在这里仍然存在问题,因为在访问可由另一个线程修改的变量时没有同步。从技术上讲,目前不能保证对 in 线程的更改在 中可见。foofoovaluevaluet2main
0赞 mouse_00 7/27/2023
@Fureeish,我认为这是常识,因为我没有获得参考价值
0赞 Eljay 7/27/2023
要使这成为常识,它必须是不可变的。在 C++ 中,最接近不可变的是 ,这通常就足够了。否则,如果需要可变,则负责确保它受到保护(例如,使用 )。valueconstvaluemutex
1赞 JaMiT 7/27/2023
“常识”永远不会像理智的人所希望的那样普遍,也不像普通人认为的那样理智。

答:

1赞 Yakk - Adam Nevraumont 7/27/2023 #1

在没有排序之前/之后关系的情况下修改或读取相同的值是一种争用条件。(实际规则更复杂,但这足以避免竞争条件,并且在违反此规则的同时避免竞争条件非常具有挑战性)。

传递引用不会修改或读取某些内容。

因此,从狭义上讲,您的代码没有争用条件,因为两个线程中只有 1 个会读取或写入您的变量。在创建修改它的变量之前修改该变量具有 happens-before 关系。std::thread

但是,如果对其参数执行任何非平凡操作,则代码会突然表现出未定义的行为。foo

评论

0赞 mouse_00 7/27/2023
你说“传递参考文献不会读到一些东西”,但我在 cpprenot 中找不到这些信息。您能提供一些参考吗?
0赞 paddy 7/27/2023
你读过参考声明吗?也许你要找的是:“引用,一旦初始化,总是引用有效的对象或函数”。
0赞 mouse_00 7/27/2023
@paddy,它并没有真正说明“引用初始化”是“读取操作”或“线程安全”
0赞 paddy 7/27/2023
您担心的“线程安全”问题究竟是什么?标识符是执行 的线程拥有的整数。对该数据的所有引用也由此线程创建。任何线程上引用的任何可能的“副本”也将引用同一对象。该对象在一个线程中存在一次,并且对它的所有引用都是不可变的。valuemain
0赞 Chris Dodd 7/27/2023
@mouse_00:关键是引用初始化不是对引用要初始化以引用的对象的读取操作。因此,在代码中,它永远不会实际访问 的值(它只创建对它的引用),因此不会有竞争。t1value