你好,我雨乐~
今天,我们聊聊多线程编程中的一个新特性std::latch
。
假如有这样一个需求,并发处理多个任务,只有这几个任务都完成以后,才继续执行后面的流程。我想,很多人跟我一样,第一反应是使用std::atomic来处理,如下:
std::atomic< int > counter ( 0 ) ; //共享原子计数器
void worker () {
std::cout <<"启动工作线程"<< std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
count.fetch_add(1);// 将计数器增加 1
std::cout <<"工作线程完成"<< std::endl;
}
int main () {
std::thread t1 (worker) ;
std::thread t2 (worker) ;
std::thread t3 (worker) ;
while(count <3){
// 等待计数器达到线程数
}
t1.join();
t2.join();
t3.join();
std::cout <<"所有工作线程均已完成..."<< std::endl;
return0;
}
这段代码在实现上没问题,但是有一种更为直观的方式,那就是C++20引入的std::latch
。
latch
这块,我想用中文翻译来着,无奈总感觉有些别扭,所以还是保持原称比较好。
概念
std::latch
是 C++20 引入的一个同步原语,用于在多线程环境中协调多个线程的执行,确保某些线程在执行继续之前等待其他线程达到某个特定的状态。通过一个计数器来控制线程的同步。当计数器的值为 0 时,所有等待的线程可以继续执行。否则,调用 wait()
方法的线程将会阻塞,直到计数器值递减至 0。
使用
现在使用std::latch来实现文章一开始的功能,如下:
#include <iostream>
#include <thread>
#include <latch> // 引入 std::latch 需要这个头文件
std::latch lock(3);// 创建一个 latch,计数器初始为 3
void worker() {
std::cout <<"启动工作线程"<< std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));// 模拟工作
std::cout <<"工作线程完成"<< std::endl;
lock.count_down();// 每个线程完成工作后,将 latch 计数器减 1
}
int main() {
std::thread t1(worker);// 创建线程 t1
std::thread t2(worker);// 创建线程 t2
std::thread t3(worker);// 创建线程 t3
lock.wait();// 主线程在这里等待,直到 latch 的计数器为 0,即所有线程都调用了 count_down()
t1.join();// 等待 t1 完成
t2.join();// 等待 t2 完成
t3.join();// 等待 t3 完成
std::cout <<"所有工作线程均已完成..."<< std::endl;
return0;
}
在上面代码中:
•创建一个全局对象std::latch
对象lock,并将其初始值设置为3。这意味着主线程将会等待,直到有 3 个线程调用 lock.count_down()
•worker
函数模拟了一个工作线程。每个工作线程在启动时会打印 "启动工作线程"
,然后休眠 1 秒钟(模拟一些任务),最后打印 "工作线程完成"
,并调用 lock.count_down()
,使得 lock
的计数器减少 1•主线程调用 lock.wait()
,它将阻塞等待,直到 lock
的计数器变为 0•当每个工作线程调用 lock.count_down()
后,计数器将逐渐减少。因为初始值为 3,所以每个线程完成后将计数器减少 1。等到所有 3 个线程都调用了 count_down()
,计数器变为 0,主线程的 wait()
调用将返回,继续执行主线程中的代码。•主线程在 wait()
返回后,调用 t1.join()
, t2.join()
, 和 t3.join()
,等待所有线程完成。•最后,主线程打印 "所有工作线程均已完成..."
。
需要注意的是 lock.wait()返回的前提是 lock的计数器为0,否则一直等待~
以上
如果对本文有疑问可以加笔者微信直接交流,笔者也建了C/C++相关的技术群,有兴趣的可以联系笔者加群。
1、呃,竟然死锁了~