闩锁和屏障提供了一种同步方式,可以让一个或者多个阻塞,直到计数器减少至零。
latch
latch可以看作一个计数器,构造一个latch对象时会初始化计数器的数值。调用count_down接口可以减少计数器,所有调用wait接口的线程将阻塞直到计数器变为零。
latch还提供了try_wait接口用于检测计数器是否为零,arrive_and_wait会减少计数器并阻塞直到计数器变为零。
latch的使用场景可以是:一些工作线程需要等待一些前置任务线程完成才能开始,这种情况下工作线程使用wait接口等待,每个前置任务线程完成后调用count_down接口减少计数器,当所有的前置任务完成后,计数器减少到零,工作线程开始执行。
代码示例:
std::latch latch(5);
std::vector<std::jthread> workers(3);
std::vector<std::jthread> preWorkers(5);auto FuncWork = [&](int i)
{latch.wait();std::string res = "work: " + std::to_string(i) + "\n";std::cout << res;
};auto FuncPreWorke = [&](int i)
{std::string res = "prework: " + std::to_string(i) + "\n";std::cout << res;latch.count_down();
};for (std::size_t i = 0; i < workers.size(); ++i)
{workers[i] = std::jthread(FuncWork, (int)i);
}for (std::size_t i = 0; i < preWorkers.size(); ++i)
{preWorkers[i] = std::jthread(FuncPreWorke, (int)i);
}
可能的输出结果:
prework: 2
prework: 0
prework: 1
prework: 3
prework: 4
work: 2
work: 0
work: 1
barrier
和latch相同的是,barrier也可以阻塞线程直到计数器变为零:
template<class CompletionFunction>
class barrier;
和latch不同的是,latch的计数器到达零之后不能重置,是一种一次性的同步工具,而barrier是可重用的,当barrier计数器变为零后,将调用CompletionFunction函数,并解除所有等待线程的锁定,然后计数器将重置,进入下一轮的同步。
barrier的使用场景可以是:并行地多个阶段的任务,每个阶段的任务需要在前一个阶段地任务完成后才能开始。
代码示例:
auto CompleteFunc = []() noexcept
{std::cout << "Complete" << std::endl;
};std::vector<std::jthread> workers(5);
std::barrier barrier(workers.size(), CompleteFunc);auto WorkFunc = [&](int i)
{std::string str = "work: " + std::to_string(i) + " step 1\n";std::cout << str;barrier.arrive_and_wait();str = "work: " + std::to_string(i) + " step 2\n";std::cout << str;barrier.arrive_and_wait();
};for (std::size_t i = 0; i < workers.size(); ++i)
{workers[i] = std::jthread(WorkFunc, (int)i);
}
可能的输出结果:
work: 1 step 1
work: 2 step 1
work: 0 step 1
work: 3 step 1
work: 4 step 1
Complete
work: 4 step 2
work: 3 step 2
work: 0 step 2
work: 2 step 2
work: 1 step 2
Complete