7 - latch和barrier

C++20提供了两种线程协调机制,std::latch(闩)和std::barrier(屏障),允许任何数量的线程阻塞阻塞,直至期待数量的线程到达。

latch

latch一次性使用 的线程协调点。一旦给定数量的线程到达latch点后,所有线程都会解出阻塞并继续执行,就是个一次性计数器。

latchstd::latch实现,在<latch>中定义,它的常用方法如下:

  • 构造函数:接收到达latch点的所需线程数。
  • arrive_and_wait():递减latch的计数器并阻塞,直至被唤醒。
  • wait():不递减计数器并阻塞,直至被唤醒。
  • try_wait():检查计时器是否到达零。
  • count_down():减少计时器,不阻塞。

例如要先加载数据到内存,然后让10个线程并行处理它,可以这样做:

latch startLatch(1);
vector<jthread> threads;

for (int i = 0; i < 10; ++i)
{
	threads.push_back(jthread([&startLatch]
	{
		// 线程初始化...

		// 等待, 直到latch计数器为0
		startLatch.wait();

		// 处理数据
	}));
}

// 读数据到内存...

// 数据读好后, 减少latch计数器,唤醒所有线程
startLatch.count_down();

在上述代码中,先初始化10个线程并让它们等待,然后读数据到内存中,读好后让latch计数器归零,唤醒所有调用latch.wait()的线程去处理数据。

barrier

屏障barrier可重用的 线程协调机制。当给定数量的线程到达barrier后,会执行完成阶段的回调,解除所有阻塞线程,并重置计数器,开始下一个阶段。在每个阶段中,可调节下阶段的预期线程数。

barrierstd::barrier实现,在barrier中定义。它常用的方法就是arrive_and_wait()。其余可用方法详见标准库指南。

例如如下代码,它启动4个线程,在循环中连续执行某些操作。在每次迭代中,所有线程都是用barrier进行同步:

void onComplete() noexcept { /* ... */ }

int main()
{
	const size_t thread_num = 4;
	barrier barrierPoint(thread_num, onComplete);
	vector<jthread> threads;

	for (int i = 0; i < thread_num; ++i)
	{
		threads.push_back(jthread([&barrierPoint](stop_token token)
		{
			while (!token.stop_requested())
			{
				// 做计算

				// 阻塞当前线程
				barrierPoint.arrive_and_wait();
			}
		}));
	}
}

参考资料

  • C++20高级编程
  • 同步操作 | 现代C++并发编程教程 (mq-b.github.io)