Bootstrap

C++20 Sync Stream: 解决多线程输出混乱问题

C++中的 sync stream 是指在多线程环境下同步输出流的概念. 在 C++20 标准中实现了 std::osyncstream 相关功能, 用于解决多线程输出流竞争和混乱问题.


1. 背景: 多线程输出的混乱

在多线程环境下, 多个线程同时向 std::cout 或其他输出流写入数据时, 常会导致输出内容交错和混乱.

举一个简单的示例:

#include <iostream>
#include <thread>
#include <vector>

int main() {
  constexpr int num_thread = 10;
  std::vector<std::thread> threads;

  // 开启线程
  for (int i = 0; i < num_thread; i++) {
    threads.emplace_back(
        [](int id) { std::cout << "Thread " << id << " is doing job\n"; }, i);
  }

  // 等待线程结束
  for (auto& t : threads) {
    t.join();
  }
  return 0;
}

我们通常会希望得到如下输出(线程顺序可能不同):

Thread 0 is doing job
Thread 2 is doing job
Thread 3 is doing job
Thread 1 is doing job
Thread 4 is doing job
Thread 5 is doing job
Thread 6 is doing job
Thread 7 is doing job
Thread 8 is doing job
Thread 9 is doing job

但实际上, 输出常常是混乱的:

Thread Thread 0 is doing job
Thread 2 is doing job
1 is doing job
Thread 3 is doing job
Thread 4 is doing job
Thread 5 is doing job
Thread 6 is doing job
Thread 7 is doing job
Thread 9 is doing job
Thread 8 is doing job

这种混乱会使输出难以使用.


2. 原因解析

2.1 全局资源共享和原子操作

  1. std::cout 是全局标准输出流, 多个线程无序访问时会导致内容交错.

  2. 虽然 std::cout 保证单次操作的原子性, 例如:

    // Thread A
    std::cout << "Thread A\n";
    // Thread B
    std::cout << "Thread B\n";
    

    可能输出为:

    thread A
    thread B
    

    或者:

    thread B
    thread A
    

    绝不会出现内容叠加在一起的情况, 比如这种:

    thread thread A
    B
    

    但如果多个操作组合在一起, 例如:

    std::cout << "Thread " << id << " is doing job\n";
    

    输出可能会交错, 导致不可读的结果.

2.2 输出混乱的影响

  • 难以调试: 多线程调试信息混乱, 增加了定位问题的难度.
  • 日志不可用: 实时系统中的日志输出可能会出现乱码, 导致信息丢失.

3. std::osyncstream 介绍

C++20 推出了同步输出流 std::osyncstream, 用于解决上述问题.

3.1 基础原理

std::osyncstream 通过内置缓冲区, 在操作完成后一次性写入目标流, 从而保证输出内容的完整性和顺序. 每个线程有独立的缓冲区, 避免了线程间竞争.

3.2 用法示例

#include <iostream>
#include <syncstream>  // 引入对应头文件
#include <thread>
#include <vector>

int main() {
  constexpr int num_thread = 10;
  std::vector<std::thread> threads;

  // 开启线程
  for (int i = 0; i < num_thread; i++) {
    threads.emplace_back(
        [](int id) {
          std::osyncstream(std::cout) << "Thread " << id << " is doing job\n";
        },
        i);
  }

  // 等待线程结束
  for (auto& t : threads) {
    t.join();
  }
  return 0;
}

输出可以保证不会交错:

Thread 0 is doing job
Thread 3 is doing job
Thread 4 is doing job
Thread 7 is doing job
Thread 5 is doing job
Thread 1 is doing job
Thread 2 is doing job
Thread 9 is doing job
Thread 8 is doing job
Thread 6 is doing job

4. 优势和注意事项

4.1 优势

  • 线程安全: 每个线程独立缓冲, 避免竞争.
  • 完整性: 确保每次输出操作的内容完整无误.
  • 易用性: 不需要显式加锁, 简化了代码.
  • 兼容性: 支持主流 C++20 编译器(GCC 10+, Clang 12+, MSVC 19.28+).

4.2 注意事项

  • 性能开销: 由于使用了缓冲区和锁, 性能可能略有下降.
  • C++20 要求: 需要支持 C++20 标准的环境.

5. 总结

std::osyncstream 是 C++20 中一个重要的新特性, 通过引入线程安全的同步输出流, 解决了多线程环境下输出混乱的问题. 其实现简单高效, 适合在多线程日志, 调试输出等场景中广泛应用. 在实际项目中, 选择合适的工具和技术, 可以显著提升系统的稳定性和可维护性.

源码链接

;