【问题标题】:C/C++ arrays with threads - do I need to use mutexes or locks?带线程的 C/C++ 数组 - 我需要使用互斥锁还是锁?
【发布时间】:2015-08-25 20:13:09
【问题描述】:

我是使用线程的新手,并且已经阅读了很多关于如何共享和保护数据的内容。但是我也没有真正掌握使用互斥锁和锁来保护数据。

下面是我将要解决的问题的描述。需要注意的重要一点是时间紧迫,因此我需要尽可能减少开销。

我有两个固定大小的双精度数组。

  • 第一个数组将为后续计算提供数据。 线程将从它读取值,但它永远不会被修改。任何线程都可能在某个时间读取一个元素。

  • 第二个数组将用于存储线程执行的计算结果。这个数组的一个元素只会被一个线程更新,并且可能只会在结果值时更新一次
    写给它。

那么我的问题:

  1. 每次访问只读数组中的数据时,我真的需要在线程中使用互斥锁吗?如果有,您能解释一下原因吗?

  2. 当线程写入结果数组时,我是否需要在线程中使用互斥锁,即使这将是唯一写入此元素的线程?

  3. 我应该使用原子数据类型吗?如果我这样做会不会有任何显着的时间开销?

  4. 此类问题的许多答案似乎是 - 不,如果您的变量是对齐的,则您不需要互斥锁。我在这个例子中的数组元素会对齐,还是有什么方法可以确保它们对齐?

代码将在 64 位 Linux 上实现。我正计划使用 Boost 库进行多线程处理。

几天来,我一直在考虑这个问题并在整个网络上查看,一旦发布,答案和清晰的解释就会在几秒钟内返回。有一个“公认的答案”,但所有答案和 cmets 都同样有帮助。

【问题讨论】:

  • 锁的目的是防止一个并发任务在另一个任务正在使用它的同时修改数据。对于输入数组,数据是不可变的(没有线程修改它),所以你不需要锁定它。
  • 如果你确实在你的描述中持有这些谓词,我认为不需要任何锁或原子。
  • 至于问题 4:您的数组(显然)需要对齐以满足其类型,并且默认情况下是对齐的。但是,您可能还想额外对齐缓存行(通常为 64 字节),并且您可能真的想避免错误共享:en.wikipedia.org/wiki/False_sharing

标签: c++ c arrays multithreading mutex


【解决方案1】:
  1. 每次访问只读数组中的数据时,是否真的需要在线程中使用互斥锁?如果是,您能解释一下原因吗?

没有。因为数据从不被修改,所以不会有同步问题。

  1. 当线程写入结果数组时,我是否需要在线程中使用互斥锁,即使这将是唯一写入此元素的线程?

视情况而定。

  1. 如果任何其他线程要读取元素,则需要同步。
  2. 如果任何线程可能会修改向量的大小,则需要同步。

在任何情况下,请注意不要让不同的线程大量写入相邻的内存位置。那可能会破坏性能。请参阅“虚假共享”。考虑到您可能没有很多内核,因此也没有很多线程,并且您说写入只完成一次,但这可能不会成为一个大问题。

  1. 我应该使用原子数据类型吗?如果我这样做会不会有大量的时间开销?

如果您使用锁(互斥锁),则不需要原子变量(而且它们确实有开销)。如果您不需要同步,则不需要原子变量。如果您需要同步,那么在某些情况下可以使用原子变量来避免锁定。在哪些情况下你可以使用原子而不是锁......我认为更复杂并且超出了这个问题的范围。

鉴于您在 cmets 中的情况描述,似乎根本不需要同步,因此不需要原子或锁。

  1. ...本例中的数组元素是否对齐,或者有什么方法可以确保它们对齐?

正如 Arvid 所指出的,您可以使用 c++11 中引入的 alginas 关键字请求特定对齐。在 c++11 之前,您可以求助于编译器特定的扩展:https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Variable-Attributes.html

【讨论】:

  • 只是为了确认(基于答案 1. 和 2) - 在这些计算期间没有其他线程将读取或写入该数组元素,并且数组大小是固定的,因此写入数组不需要互斥锁?
  • @KirilKirov 如果线程 A 修改 x[0] 并且线程 B 修改 x[i] 其中i != 0 你确定需要同步吗?
  • 哦,对不起,我错过了“曾经写过这个元素的人”。忽略我的评论。
  • @BarbieBear 但是线程将修改的元素是在线程开始之前预先确定的吗?或者线程准备好写入结果时会选择一个元素吗?在前一种情况下,您不需要锁定,因为没有两个线程会写入相同的元素,但在后一种情况下,您确实需要锁定,否则两个(或更多)线程可能会选择相同的元素。
  • 可能会影响与 alignas 说明符的对齐。 en.cppreference.com/w/cpp/language/alignas
【解决方案2】:

在给定的两个条件下,不需要互斥体。请记住每次使用互斥锁(或任何同步构造)都会产生性能开销。所以你想尽可能地避免它们(当然,不影响正确的代码)。

  1. 没有。不需要互斥锁,因为线程只读取数组。

  2. 没有。由于每个线程只写入不同的内存位置,因此不可能出现竞争条件。

  3. 没有。这里不需要对对象进行原子访问。事实上,使用原子对象可能会对性能产生负面影响,因为它会阻止优化可能性,例如重新排序操作。

【讨论】:

    【解决方案3】:

    您需要使用锁的唯一情况是在共享资源上修改数据时。例如,如果某些线程用于写入数据而某些线程用于读取数据(在这两种情况下都来自同一资源),那么您只需要在写入完成时锁定。这是为了防止所谓的“种族”。

    当您制作在共享资源上操作数据的程序时,Google 上有很好的种族信息。

    【讨论】:

      【解决方案4】:

      你在正确的轨道上。

      1) 对于第一个数组(只读),您不需要为其使用互斥锁。由于线程只是读取而不更改数据,因此线程无法破坏另一个线程的数据

      2) 我对这个问题有点困惑。如果您知道线程 1 只会将一个元素写入数组插槽 1,而线程 2 只会写入数组插槽 2,那么您不需要互斥锁。但是我不确定你是如何实现这个属性的。如果我的上述陈述不适合您的情况,您肯定需要互斥锁。

      3) 给定原子的定义:

      原子类型是封装了一个值的类型,该值的访问保证不会导致数据争用,并可用于在不同线程之间同步内存访问。

      重点说明,互斥锁是原子的,这意味着只需要 1 条汇编指令来获取/释放锁。如果需要 2 个汇编指令来获取/释放锁,则锁将不是线程安全的。例如,如果线程 1 试图抢锁并切换到线程 2,则线程 2 会抢锁。

      使用原子数据类型会减少开销,但不会显着。

      4) 我不确定您如何确保您的变量符合要求。由于线程可以在您的程序中随时切换(您的操作系统决定线程何时切换)

      希望对你有帮助

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-01
        • 2020-12-07
        • 2022-07-31
        • 1970-01-01
        相关资源
        最近更新 更多