C/C++ 带线程的数组 - 我需要使用互斥体或锁吗?

C/C++ arrays with threads - do I need to use mutexes or locks?

我刚开始使用线程,并且阅读了很多关于如何共享和保护数据的资料。但是我也没有真正掌握使用互斥锁和锁来保护数据。

下面是我将要处理的问题的描述。需要注意的重要一点是它是时间紧迫的,所以我需要尽可能减少开销。

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

我的问题是:

  1. 每次访问只读数组中的数据时,我真的需要在线程中使用互斥锁吗?如果是这样,你能解释一下为什么吗?

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

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

  4. 对这类问题的许多答案似乎是——不,如果你的变量是对齐的,你就不需要互斥锁。此示例中的数组元素是否对齐,或者是否有某种方法可以确保它们对齐?

代码将在 64 位 Linux 上实现。我正计划将 Boost 库用于多线程。

几天来我一直在考虑这个问题,并在整个网络上寻找,一旦发布,答案和清晰的解释在几秒钟内就回来了。有一个“已接受的答案”,但所有答案和评论都同样有帮助。

  1. Do I really need to use a mutex in a thread each time I access the data from the read-only array? If so could you explain why?

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

  1. Do I need to use a mutex in a thread when it writes to the result array even though this will be the only thread that ever writes to this element?

视情况而定。

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

无论如何,注意不要让不同的线程大量写入相邻的内存位置。这可能会破坏性能。参见 "false sharing"。考虑到,您可能没有很多内核,因此没有很多线程,并且您说写入只完成一次,但这可能不会成为一个重大问题。

  1. Should I use atomic data types and will there be any significant time over head if I do?

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

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

  1. ...Would my array elements in this example be aligned, or is there some way to ensure they are?

正如 Arvid 所指出的,您可以使用 alginas keyword which was introduced in c++11. Pre c++11, you may resort to compiler specific extensions: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Variable-Attributes.html

请求特定对齐

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

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

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

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

你走在正确的轨道上。

1) 对于第一个数组(只读),您不需要为其使用互斥锁。由于线程只是读取而不是改变数据,所以一个线程不可能破坏另一个线程的数据

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

3) 给定原子的定义:

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

要点,互斥锁是原子的,这意味着 grab/release 锁只需要 1 条汇编指令。如果 grab/release 一个锁需要 2 条汇编指令,那么这个锁就不是线程安全的。例如,如果线程 1 试图抢锁并切换到线程 2,则线程 2 将抢锁。

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

4) 我不确定您如何确保您的变量已排列。由于线程可以在您的程序中随时切换(您的 OS 决定线程何时切换)

希望对您有所帮助

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

google 上有很好的 race 信息,可以帮助您编写处理共享资源上数据的程序。