在 C++98 中将单个字符串写入 cout "thread safe"

Is writing a single string to cout "thread safe" in C++98

我知道严格来说在 C++98 中没有什么是线程安全的,因为在 C++11 之前的标准中没有线程。然而,实际上线程早在 C++11 之前就已经在 C++ 中使用了。

所以假设两个 pthreads 同时调用这个:

void printSomething(){
    std::cout << "something\n";
}

什么会导致两个输出交错?或者我会在实践中总是得到两行包含 "something"?

我问这个是因为 made me wonder and I found this answer 说明:

... in C++11, std::cout is thread-safe.

但是 this answer 给出了一个例子

std::cout << "aaaaaaaaaa" << "bbbbbbbbbb";

并得出结论,在 C++98 中,执行此操作的两个线程可能会交错输出,但我找不到任何关于两个线程只调用一次 operator<< 的信息。

正如评论中指出的那样,C++98 在标准中没有提供任何线程的概念,因此您无法通过参考标准来回答这个问题。

然而,任何合理的 C++98 实现 旨在与线程一起使用(即,它们中的大多数在某些嵌入式市场之外)几乎肯定会至少提供更多 - or-less safe std::cout for "plain" 用例。毕竟,使用那个流来输出是很常见的,在线程程序中跨线程使用它也会很常见。

不幸的是,您只能说这么多。请特别注意,我什至没有以特定方式定义 "safe"。至少您可能期望它不会崩溃、损坏您的程序或产生输出 "out of thin air"。除此之外,这取决于。

它取决于什么

下一步是检查实现本身。希望你有来源1

通常你会发现实现有一些线程不可知的代码(例如,复制到堆栈本地缓冲区)并且在某些时候锁定然后操作 std::cout 对象内的共享状态。何时何地锁定很重要。

单字符串

例如,人们希望单个项目在两个线程上的输出,例如线程 1 上的 std::cout << "AAAA" 和线程 2 上的 std::cout << "BBBB" 至少会导致 "not-interleaved" AAAABBBBBBBBAAAA 的输出,但永远不会 BBAAAABB 或类似的输出。对于以某些方式缓冲的实现,这 可能不正确 !例如,实现可能会锁定,然后尽可能多地复制到内部缓冲区中,如果已满,则输出,然后解锁并再次进行。

请记住,即使 std::cout 对象也被完全锁定(即,基本上整个 operator<<(const char *) 都处于锁定状态 运行),您可能会看到甚至单个字符串的交错当它 运行 与其他正在写入 stdout 但通过 std::cout 以外的机制同时写入的代码时输出 - 例如 printf().

在这种情况下,C/C++ 运行次通常不会共享任何锁,您将依赖于底层 write() 类型调用的锁定由 OS 提供。尽管此调用是完全锁定且线程安全的,但它可能不会一次性写入整个字符串(这很常见,例如,当 writing to a pipe 等中间缓冲区填满时)。

多个运算符

流接口的主要问题是多个运算符。 std::cout << "AAA" << "BBB" ... 之类的东西几乎总是变成对共享 std::cout 对象的多次调用。在您没有外部锁定的情况下,这些线程可以以任何方式与其他线程自由交错。至关重要的是,这几乎意味着 iomanip 中的流操纵器无法安全使用。 std::cout << std::hex << 123 << std::dec 之类的东西会全局修改流,而其他一些不需要 hex 输出的线程可能会在错误的时间 运行 并且无论如何都会得到它。此外,流格式化状态和标志可以在操作的 中间 发生变化这一事实可能会产生一些奇怪的、美妙的或彻头彻尾的可怕结果。


1 当然,源代码可用于开源 运行 次,例如 libstc++(由 gcc 使用)和 libc++(LLVM 在大多数非 Linux 平台)。 Microsoft 似乎也在为他们的 C 运行time 提供源代码。