前言

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

举例

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

#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行内容,但是运行结果可能是这样的

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是什么样的:

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
  4. ……

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

std::stringstream ss;
ss << "msg1"
   << " msg2"
   << " msg3"
   << " thread_id = " << std::this_thread::get_id() << std::endl;
std::cout << ss.str();

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