【问题标题】:Can I use char variable without lock in the multi-threading case我可以在多线程情况下使用不加锁的 char 变量吗
【发布时间】:2021-07-05 05:59:21
【问题描述】:

正如c/c++标准所说,char的大小必须是1。据我了解,这意味着 CPU 保证对 char 的任何读取或写入都必须在一条指令中完成。

假设我们有很多线程,它们共享一个char 变量:

char target = 1;

// thread a
target = 0;

// thread b
target = 1;

// thread 1
while (target == 1) {
    // do something
}

// thread 2
while (target == 1) {
    // do something
}

总而言之,线程有两种:一种是把target设置成01,另一种是把target == 1做一些任务。目的是我们可以通过修改target的值来控制task-threds。

据我了解,我们似乎根本不需要使用mutex/lock。但是我的编码经验让我有一种强烈的感觉,在这种情况下我们必须使用mutex/lock

我现在很困惑。在这种情况下我应该使用mutex/lock 吗?

你看,我可以理解为什么我们在其他情况下需要mutex/lock,例如i++。因为i++ 不能仅在一条指令中完成。那么target = 0 可以在一条指令中完成,对吗?如果是这样,是否意味着在这种情况下我们不需要mutex/lock

好吧,我知道我们可以使用std::atomic,所以我的问题是:既不使用mutex/lcok 也不使用std::atomic 是否可以。

【问题讨论】:

  • 永远不要做任何假设。它可以被打断。如果您不想使用互斥锁,也许 volatile sig_atomic_t 会有所帮助。
  • 在这种情况下,目标没有标记为易失性,因此编译器可能只会执行一次读取。
  • 我期待有评论提到 volatile,所以我已经准备好链接 When to use volatile with multi threading?
  • @MatG 同步的东西来自 sig_atomic_t,而不是来自 volatile。如果有,请准备另一个链接。
  • @tango-1 事实上,sig_atomic_t 只不过是一个typedef int...而volatile sig_atomic_t 在多核机器中不是线程安全的。

标签: c++ c multithreading locking mutex


【解决方案1】:

TL;DR

既不使用 mutex/lcok 也不使用 std::atomic 是否可以

没有

一般来说,假设事情是不好的。如果您需要为某事提供担保,请确保您拥有它们。

这与一个常见的逻辑谬误密切相关。仅仅因为你无法想象为什么某事可能是真的,那并不意味着它是真的。

加长版

正如 c/c++ 标准所说的

没有“C/C++”之类的东西,也绝对不是“C/C++ 标准”。它们是两种完全不同的语言,具有不同的标准。但是,他们确实同意这一点。 sizeof (char) 在两种语言中都是 1。

(旁注:sizeof 'a' 会产生不同的结果。)

据我了解,这意味着 CPU 保证对 char 的任何读取或写入都必须在一条指令中完成。

这是不正确的。 CPU 有自己的规范,完全独立于语言标准。没有任何东西说这一定是真的,即使它可能在大多数或所有情况下都是如此。

因为 i++ 不能只在一条指令中完成。

这取决于 CPU。 x86 架构对此有一个指令。 https://c9x.me/x86/html/file_module_x86_id_140.html

据我了解,我们似乎根本不需要使用互斥锁/锁。但是我的编码经验给了我一种强烈的感觉,在这种情况下我们必须使用互斥锁。

即使目标 CPU 确实在一条指令中读取和写入(它可能会这样做),也没有任何说明需要将 C 或 C++ 代码编译为该指令。

C 和 C++ 的标准都描述了代码的行为。不是它如何转换为程序集。

所以不,你不能做出你正在做的假设。

【讨论】:

    【解决方案2】:

    一般来说,不能假设读取或写入char 是原子操作。但是,目标架构可能提供这种保证。对于嵌入式 C 程序,通常的做法是依靠这种底层保证来避免在某些情况下同步机制的开销。

    在问题的示例中必须注意,即使读/写target是原子操作,值也可以随时更改,因此不能保证它会在1内部while 循环。

    【讨论】:

      【解决方案3】:

      std::atomic 保证访问变量是原子的。来自cppreference

      std::atomic 模板的每个实例化和完全特化 定义原子类型。如果一个线程写入一个原子对象,而 另一个线程从中读取,行为是明确定义的(见内存 模型了解数据竞赛的详细信息)。

      char 实际上是原子的(大小为 1 是不够的)时,std::atomic<char> 不需要额外的同步。但是,在char 不是原子的平台上,std::atomic<char> 保证它可以通过使用互斥锁或类似物以原子方式读取和写入。

      在实践中,我希望char 是原子的,但标准并不能保证这一点。

      还要考虑诸如+=之类的操作读取写入值,因此仅原子读取和写入不足以安全地调用+=,而std::atomic<T>有一个适当的@987654331 @。

      TL;DR

      我现在很困惑。在这种情况下我应该使用互斥锁/锁吗?

      让其他人为您做出决定。当你想要一些原子的东西时,使用std::atomic<something>,除非你想对同步进行细粒度的控制。

      【讨论】:

      • 原子性在 NUMA 上是不够的。这种变化也需要传播。 std::atomic<char> 会这样做,普通的 char 不会。
      • 说得好,对特定架构做出假设并不能很好地发挥代码可移植性;此外,使用 std::atomic 清楚地表明该变量应该被同时访问。
      • 感谢您告诉我std::atomic,好吧,所以我改变了我的问题......哈哈
      • @Yves 很好,我回答了您的原始问题,您对其进行了修改,因此我的回答不再适用。无论如何,我的回答还是一样:“让别人做决定”。当你想要一个原子变量时,没有理由不使用std::atomic
      • 我的错。我想用mutex/lock来代表各种锁机制……
      猜你喜欢
      • 1970-01-01
      • 2017-11-11
      • 2013-05-23
      • 2014-10-03
      • 2021-11-17
      • 2021-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多