同步push_back和std::thread

2023-11-29

My code

void build(std::vector<RKD <DivisionSpace> >& roots, ...) {
  try {
    // using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
    std::lock_guard<std::mutex> lck (mtx);
    roots.push_back(RKD<DivisionSpace>(...));
  }
  catch (const std::bad_alloc&) {
    std::cout << "[exception caught when constructing tree]\n";
    return;
  }
}

现在,实际工作应该串行完成,而不是并行完成。

的构造函数RKD可以与其他构造函数并行运行RKD。然而,将物体推回原处std::Vector是一个临界区,对吗?

我要构建的对象的数量是已知的。实际上,它的范围将是 [2, 16]。理论上它可以是任何正数。

另外,我对它们插入容器的顺序不感兴趣。

所以我可以做类似的事情:

RKD tree = RKD(...);
mutex_lock(...);
roots.push_back(tree);

但这就意味着抄袭,不是吗?

我应该怎么做才能使我的代码并行?


我决定使用锁(而不仅仅是互斥锁),因为this answer.


Tomasz Lewowski 在他的评论中提出的建议非常简单,并且基于以下观察:push_back on a std::vector可能需要重新分配后备存储并复制(或者最好移动)元素。这构成了需要同步的关键部分。

对于接下来的示例,假设我们想要一个填充前 12 个素数的向量,但我们不关心它们的顺序。 (我刚刚在这里对数字进行了硬编码,但假设它们是通过一些昂贵的计算获得的,这对于并行计算是有意义的。)在以下场景中存在危险的竞争条件。

std::vector<int> numbers {};  // an empty vector

// thread A             // thread B             // thread C

numbers.push_back( 2);  numbers.push_back(11);  numbers.push_back(23);
numbers.push_back( 3);  numbers.push_back(13);  numbers.push_back(27);
numbers.push_back( 5);  numbers.push_back(17);  numbers.push_back(29);
numbers.push_back( 7);  numbers.push_back(19);  numbers.push_back(31);

另外还有一个问题push_back。如果两个线程同时调用它,它们都将尝试在同一索引处构造一个对象,这可能会带来灾难性的后果。所以问题并没有解决reserve(n)在分叉线程之前。

但是,由于您事先知道元素的数量,因此您可以简单地assign他们到一个特定的位置std::vector而不改变其大小。如果不改变大小,就没有临界区。因此,以下场景中不存在竞争。

std::vector<int> numbers(12);  // 12 elements initialized with 0

// thread A          // thread B          // thread C

numbers[ 0] =  2;    numbers[ 1] =  3;    numbers[ 2] =  5;
numbers[ 3] =  7;    numbers[ 4] = 11;    numbers[ 5] = 13;
numbers[ 6] = 17;    numbers[ 7] = 19;    numbers[ 8] = 23;
numbers[ 9] = 29;    numbers[10] = 31;    numbers[11] = 37;

当然,如果两个线程都尝试写入same指数,比赛将再次举行。幸运的是,在实践中防止这种情况并不困难。如果您的向量有 n 个元素并且您有 p 个线程,则线程 i 仅写入元素 [i n / p, (i + 1) n / p)。请注意,这比仅当 j mod p = i 写入索引 j 处的元素更好。 i>i 因为它会减少缓存失效。所以上面例子中的访问模式不是最优的,最好是这样的。

std::vector<int> numbers(12);  // 12 elements initialized with 0

// thread A          // thread B          // thread C

numbers[ 0] =  2;    numbers[ 4] = 11;    numbers[ 8] = 23;
numbers[ 1] =  3;    numbers[ 5] = 13;    numbers[ 9] = 29;
numbers[ 2] =  5;    numbers[ 6] = 17;    numbers[10] = 31;
numbers[ 3] =  7;    numbers[ 7] = 19;    numbers[11] = 37;

到目前为止,一切都很好。但如果你没有怎么办?std::vector<int> but a std::vector<Foo>? If Foo没有默认构造函数,那么

std::vector<Foo> numbers(10);

将无效。即使它有一个,创建许多昂贵的默认构造对象只是为了很快重新分配它们而不检索值也是令人无法容忍的。

当然,大多数设计良好的类应该有一个非常便宜的默认构造函数。例如,一个std::string默认构造为不需要内存分配的空字符串。一个好的实现会将默认构造字符串的成本降低到仅

std::memset(this, 0, sizeof(std::string));

如果编译器足够聪明,可以发现我们正在分配和初始化整个std::vector<std::string>(n),它也许能够进一步优化为一次调用

std::calloc(n, sizeof(std::string));

所以如果有任何机会你可以Foo廉价地默认构造和分配,你就完成了。但是,如果这变得很困难,您可以通过将其移至堆来避免该问题。智能指针可以廉价地默认构造,所以

std::vector<std::unique_ptr<Foo>> foos(n);

最终将减少到

std::calloc(n, sizeof(std::unique_ptr<Foo>));

无需你做任何事Foo。当然,这种便利是以每个元素的动态内存分配为代价的。

std::vector<std::unique_ptr<Foo>> foos(n);

// thread A                    // thread B                           // thread C

foos[0].reset(new Foo {...});  foos[n / 3 + 0].reset(new Foo {...});  foos[2 * n / 3 + 0].reset(new Foo {...});
foos[1].reset(new Foo {...});  foos[n / 3 + 1].reset(new Foo {...});  foos[2 * n / 3 + 1].reset(new Foo {...});
foos[2].reset(new Foo {...});  foos[n / 3 + 2].reset(new Foo {...});  foos[2 * n / 3 + 2].reset(new Foo {...});
...                            ...                                    ...

这可能没有您想象的那么糟糕,因为虽然动态内存分配不是免费的,sizeof a std::unique_ptr非常小,所以如果sizeof(Foo)很大,你会得到一个更紧凑的向量,迭代速度更快。当然,这完全取决于您打算如何使用您的数据。

如果您事先不知道元素的确切数量或者担心会弄乱索引,还有另一种方法可以做到这一点:让每个线程填充自己的向量并在最后合并它们。继续素数的例子,我们得到了这个。

std::vector<int> numbersA {};  // private store for thread A
std::vector<int> numbersB {};  // private store for thread B
std::vector<int> numbersC {};  // private store for thread C

// thread A              // thread B              // thread C

numbersA.push_back( 2);  numbersB.push_back(11);  numbersC.push_back(23);
numbersA.push_back( 3);  numbersB.push_back(13);  numbersC.push_back(27);
numbersA.push_back( 5);  numbersB.push_back(17);  numbersC.push_back(29);
numbersA.push_back( 7);  numbersB.push_back(21);  numbersC.push_back(31);

// Back on the main thread after A, B and C are joined:

std::vector<int> numbers(
    numbersA.size() + numbersB.size() + numbersC.size());
auto pos = numbers.begin();
pos = std::move(numbersA.begin(), numbersA.end(), pos);
pos = std::move(numbersB.begin(), numbersB.end(), pos);
pos = std::move(numbersC.begin(), numbersC.end(), pos);
assert(pos == numbers.end());

// Now dispose of numbersA, numbersB and numbersC as soon as possible
// in order to release their no longer needed memory.

(The std::move上面代码中使用的是算法库中的一个.)

这种方法具有最理想的内存访问模式,因为numbersA, numbersB and numbersC正在写入完全独立分配的内存。当然,我们还要做一些额外的事情顺序的连接中间结果的工作。请注意,效率在很大程度上取决于这样一个事实:与查找/创建元素的成本相比,移动分配元素的成本可以忽略不计。至少如上面所写,代码还假设您的类型有一个廉价的默认构造函数。当然,如果您的类型不是这种情况,您可以再次使用智能指针。

我希望这能为您提供足够的想法来优化您的问题。

如果您以前从未使用过智能指针,请看一下“C++ 中的 RAII 和智能指针”并查看标准库的动态内存管理库。上面显示的技术当然也适用于std::vector<Foo *>但在现代 C++ 中我们不再使用这样的拥有资源的原始指针。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

同步push_back和std::thread 的相关文章

随机推荐

  • 如何获取 Highcharts 树图中钻取事件的当前级别?

    似乎在 Highcharts Treemap 中没有触发下钻事件 我需要执行一些任务 例如在向下钻取和向上钻取事件上显示当前级别编号的警报 如何在树状图中完成此操作 我看到此时您可以捕获重绘事件并准备一个简单的 解析器 来检查ID 第一级的
  • Bash 子 shell 与 ssh 命令的行为非常奇怪

    考虑脚本 s2 bin bash ssh localhost tail 和脚本 s1 bin bash s2 sleep 1000 wait 现在呼叫 s1 它不会调用 ssh 命令 如果你删除 s1 中的 它就会 如果你直接调用 s2 就
  • 如何为 Intellij 编译器提供更多堆空间?

    当我制作 Intellij 项目时 我不断收到以下内存不足错误 我已经增加了堆大小idea vmoptions Xms128m Xmx2048m XX MaxPermSize 1024m XX ReservedCodeCacheSize 6
  • 使用 RSelenium 在 Chrome 中启用 Adblocker 扩展

    我正在从这个页面抓取 R 中的数据 显示弹出广告 这些广告会干扰脚本 因此我想启用广告拦截器扩展 https chrome google com webstore detail adblock gighmmpiobklfepjocnamgk
  • 如何根据用户或电子邮件过滤仪表板内容?

    我正在尝试在 Google Data Studio 上构建一个仪表板 该仪表板会根据访问仪表板的用户 使用其 Google 帐户凭据 自动过滤数据 以下是一些细节 因此 这个仪表板应该为员工显示一些汇总数据 但我们希望限制可见性并实施一些访
  • (无意中)在迭代列表时跳过项目

    我有一个列表 我想从中删除其他列表中未出现的项目 我尝试过以下方法 for w in common for i in range 1 n if not w in words i common remove w 但是 这无法删除某些项目 添加
  • 如何以编程方式使用 Google Analytics API 函数获取指标和维度列表?

    我正在尝试使用 Google Analytics API 需要通过 Google Analytics API 获取指标和维度列表 如何在 php 中使用 Google Analytics API 函数获取指标和维度列表 这无法通过 API
  • 团队构建:使用 MSDeploy 本地发布

    我刚刚开始使用团队构建功能 我发现做一些非常简单的事情所需的大量事情有点令人不知所措 我目前的设置是一个包含 Web 应用程序 组装应用程序和测试应用程序的解决方案 Web 应用程序设置了一个通过文件系统发布的 PublishProfile
  • Magento:将商品添加到购物车时如何更改商品价格

    当我将商品添加到购物车时 我希望能够以编程方式 而不是通过目录或购物车规则 更改商品价格 以下回答以编程方式将产品添加到购物车并更改价格展示了如何在更新购物车时执行此操作 而不是在添加产品时执行此操作 Thanks 您可以使用观察者类来监听
  • 使用 TypeScript 的 React 组件中的默认属性值

    我不知道如何使用 Typescript 为我的组件设置默认属性值 这是源代码 class PageState export class PageProps foo string bar export class PageComponent
  • hibernate加载对象图的正确方法是什么

    假设我有 3 个表 GrandCat Cat 和 Kitt 它们具有一对多的关系 所以我有以下课程 所有关联都是延迟加载 GrandCat int age Set
  • 在 MFC 中添加加速器(快捷方式) - 如何?

    我找到了这个链接 http support microsoft com kb 222829 但我无法理解那么多 好的 我知道我需要将其添加到我的头文件中 HACCEL m hAccelTable 然后是这个 m hAccelTable Lo
  • LaunchDaemons 和环境变量

    一段时间以来 我注意到我的 MacPorts 安装的 Apache2 实例在我启动时尚未启动 MacPorts Apache2 在启动时停止启动 LaunchDaemon 已加载 今天我在日志文件中发现了一些可能指向答案的内容 但我找不到任
  • 启用项目功能时启用 Rust nightly 功能

    在库箱中 我想按需提供回溯并使用 Rust 夜间回溯功能 为了做到这一点 Rust 需要设置 feature backtrace 在我的板条箱根部 有没有办法表达仅当设置了创建级别功能 backtrace 时 我才需要 Rust 夜间功能
  • 使用 Excel 中的 VBA 将 2 个单元格的内容合并到另一个第 3 个单元格中

    我有两个单元格 A1 和 A2 其中每一个的内容都是一个字符串 A1 你好 A2 世界 我的目标是将 A1 和 A2 的内容合并到另一个单元格中 例如A3即A3的内容应该是 你好世界 我想使用 VBA 宏来执行此操作 而不仅仅是将字符串作为
  • 使用 dplyr 按多组进行汇总

    我正在尝试使用 dplyr 来总结基于 2 个组的数据集 年份 和 区域 数据集如下所示 Year Area Num 1 2000 Area 1 99 2 2001 Area 3 85 3 2000 Area 1 60 4 2003 Are
  • 使用 window.onscroll 事件检测页面/框架滚动

    我想定位一个DIV位于页面内 以便即使用户垂直滚动页面 用户也可以看到它 该页面的顶部有一个标题 即75 px高的 现在 当用户位于页面顶部并且没有垂直滚动时 DIV必须位于标题下方 然而 一旦用户滚动页面导致标题消失 同样的DIV现在必须
  • 如何在 PHP 中检查 PDF 文件是否受密码保护 [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 使用 PHP 上传多个文件时 如何检查上传的 PDF 文件是否受密码保护 如果它
  • java中如何将时间戳转换为日期和时间?

    我有一个来自 Linux 服务器的 json 时间戳 我想使用 Java 将其转换为简单的日期时间格式 我需要以下格式的日期和时间 dd mm yyyy hh mm ss 这是我的 JSON 数据 batch date 1419038000
  • 同步push_back和std::thread

    My code void build std vector