You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++20线程安全与代码设计选择的技术疑问

《C++20完全指南》示例代码技术疑问解答

示例代码

#include <iostream>
#include <array>
#include <thread>
#include <latch>
using namespace std::literals;  // for duration literals

void loopOver(char c) {
  // loop over printing the char c:
  for (int j = 0; j < c/2; ++j) {
    std::cout.put(c).flush();
    std::this_thread::sleep_for(100ms);
  }
}

int main()
{
  std::array tags{'.', '?', '8', '+', '-'};  // tags we have to perform a task for

  // initialize latch to react when all tasks are done:
  std::latch allDone{tags.size()};   // initialize countdown with number of tasks

  // start two threads dealing with every second tag:
  std::jthread t1{[tags, &allDone] {
                    for (unsigned i = 0; i < tags.size(); i += 2) {  // even indexes
                      loopOver(tags[i]);
                      // signal that the task is done:
                      allDone.count_down();  // atomically decrement counter of latch
                    }
                    //...
                  }};
  std::jthread t2{[tags, &allDone] {
                    for (unsigned i = 1; i < tags.size(); i += 2) {  // odd indexes
                      loopOver(tags[i]);
                      // signal that the task is done:
                      allDone.count_down();  // atomically decrement counter of latch
                    }
                    //...
                  }};
  //...
  // wait until all tasks are done:
  std::cout << "\nwaiting until all tasks are done\n";
  allDone.wait();                      // wait until counter of latch is zero
  std::cout << "\nall tasks done\n";   // note: threads might still run
  //...
}

问题解答

1. 按值捕获tags而非引用的优势

  • 彻底规避悬空引用风险:tags是main函数的局部变量,尽管当前代码中std::jthread会在main结束前自动join,但如果后续代码调整为分离线程,引用捕获会导致线程访问已销毁的对象。按值捕获会复制tags的独立副本,副本生命周期与线程闭包绑定,从根源消除悬空引用问题。
  • 消除同步与内存可见性开销:线程仅读取tags,按值捕获后每个线程拥有专属副本,无需考虑跨线程缓存一致性或内存可见性问题,访问效率更高,也不需要额外同步机制。
  • 提升代码鲁棒性:即使后续main中对tags进行修改(当前代码无此操作),线程持有的副本不受影响,保证线程始终看到创建时的tags状态,行为更可预测。

2. std::cout.put().flush()组合的线程安全性

C++20标准明确:

  • std::cout.put()作为单个字符输出操作是线程安全的——不同线程调用put()时,每个字符的输出是原子的,不会出现字符拆分、乱码等未定义行为。
  • flush()操作本身也是线程安全的单个操作,不会与其他流操作产生数据竞争。
  • 但put()和flush()的组合并非原子操作:线程A执行完put()后,线程B可能先于A的flush()执行put(),导致两个字符同时存入缓冲区,随后A的flush()会将两者一起输出。这种情况会导致输出顺序混乱,但不属于未定义行为,仅为顺序不可控。

3. 内置线程安全保障的说明

C++标准对标准IO流的线程安全有明确内置保障:

  • 同一标准流对象的单个字符输入/输出操作是原子的,不存在数据竞争导致的未定义行为,因此std::cout.put()无需额外显式同步。
  • flush()作为单个流操作,同样具备内置线程安全,不会与其他流操作冲突。
  • 需注意:这种保障仅针对单个字符操作,多字符输出(如std::cout << "string")不保证原子性,可能被其他线程输出打断;同时,多个操作的组合(如put()+flush())也不保证原子性,仅能保证每个单独操作的安全性。

内容的提问来源于stack exchange,提问作者sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 00:13:19