避免cout线程不安全的一个做法

前言

std::cout重载了<<运算符,这使得写一些很短的代码时很方便。但是如果在多线程的条件下,cout并不是线程安全的。

举例

举个例子,我们创建5个线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <thread>

void Test() {
std::cout << "msg1"
<< " msg2"
<< " msg3"
<< " thread_id = " << std::this_thread::get_id() << std::endl;
}

int main() {
std::thread threads[5];
for (int i = 0; i < 5; i++) {
threads[i] = std::thread(Test);
}

for (int i = 0; i < 5; i++) {
threads[i].join();
}
}

实际上,看样子好像控制台应该输出5行内容,但是运行结果可能是这样的

1
2
3
4
5
msg1 msg2 msg3 thread_id = msg1 msg2 msg3 thread_id = 139926598575808139926606968512

msg1 msg2 msg3 thread_id = 139926590183104
msg1 msg2 msg3 thread_id = 139926455965376
msg1 msg2 msg3 thread_id = 139926581790400

这是因为cout在使用时可能会存在线程之间的打印信息乱串的问题,看一下编译器眼中我们这段程序中的cout是什么样的:

1
std::operator<<(std::operator<<(std::operator<<(std::operator<<(std::operator<<(std::cout, "msg1"), " msg2"), " msg3"), " thread_id = "), std::this_thread::get_id()).operator<<(std::endl);

可以看到,这不是通过单个 std::operator<< 调用完成的,也就是说这个操作并不是原子的

解决方法

  1. 使用std::format(C++20)
  2. 使用第三方库,如follyfmtlib
  3. 使用stringstream

这里使用stringstream做个演示。将Test函数修改如下:

1
2
3
4
5
6
std::stringstream ss;
ss << "msg1"
<< " msg2"
<< " msg3"
<< " thread_id = " << std::this_thread::get_id() << std::endl;
std::cout << ss.str();

这样,控制台的输出就不会乱串了。