我们在哪里可以在std::闩锁上使用std::屏障? 最近我听到了新的C++标准的特点,即:
我不知道在什么情况下,它们相互适用和有用我们在哪里可以在std::闩锁上使用std::屏障? 最近我听到了新的C++标准的特点,即:,c++,visual-c++,std,c++-experimental,C++,Visual C++,Std,C++ Experimental,我不知道在什么情况下,它们相互适用和有用 如果有人能举一个例子,说明如何明智地使用它们中的每一个,那将非常有帮助 非常简短的回答 他们的目标完全不同: 当您有一组线程并且希望同时跨线程进行同步时,屏障非常有用,例如,要同时对所有线程的数据进行操作 如果您有一堆工作项,并且希望知道它们何时都被处理,并且不一定对哪个线程处理它们感兴趣,那么闩锁非常有用 更长的答案 当您有一个执行某些处理的工作线程池和一个共享的工作项队列时,通常使用屏障和闩锁。这不是使用它们的唯一情况,但这是一种非常常见的情况
- 如果有人能举一个例子,说明如何明智地使用它们中的每一个,那将非常有帮助李>
- 当您有一组线程并且希望同时跨线程进行同步时,屏障非常有用,例如,要同时对所有线程的数据进行操作
- 如果您有一堆工作项,并且希望知道它们何时都被处理,并且不一定对哪个线程处理它们感兴趣,那么闩锁非常有用
const size_t worker_count = 7; // or whatever
std::vector<std::thread> workers;
std::vector<Proc> procs(worker_count);
Queue<std::function<void(Proc&)>> queue;
for (size_t i = 0; i < worker_count; ++i) {
workers.push_back(std::thread(
[p = &procs[i], &queue]() {
while (auto fn = queue.pop_back()) {
fn(*p);
}
}
));
}
工作原理:
lack.count\u down()
,有效地减少从work.size()
开始的内部计数器lack.wait()
返回,生产者线程知道所有工作项都已处理- 闩锁计数是将要处理的工作项的数量,而不是工作线程的数量
方法可以在每个线程上调用零次、一次或多次,不同线程的调用次数可能不同。例如,即使将7条消息推送到7个线程上,也可能是所有7个项目都被处理到同一个线程上(而不是每个线程一个),这很好count\u down()
- 其他不相关的工作项可以与这些工作项交错(例如,因为它们被其他生产者线程推到队列上),这也没关系
- 原则上,在所有工作线程完成所有工作项的处理之前,
可能不会被调用。(这是编写线程化代码时需要注意的一种奇怪情况。)但没关系,这不是竞争条件:latch.wait()
在这种情况下会立即返回latch.wait()
- 使用闩锁的另一种选择是,除了此处显示的队列之外,还有另一个队列包含工作项的结果。线程池回调将结果推送到该队列上,而生产者线程将结果从队列中弹出。基本上,它与此代码中的
方向相反。这也是一个非常有效的策略,事实上,如果说它更常见的话,但在其他情况下,闩锁更有用队列
typedef Fn std::function<void()>;
Fn completionFn = [&procs]() {
// Do something with the whole vector of Proc objects
};
auto barrier = std::make_shared<std::barrier<Fn>>(worker_count, completionFn);
auto workerFn = [barrier](Proc&) {
barrier->count_down_and_wait();
};
for (size_t i = 0; i < worker_count; ++i) {
queue.push_back(workerFn);
}
工作原理:
关键思想是在每个线程中等待屏障两次,并在其间执行工作。第一次等待的目的与前一个示例相同:它们确保在开始此工作之前完成队列中任何较早的工作项。第二次等待确保队列中的任何后续项在该工作完成之前不会启动
注意事项:
注释与前面的屏障示例基本相同,但有一些不同之处:
- 一个不同之处是,由于屏障没有绑定到特定的完成函数,因此更有可能在多个用途之间共享它,就像我们在闩锁示例中所做的那样,避免使用共享指针
- 这个例子让人觉得在没有完成函数的情况下使用屏障要复杂得多,但这只是因为这种情况并不适合他们。有时,你所需要的只是到达障碍物。例如,虽然我们在线程启动之前初始化了一个队列,但可能每个线程都有一个队列,但在线程的运行函数中进行了初始化。在这种情况下,屏障可能只是表示队列已经初始化,并准备好让其他线程相互传递消息。在这种情况下,您可以使用没有完成功能的屏障,而不需要像这样等待两次
- 实际上,您可以为此使用闩锁,调用
,然后调用count\u down()
来代替wait()
。但是使用屏障更有意义,因为调用组合函数更简单,而且使用屏障可以更好地向代码的未来读者传达您的意图count\u down\u和\u wait()
- 在任何情况下,以前的“危险”警告仍然适用
闩锁
参考:“不可能增加或重置计数器,从而使闩锁成为一次性屏障。”以及来自链接的屏障
参考:“与std::experimental::latch不同,屏障是可重复使用的;一旦参与的线程从屏障的同步点释放出来,它们就可以重用同一个屏障。“那么你需要重用屏障吗,还是一次性的?@Someprogrammerdude重用屏障…我希望这个链接可以帮助你“[…]在哪些情况下它们是适用和有用的[…]“可能与现在定义的方式不同:std::latch
的问题在于它需要一个显式的同步
typedef Fn std::function<void()>;
Fn completionFn = [&procs]() {
// Do something with the whole vector of Proc objects
};
auto barrier = std::make_shared<std::barrier<Fn>>(worker_count, completionFn);
auto workerFn = [barrier](Proc&) {
barrier->count_down_and_wait();
};
for (size_t i = 0; i < worker_count; ++i) {
queue.push_back(workerFn);
}
auto barrier = std::make_shared<std::barrier<>>(worker_count);
auto workerMainFn = [&procs, barrier](Proc&) {
barrier->count_down_and_wait();
// Do something with the whole vector of Proc objects
barrier->count_down_and_wait();
};
auto workerOtherFn = [barrier](Proc&) {
barrier->count_down_and_wait(); // Wait for work to start
barrier->count_down_and_wait(); // Wait for work to finish
}
queue.push_back(std::move(workerMainFn));
for (size_t i = 0; i < worker_count - 1; ++i) {
queue.push_back(workerOtherFn);
}