【问题标题】:concurrent_vector for 2d array二维数组的 concurrent_vector
【发布时间】:2011-11-04 15:10:54
【问题描述】:

我目前正在尝试使用tbb::concurrent_vector<T> 表示一个二维数组。这个二维数组将被许多不同的线程访问,这就是为什么我希望它能够最有效地处理并行访问。

我想出了两个解决方案:

  • 使用tbb::concurrent_vector<tbb::concurrent_vector<T> > 来存储它。

  • 将所有内容存储在tbb::concurrent_vector<T> 中,并通过x * width + y 访问元素

我更喜欢第二个,因为我不想锁定一整行来访问一个元素(因为我假设要访问元素 array[x][y],tbb 实现将锁定 xth 行,然后yth 元素)。

我想知道哪种解决方案对您来说更好。

【问题讨论】:

  • std::vector<tbb::concurrent_vector> 呢?我不知道足够多的 TBB 来回答,但这对我来说看起来不错。
  • 好吧,如果 std::vector 保存行 (tbb::concurrent_vector),如果我尝试在一个线程中添加新行并在另一个线程中删除一行,我会遇到麻烦。跨度>
  • 这可能取决于您希望容器处理哪些并发操作。

标签: c++ multithreading parallel-processing tbb concurrent-vector


【解决方案1】:

首先,我认为tbb::concurrent_vector 可能存在一些混淆。此容器类似于std::vector,但具有线程安全的大小调整功能,但由于内部存储布局,元素访问速度较慢。

您可以阅读更多关于它的信息here

在您的情况下,由于您提出的第二种解决方案(带有x * width + y 索引的一维数组),我假设您的算法不涉及密集的多线程数组大小调整。因此,与 std::vector 这样的单线程容器相比,您不会从 tbb::concurrent_vector 中受益。

我猜你假设 tbb::concurrent_vector 保证线程安全的元素访问,但它没有 - 引用 tbb::concurrent_vector::operator[] doc:

获取给定索引处元素的引用。

此方法对于并发读取是线程安全的,并且在向量增长时也是如此,只要调用线程检查了该索引

如果您不调整数组大小,您只对第一部分感兴趣:线程安全并发读取。但是 std::vector 甚至原始 C 数组都可以为您提供相同的功能。另一方面,两者都不提供线程安全的任意访问(读/写元素)。

您必须使用锁来实现它,或者找到另一个为您执行此操作的库(可能是来自 STLPort 的 std::vector,但我不确定)。虽然这会非常低效,因为每次访问 2D 数组中的元素都会涉及线程同步开销。虽然我不知道您究竟要实现什么,但同步所花费的时间很可能比您的实际计算时间长。

现在回答你的问题,即使在单线程设置中,使用一维数组来表示 ND 数组总是更好,因为计算索引 (x * width + y) 比额外的内存访问更快真正的ND阵列。 对于并发向量,情况更是如此,因为在最佳情况下(没有冲突的行访问)您将拥有两倍的锁开销,并且在发生冲突的情况下更多。

因此,在您提出的两种解决方案中,我会毫不犹豫地选择第二种:一维数组(不是必需的 tbb::concurrent_vector),具有足够的元素访问锁定功能。

根据您的算法和不同线程的访问模式,另一种方法 - 用于图像编辑软件(gimp、photoshop...) - 是基于图块的:

template<typename T> struct Tile {
    int offsetX, int offsetY;
    int width, height;
    Not_ThreadSafe_Vector<T> data;
};
ThreadSafe_Vector< Tile<T> > data

Not_ThreadSafe_Vector 可以是任何不锁定元素访问的容器,例如std::vector ; ThreadSafe_Vector 是一个具有线程安全读/写元素访问权限的容器(不是tbb::concurrent_vector!)。 这样,如果您的访问模式中有一些局部性(一个线程更有可能访问靠近其先前访问的元素而不是远处的元素),那么每个线程都可以处理来自单个图块的大部分数据时间,并且您只有在切换到另一个图块时才会有同步开销。

【讨论】:

    【解决方案2】:

    tbb::concurrent_vector 对于访问元素(读取、写入、获取地址)和增长向量是完全线程安全的。查看英特尔员工 Arch D. Robison here 的回复。

    但是,清除或销毁向量的操作不是线程安全的(请参阅 $6.2.1 英特尔 TBB 教程 pdf 文档)。也就是说,如果tbb::concurrent_vector 上还有其他操作正在进行,则不要调用clear()

    正如 Antoine 所提到的,由于效率和性能,始终首选将 ND 阵列作为一维阵列处理。

    【讨论】:

    • tbb::concurrent_vector 对于访问元素来说并不是完全线程安全的。 Arch 的短语“concurrent_vector 允许多个线程访问(读取、写入、获取地址)同一元素。它不会序列化访问,因此这些访问是否安全取决于数据类型。”意味着您可以同时访问元素,但安全性取决于特定的元素类型。
    • @Alex 不确定我理解。您是否有 2 种数据类型之间潜在差异的示例以及它会导致什么?如果tbb::concurrent_vector 不是线程安全的,那为什么要这样做呢?序列化访问不是线程安全的。那完全是另一回事(尽管您在下划线很好)。同样,它是线程安全的,除非还有我不明白的地方。
    • tbb::concurrent_vector 提供容器范围的线程安全操作,例如push_backgrow_by 等等。虽然项目访问功能也是安全的,但在某些情况下不能同时修改项目。考虑tbb::concurrent_vector&lt;std::vector&lt;int&gt;&gt; cv。并发cv.push_back(std::vector&lt;int&gt;{}) 是安全的,但并发cv[0].push_back(0) 是不安全的,因为std::vector&lt;int&gt; 不是线程安全的。但是,使用tbb::concurrent_vector&lt;std::atomic&lt;int&gt;&gt; cv 同时修改元素是安全的,例如cv[0]++.
    猜你喜欢
    • 1970-01-01
    • 2017-01-19
    • 1970-01-01
    • 2019-01-23
    • 2012-02-18
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    相关资源
    最近更新 更多