C++:错误代码分析<2>
🌏主页:R6bandito_
🚀所属专栏:C/C++错误代码收集整理
源码
考虑以下代码:
void do_some_work() {std::cout << "Do some work" << std::endl;
}int main(int argc, const char* argv[]) {std::vector<std::thread> threads;for (int i = 0; i < 10; i++) {threads.emplace_back(std::thread(do_some_work));}for (auto& th : threads) {th.join();}return 0;
}
问题描述
根据代码,理想输出应是打印10行字符串,每行的字符串都是:"Do some work"。但是实际输出却出现了严重的格式问题。且每次的输出格式都会发生一定变化。部分示例错误输出如下:
/*第一次*/
Do some workDo some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some workDo some work/*第二次*/
Do some workDo some workDo some workDo some workDo some work
Do some workDo some work
Do some workDo some workDo some work
原因分析
该段代码定义了一个动态存储线程(thread)的vector容器,并向其中压入了10个线程任务,每个线程任务都是执行do_some_work
。待线程全部启动后通过基于范围的for循环遍历该vector容器,将所有线程的运行结果通过join进行回收。
而问题正是出在线程上,我们成功启动了10个线程,但是却忽略了一点:线程的执行顺序并不确定,取决于操作系统的调度策略。线程之间是并发运行的,而并非依序执行。
当每次运行程序时,操作系统都会根据自身的调度策略,调整线程的执行顺序,因而会造成每次执行程序时,看到的输出结果都不相同。
举个可能的例子:
线程A拿到了CPU所分给的执行时间切片,当线程A执行到打印"Do some work"时,还没来得及打印换行符,便被操作系统强制暂停。
这时,论到线程B执行了,线程B成功执行了整套流程。一段时间后,A又重新拿到时间切片开始执行,并将剩下的一个换行符打印。
因此,当最后调用join回收资源的时候,线程AB的结果就如下所示:
Do some workDo some work //线程A的换行符被打印到别处去了
代码修正
解决方法:确保每次只有一个线程能够访问并执行cout,因此需要用到互斥锁。
当该线程获得锁后,其它线程由于无法获得锁,因此就算分得时间切片也无法执行。这样就确保了每次只有得到锁的那个线程能够被完整执行。
修正代码如下:
std::mutex mtx;void do_some_work() {std::lock_guard<std::mutex> lock(mtx);std::cout << "Do some work" << std::endl;
}int main(int argc, const char* argv[]) {std::vector<std::thread> threads;for (int i = 0; i < 10; i++) {threads.emplace_back(std::thread(do_some_work));}for (auto& th : threads) {th.join();}return 0;
}
输出结果:
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
🌹🌹🌹