【问题标题】:multithread vector多线程向量
【发布时间】:2012-06-22 00:23:25
【问题描述】:

我正在使用 C++ 开发一个项目,该项目使用自创地图来存储数据 - 从这个意义上说,地图更像是“地理”地图,所以是图像。有不同的线程读取和写入它。地图的数据存储在整数向量的标准向量中。它的大小不变,只是通过getter和setter函数改变某些像素的内容。

我的问题如下: 有时一切都很好,但更多时候我得到损坏的图像,因为像素的值改变了符号或变得与它们应该的完全不同。这可能是对像素的线程读/写访问的问题,如果是这样,我应该使用什么来代替标准向量? 我尝试使用互斥锁来确保只有一个线程读取或写入向量,但是这些读/写操作经常发生,如果我在每次操作时锁定向量,应用程序就会变得太慢。

【问题讨论】:

  • 你应该使用一维向量。它的性能优于 2D。
  • 原子操作可以解决问题,看看你怎么说“大小不会改变,只有通过 getter 和 setter 函数的某些像素的内容”。此外,尝试对访问进行“分区”(即不同的线程不会同时修改完全相同的像素)以最大程度地减少缓存中毒。当然,这并不能确保两个相邻像素彼此一致,但是如果您以混乱的方式修改它们,再次锁定也不能保证这一点。但是,它保证,例如同时递增和递减的值不会得到“奇怪”的结果。
  • @Damon:我相信 C++11 中定义的内存模型旨在确保不会发生像您描述的相邻像素的效果。见here
  • @BjörnPollex:我不明白它是怎么做到的。正确分区(例如,线程 1 在像素 0-999 上运行内核,线程 2 在 1000-1999 上,线程 3 在 2000-2999 上)无论如何都不会发生。然而,想象一下“线程读取和写入”,即线程 1 更新(原子地,正确地)值 20 到 50,而线程 2 读取值 30 和 40 作为输入值以计算值 200。即使两个值都正确更新,也有无法判断整个数据集是否一致没有锁。编译器应该如何在不知道可能正在运行的线程的情况下执行此操作?
  • 另一方面,假设线程 1 和 2 都在地图上或多或少相同的区域(重叠)中绘制“山丘”,即它们根据一些距离函数。他们都使用原子增量,并且看到它“正常工作”(尽管它是缓存的噩梦)。在这种情况下,硬件可以发挥所有作用。这在很大程度上取决于您是编写、读取-修改-更新还是读取-修改-更新并读取另一个位置。

标签: c++ multithreading vector


【解决方案1】:

您将需要某种锁定。为防止这严重影响您的性能,您应该尝试使锁的范围尽可能小。例如,您可以锁定单个行向量,以便在不同行上的写入不会相互干扰。哪种解决方案最适合您取决于您​​的访问模式和平台。

【讨论】:

  • 非常感谢。我将尝试仅锁定一小部分矢量以进行写入 - 我是否也需要将其锁定在读取运算符中?
  • 这取决于你想对数据做什么。如果您只阅读,则不需要锁。如果您从正在写入的区域读取,则需要一个锁 - 但您可以使用Readers-Writer Lock。也许在你的场景中,如果你只有一个作家,你甚至可以没有锁——你有时可能会读取不一致的数据,但取决于你用它做什么,这可能是也可能不是问题。
【解决方案2】:

在做多线程的时候,尽量把线程的作用域隔离开来。

考虑并举例。想象一下,你有一堵墙,你想让它涂上黑白条纹。 为了加快工作速度,您决定雇用两名员工。

现在,您可以通过两种方式分配此工作。 1. 将黑色条纹分配给一名工人,将白色条纹分配给另一名工人。 2. 将墙分成两个分区,左侧分区分配给一名工人,右侧部分分配给第二名工人

现在哪一个可能会产生更好的性能?

在一般情况下,第二种情况更好,因为工人的工作区域分隔良好,一个人不需要等待另一个人完成他的工作。 当然第一种方法没有错,但是,有可能一个工人正在某个地方绘画,而第二个工人也可能到达同一个地方,并且必须等待第一个工人完成。 现在想象一下,如果你有 10 名工人,每人只涂一种特定的颜色会发生什么。

多线程编程是类似的。如果您能以更好的方式对问题数据进行分区,则可以使线程有效地工作。

【讨论】:

  • 不幸的是,在大多数线程的情况下这是不可能的 - 总是有两个线程在向量的同一部分工作。还有更多这样的线程组——它们大多是孤立的。
【解决方案3】:
  1. 使用轻量级锁。那是在 Windows 上使用“CriticalSection”。或者编写自己的用户空间锁,例如就像在 TinyThread (http://tinythreadpp.bitsnbites.eu/) 中一样。这些是用 ASM 编写的,实际锁定和解锁的开销几乎为零。

  2. 一旦您确定锁本身很快,如果事情仍然运行缓慢,那是因为您有锁争用。例如。多个线程都需要锁定相同的资源。在您的用例中,请考虑类似“读/写”互斥锁。这是一个具有读互斥锁和写互斥锁的类。互斥体上的“readLock”方法仅锁定几个周期以增加互斥体上的引用计数。 “readUnlock”确实会减少引用计数。 "writeLock" 锁定读取互斥体,并设置一个标志以防止读取器锁定读取互斥体。然后锁定写互斥体并执行写操作。因此,您可以保证一次只能发生一次写入操作,并且在写入期间不会发生读取。但允许同时读取。

【讨论】:

  • 您的第二个选项听起来更像是读/写锁,而您可以同时进行多次读取与一次写入互斥。 en.wikipedia.org/wiki/Read/write_lock_pattern
  • 是的。就是这样。称它为读/写互斥锁是错误的吗?所有写操作都是互斥的。
  • 我不会说它错了,但它通常被称为读/写锁。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-03
  • 2016-08-14
  • 2021-12-09
  • 2012-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多