Cout 同步/线程安全吗?

一般情况下,我假设流不是同步的,应该由用户来进行适当的锁定。然而,像 cout这样的东西在标准库中是否得到了特殊的处理?

也就是说,如果多个线程正在写入 cout,它们会破坏 cout对象吗?我明白,即使同步,您仍然会得到随机交错输出,但这是交错保证。也就是说,从多个线程使用 cout是否安全?

这个供应商是依赖的吗? gcc 是做什么的?


重点 : 如果你回答“是”,请提供一些参考,因为我需要一些证明。

我关心的也不是底层的系统调用,这些都很好,但是流在上面添加了一个缓冲层。

43394 次浏览

C + + 标准没有指定写入流是否是线程安全的,但通常不是。

Www.techrepublic.com/article/use-stl-streams-for-easy-c-plus-plus-thread-safe-logging

还有 C + + 中的标准输出流是否线程安全(cout、 cerr、 clog) ?

更新

请看@Martinho Fernandes 的回答,了解一下新标准 C + + 11是怎么说的。

C + + 03标准对此没有任何说明。当您不能保证某些内容的线程安全时,应该将其视为不是线程安全的。

这里特别感兴趣的是 cout被缓冲的事实。即使对 write的调用(或在特定实现中实现这种效果的任何东西)保证是相互排斥的,缓冲区也可能由不同的线程共享。这将很快导致流的内部状态的损坏。

即使对缓冲区的访问保证是线程安全的,您认为在这段代码中会发生什么?

// in one thread
cout << "The operation took " << result << " seconds.";


// in another thread
cout << "Hello world! Hello " << name << "!";

您可能希望这里的每一行都有互斥锁,但是实现如何保证这一点呢?

在 C + + 11中,我们有一些保证,FDIS 在27.4.1[ iostream.objects.view]中说:

通过多个线程并发访问同步的(27.5.3.4)标准 iostream 对象的格式化和非格式化输入(27.7.2.1)和输出(27.7.3.1)函数或标准 C 流不应产生 (1.10)[注意: 用户仍然必须通过以下方式同步这些对象和流的并发使用 如果它们希望避免交叉字符,可以使用多个线程

因此,您不会得到损坏的流,但是如果您不希望输出成为垃圾,仍然需要手动同步它们。

这是个好问题。

首先,C + + 98/C + + 03没有“线程”的概念,所以在那个世界里,这个问题是没有意义的。

那么 C + + 0x 呢? 请参见 Martinho 的回答(我承认这让我很吃惊)。

How about specific implementations pre-C++0x? Well, for example, here is the source code for basic_streambuf<...>:sputc from GCC 4.5.2 ("streambuf" header):

 int_type
sputc(char_type __c)
{
int_type __ret;
if (__builtin_expect(this->pptr() < this->epptr(), true)) {
*this->pptr() = __c;
this->pbump(1);
__ret = traits_type::to_int_type(__c);
}
else
__ret = this->overflow(traits_type::to_int_type(__c));
return __ret;
}

显然,这不执行锁定。xsputn也不知道。这绝对是 Cout 用的那种流线型防护罩。

据我所知,libstdc + + 不对任何流操作执行锁定。我也不指望会有,因为那会很慢。

So with this implementation, obviously it is possible for two threads' output to corrupt each other (没有 just interleave).

这段代码会破坏数据结构本身吗?答案取决于这些函数之间可能的交互; 例如,如果一个线程试图刷新缓冲区,而另一个线程试图调用 xsputn或其他什么,会发生什么情况。这可能取决于您的编译器和 CPU 决定如何重新排序内存负载和存储; 这需要仔细分析才能确定。这还取决于如果两个线程试图同时修改相同的位置,您的 CPU 将执行什么操作。

In other words, even if it happens to work fine in your current environment, it might break when you update any of your runtime, compiler, or CPU.

执行摘要: “我不会”。构建一个能够正确锁定的日志类,或者转移到 C + + 0x。

作为一个弱选项,您可以将 cout 设置为 unbuffer。很可能(虽然不能保证)会跳过所有与缓冲区相关的逻辑,直接调用 write。虽然这可能会慢得让人望而却步。

正如其他答案所提到的,这绝对是特定于供应商的,因为 C + + 标准没有提到线程(这是 C + + 0x 中的变化)。

GCC 在线程安全和 I/O 方面没有做出很多承诺。但是它所承诺的文件在这里:

关键可能是:

_ _ basic _ file 类型只是一个 周围的小包装纸的集合 C 工作台层(再次查看链接) 我们没有锁定 我们自己,但只是通过 调用 fopen、 fwrite 等。

所以,对于3.0来说,问题是 I/O 的多线程安全”必须是 “你的平台是 C 吗?” 库线程安全的 I/O?” 默认情况下,有些人不是; 许多人提出 C 的多个实现 有不同的权衡的图书馆 螺纹的安全性和效率 程序员,总是要求 处理多个线程。

(例如,POSIX 标准 需要 C 的 stdio 文件 * 操作 是原子的符合 POSIX 的 C 库(例如,在 Solaris 和 GNU/Linux)有一个内部互斥对象 序列化对文件 * 的操作。 但是,你还是需要不做吗 像调用 fclose (f)这样愚蠢的事情 在一个线程中,后面跟着一个访问 在另一个。)

因此,如果平台的 C 库是 线程安全,然后是您的上游 I/O 操作将是线程安全的 最低级别。为更高级别 操作,例如操作 data contained in the stream 格式化类(例如,设置 在 std: : ofstream 中回调) , 你需要保护这些通道,比如 任何其他重要的共享资源。

我不知道在3.0时间框架之后是否有什么变化。

MSVC 针对 iostreams的线程安全文档可以在这里找到: http://msdn.microsoft.com/en-us/library/c9ceah3b.aspx:

单个对象是线程安全的 从多个线程读取 例如,给定一个对象 A,它是安全的 从线程1读取 A thread 2 simultaneously.

如果正在写入单个对象,则为 通过一个线程,然后所有的阅读和 写入到该对象的同一个或 必须保护其他线程 例如,给定一个对象 A,如果线程 1写到 A,那么线程2必须 被禁止阅读或 写信给 A。

对其中一个进行读写操作是安全的 类型的实例,即使另一个 线程正在读取或写入 同一类型的不同实例。 例如,给定对象 A 和 B 的 如果 A 是同一类型,则它是安全的 being written in thread 1 and B is 正在线程2中读取。

...

Iostream 类别

Iostream 类遵循同样的规则 规则作为其他类,一个 异常。写入 来自多个线程的 例如,线程1可以在 与线程2相同的时间。但是, 这可能导致来自 两根线混在一起。

注意: 从流缓冲区读取是 不被认为是读操作。 它应该被视为一个写作 操作,因为这会改变 班级状况。

请注意,该信息针对的是 MSVC 的最新版本(目前针对的是 VS 2010/MSVC 10/cl.exe16.x)。您可以使用页面上的下拉控件选择旧版本 MSVC 的信息(旧版本的信息不同)。