std::async创建的线程未按预期并行执行耗时超预期的原因及解决方法
嘿,我来帮你分析这个问题~先看看你的代码里的核心问题:
你的测试代码中,每个线程都频繁调用std::cout进行输出——比如进入线程时的日志、循环10次输出字符,还有最后的换行。而std::cout是线程安全但并非无锁实现的:多个线程同时操作cout时,底层会自动加锁避免输出乱码,但这会导致线程之间互相等待锁释放,产生额外的阻塞时间。你看到的2900ms比预期多出来的900ms,很大一部分就是这种锁竞争带来的开销。
另外还有个小细节:全局的gClock是程序启动时初始化的,而fnTestAsync里的begin是函数内的clock(),不过这对整体耗时的影响不大,核心问题还是cout的竞争。
接下来给你几个可行的解决办法:
解决方法1:减少cout调用次数(推荐)
让每个线程先把要输出的内容拼接成一个完整的字符串,然后一次性输出到cout,这样每个线程只触发一次cout的锁操作,大幅降低锁竞争的概率:
修改threadPool函数如下:
#include <sstream> // 需要包含这个头文件 char threadPool(char c) { std::ostringstream oss; // 把所有要输出的内容先写到内存流里 oss << "enter thread :" << c << " cost time:" << clock() - gClock << "\n"; for (int i = 0; i < 10; i++) { oss << c; } oss << "\n"; // 一次性输出到cout std::cout << oss.str(); std::this_thread::sleep_for(std::chrono::seconds(2)); return c; }
这样修改后,每个线程只做一次cout输出,锁竞争的时间会大幅减少,整体耗时会非常接近你预期的2000ms。
解决方法2:关闭cout与C标准IO的同步
如果你的程序里没有混用C语言的printf、puts等IO函数,可以关闭cout和C标准IO的同步,减少底层的锁开销:
在main函数开头添加这两行代码:
std::ios_base::sync_with_stdio(false); std::cout.tie(nullptr);
这个操作会让cout绕过C标准IO的同步机制,输出效率更高,也能减少部分锁竞争的开销。但要注意,如果同时使用C和C的IO函数,可能会出现输出顺序混乱的问题,所以只适合纯C IO的场景。
解决方法3:用互斥锁保护cout输出(适合需要有序输出的场景)
如果希望每个线程的输出内容不会互相穿插(比如完整看到一个线程的所有输出后再看另一个的),可以用std::mutex来保护cout的调用:
首先添加互斥锁的头文件并定义全局锁:
#include <mutex> std::mutex cout_mutex;
然后修改threadPool里的输出部分:
char threadPool(char c) { // 保护第一个输出块 { std::lock_guard<std::mutex> lock(cout_mutex); std::cout << "enter thread :" << c << " cost time:" << clock() - gClock << std::endl; } std::this_thread::sleep_for(std::chrono::seconds(2)); // 保护循环输出和换行 { std::lock_guard<std::mutex> lock(cout_mutex); for (int i = 0; i < 10; i++) std::cout << c; std::cout << std::endl; } return c; }
这样每个输出块都会被互斥锁保护,输出不会乱序,但因为锁的存在,输出阶段还是会有串行的部分,不过比原代码的频繁锁竞争好很多,整体耗时也会更接近预期。
总结一下:核心问题就是多线程下频繁调用std::cout导致的锁竞争开销,通过减少cout调用次数、关闭同步或者加锁保护的方式都能解决,其中第一种方法(先缓存再一次性输出)效果最好,也最推荐。
备注:内容来源于stack exchange,提问作者YuHuanTin

