我知道严格来说,在C++ 98中没有什么是线程安全的,因为在标准中,C++ 11之前没有线程。但是,实际上,早在C++ 11之前,线程就已在C++中使用。
因此,可以说两个pthreads
同时调用此代码:
void printSomething(){
std::cout << "something\n";
}
是什么导致两个输出交错?还是我会在实践中总是得到两行包含“某物”的行?
我问这个问题是因为this question让我感到奇怪,并且我发现this answer指出:
但是this answer给出了一个类似的示例
std::cout << "aaaaaaaaaa" << "bbbbbbbbbb";
并得出结论,在C++ 98中,执行此操作的两个线程可能具有交错的输出,但是我找不到关于仅调用一次
operator<<
的两个线程的任何信息。 最佳答案
正如评论中指出的那样,C++ 98在标准中未提供任何线程概念,因此您不能通过引用标准来回答此问题。
但是,任何旨在与线程一起使用的合理C++ 98实现(即,其中大多数都在某些嵌入式市场之外)几乎可以肯定会为“普通”用例至少提供或多或少的安全std::cout
。毕竟,使用该流进行输出是非常普遍的,并且在线程化程序中的线程之间使用它也将是非常普遍的。
不幸的是,这可以说得差不多。请特别注意,我什至没有以特定方式定义“安全”。至少您可能希望它不会崩溃,破坏程序或“凭空产生”输出。除此之外,还取决于。
它依赖什么
下一步是检查实现本身。希望你有source1!
通常,您会发现该实现具有一些与线程无关的代码(例如,复制到堆栈本地缓冲区中),并在某些时候进行了锁定,然后在std::cout
对象内部操作了共享状态。何时何地锁定很重要。
单弦
例如,人们希望两个线程上的单个项目的输出,例如线程1上的std::cout << "AAAA"
和线程2上的std::cout << "BBBB"
,至少会导致AAAABBBB
或BBBBAAAA
的“非交错”输出,但不会导致BBAAAABB
或类似的东西。对于以某些方式进行缓冲的实现而言,情况可能并非如此!例如,该实现可以锁定,然后尽可能多地复制到内部缓冲区中,如果已满,则输出,然后解锁并再次进行。
请记住,即使std::cout
对象是完全锁定的(即,整个operator<<(const char *)
本质上是在锁下运行的),当它与正在写入stdout
的其他代码同时运行但通过std::cout
以外的其他机制-例如printf()
。
在这种情况下,C/C++运行时通常不会共享任何锁定,并且您将依赖于操作系统提供的底层write()
-type调用的锁定。尽管此调用是完全锁定的并且是线程安全的,但它可能不会一次写入整个字符串(例如,当中间缓冲区填满时,例如writing to a pipe,这是很常见的)。
多个运营商
流接口(interface)的主要问题是多个运算符。诸如std::cout << "AAA" << "BBB" ...
之类的东西几乎总是变成对共享std::cout
对象的多次调用。无需外部锁定,就可以自由地将它们与其他线程进行交错。至关重要的是,这几乎意味着iomanip
中的流操纵器无法安全使用。像std::cout << std::hex << 123 << std::dec
这样的东西会全局地修改流,而其他一些不希望hex
输出的线程则可能在错误的时间运行并获得它。此外,流格式化状态和标志可以在操作过程中更改的事实可能会产生一些奇怪,奇妙或彻头彻尾的可怕结果。
1当然,该源可用于开源运行时,例如libstc++(由gcc使用)和libc++(在大多数非Linux平台上默认由LLVM使用)。 Microsoft似乎也在为其C运行时提供源代码。