在 C++98 中写入单个字符串以"线程安全"吗



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

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

void printSomething(){
std::cout << "somethingn";
}

是什么原因导致两个输出交错?或者,在实践中,我会得到总是包含"某物"的两行吗?

我问这个问题是因为这个问题让我感到好奇,我发现这个答案是:

。。。在C++11中,std::cout是线程安全的。

但这个答案提供了的一个例子

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

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

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

然而,任何旨在与线程一起使用的合理的C++98实现(即,一些嵌入式市场之外的大多数线程)几乎肯定会为"普通"用例提供至少或多或少的安全std::cout。毕竟,使用该流进行输出是非常常见的,在线程化程序中跨线程使用它也是非常常见的。

不幸的是,这已经是你所能说的最具体的了。特别要注意的是,我甚至没有以特定的方式定义"安全"。至少你可能预计它不会崩溃、损坏你的程序或"凭空"产生输出。除此之外,这取决于情况。

它取决于什么

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

通常,您会发现该实现具有一些与线程无关的代码(例如.复制到堆栈本地缓冲区中),并且在某些时候锁定并操纵std::cout对象内部的共享状态。何时何地锁定很重要。

单个字符串

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

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

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

多个运算符

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


1当然,该源代码可用于开源运行时,如libstc++(由gcc使用)和libc++(在大多数非Linux平台上由LLVM默认使用)。微软似乎也在为他们的C运行时提供源代码。

最新更新