【问题标题】:do need to use mutex lock?是否需要使用互斥锁?
【发布时间】:2011-12-01 19:08:21
【问题描述】:

简单版:在 C++ 程序中,我使用两个不同的线程来处理一些整数变量。但我敢肯定,一个人总是在其中写入一些价值,而另一个人只是阅读那个。读/写数据时还需要使用互斥锁吗?

现在细节:主要思想是第一个线程生成一些信息并将它们保存到一个数组中,第二个线程从该数组中读取数据并处理它们。这个数组代表一个队列。这意味着我有两个索引值指向队列中的第一个和最后一个项目。现在我想知道在读取或写入值时是否必须锁定这两个索引值,或者是否可以在不锁定的情况下检查它们?注意generator线程是queue_back的唯一线程改变索引,处理器线程拥有改变queue_front的独占权限。

如果我正在为基于 linux 的系统进行任何更改,并且代码是使用 gcc 编译的。

PS:在一些使用线程的代码中,我看到关键字volatile围绕不同线程之间共享的变量,我也需要使用它吗?

【问题讨论】:

  • 如果不使用volatile,其他线程可能永远看到对变量所做的更改。

标签: c++ multithreading mutex


【解决方案1】:

不,读写不是原子的,你需要使用一些同步机制来同步它。

此外,您必须将共享整数标记为 volatile,否则优化器可能会认为该变量永远不会在您的某个线程中更新。

gcc 允许您对 int、long 和 long long(以及它们的无符号对应项)进行原子操作。

查找函数:

type __sync_fetch_and_add (type *ptr, type value);
type __sync_fetch_and_sub (type *ptr, type value);
type __sync_fetch_and_or (type *ptr, type value);
type __sync_fetch_and_and (type *ptr, type value);
type __sync_fetch_and_xor (type *ptr, type value);
type __sync_fetch_and_nand (type *ptr, type value);

【讨论】:

  • 正要输入类似的内容。
  • @JohnDibling:同意并修改。
  • volatile is neither necessary nor sufficient (也没有用,除非有一些编译器添加了自己的定义以使其对此类事情有用)。
  • int 的读取和写入可能是原子的,因为没有线程可以读取部分写入的值。这取决于 CPU 架构、int 的大小(以八位字节为单位)、int 对象是否对齐以及对齐到什么边界(例如 4 字节、8 字节、16 字节...)。跨度>
  • @DanielTrebbien:至少在编写代码时,我不想依赖 也许
【解决方案2】:

读/写数据时还需要使用互斥锁吗?

是的,您需要一把锁。您可能对称为读/写锁的更具体的实现感兴趣。

您还可以使用原子和/或内存屏障。使用这些将需要更好地了解您的目标架构。重现多线程错误可能非常困难,这些替代方案应被视为一种可能不可移植的优化。

我已经看到关键字 volatile 围绕在不同线程之间共享的变量,我也需要使用它吗?

哎呀。不!对于 C++ 中的多线程读写,这不是一个安全或可移植的解决方案。改用原子、锁、复制、不可变和纯实现(等等)。

volatile 的解释可能因平台和/或编译器而异,并且没有指定在 C 或 C++ 中以任何特定方式操作以实现多线程读写(有一个古老的错误传说,它可以可靠地使用作为原子读/写)。我曾经在一个多线程 C++ 程序中测试了volatile 的有效性(在带有苹果 gcc 的 intel-mac 上)。我不会提供结果,因为它足够好,有些人可能会考虑使用它,尽管他们应该不是因为“几乎”不够好。 p>

并且限定volatile 的使用:它存在于我的(大型、严格编写、多线程感知)代码库中,其唯一目的是与平台相关的原子API 进行交互。老实说:还有一些早期的其他用途,但它们可以而且应该被删除。

【讨论】:

    【解决方案3】:

    是的,您需要使用互斥锁、临界区、互锁访问等来同步对变量的访问,以确保读取线程在写入线程仍在保存它们时不会读取不完整的字节。这在多核/CPU 系统上尤其重要,其中两个线程可以真正并行访问变量。

    【讨论】:

    • 如果将数组的大小限制为 256,并将索引值限制为单字节数据怎么办?
    【解决方案4】:

    读取和写入不大于机器字的正确对齐数据(通常是int 解析为的任何内容)在大多数主要架构上都是原子操作。这不是意味着每个架构。​​

    这意味着不,您不能只阅读headtail 并期望数据是一致的。但是,例如,如果sizeof(int) 恰好是 4 而sizeof(short) 恰好是 2,并且如果您不关心“非主流”平台,您可以做一些联合诡计并且无需原子操作或互斥锁就可以逃脱.

    如果您希望您的代码具有可移植性,则无法绕过适当的锁定或原子比较/交换。

    关于volatile,这确实为 Microsoft Visual C++ 插入了一个内存屏障(作为特定于编译器的诡辩),但标准确实保证除了编译器不会优化变量。就目前而言,制作 volatile 并没有多大帮助,而且它绝对不能保证线程安全。

    【讨论】:

      【解决方案5】:

      是的,您需要使用某种同步机制(例如互斥锁)来保护生成器和读取器线程中的队列索引。

      HTH

      【讨论】:

      • 等等,真的吗?编译器是不允许优化掉的吗?我不认为这是对的。这是 volatile 关键字的唯一用法。
      • @Mooing Duck -- 是的,你是对的,我错了。我改变了答案。谢谢你捡起来。
      【解决方案6】:

      如果您真的只是共享一个整数,那么std::atomic<int> 听起来是正确的类型,来自<atomic> 标头。 (如果你有一个旧的编译器,还应该有 Boost 或 TR1 版本。)这确保了原子读写。据我所知,不需要volatile 限定符。

      【讨论】:

      • 嗯,我分享的不仅仅是一个整数,但同时只能访问一个整数。我的意思是整个队列变量是共享的,但是两个线程在尝试访问队列节点之前首先检查头和尾变量。
      猜你喜欢
      • 1970-01-01
      • 2010-09-12
      • 2015-10-31
      • 2011-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多