cout同步/线程安全吗?

时间:2011-06-16 15:19:04

标签: c++ gcc c++11

一般来说,我假设流不同步,由用户做适当的锁定。但是,像cout这样的事情会在标准库中得到特殊处理吗?

也就是说,如果多个线程写入cout,它们是否会破坏cout对象?据我所知,即使同步,你仍然会得到随机交错的输出,但保证交错。也就是说,从多个线程使用cout是否安全?

此供应商是否依赖? gcc做什么用?


重要:如果您说“是”,请为您的答案提供某种参考,因为我需要某种证明。

我关注的还不是基础系统调用,这些都很好,但是流在顶部添加了一层缓冲。

4 个答案:

答案 0 :(得分:100)

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.overview]中说明以下内容:

  

同步访问同步(§27.5.3.4)标准iostream对象的格式化和未格式化输入(§27.7.2.1)和输出(§27.7.3.1)函数或多个线程的标准C流不得结果   在数据竞赛中(§1.10)。 [注意:用户仍必须同步这些对象和流的并发使用   多个线程,如果他们希望避免交错字符。 - 结束说明]

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

答案 1 :(得分:15)

这是一个很好的问题。

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

C ++ 0x怎么样?见Martinho's answer(我承认让我感到惊讶)。

C ++ 0x之前的特定实现怎么样?好吧,例如,以下是来自GCC 4.5.2(“streambuf”标题)的basic_streambuf<...>:sputc的源代码:

 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也没有。{1}}。这绝对是cout使用的streambuf的类型。

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

因此,使用此实现,显然两个线程的输出可能会相互损坏(只是交错)。

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

换句话说,即使它在当前环境中正常工作,在更新任何运行时,编译器或CPU时也可能会中断。

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

作为一种弱选择,您可以将cout设置为无缓冲。很可能(尽管不能保证)会跳过与缓冲区相关的所有逻辑并直接调用write。虽然这可能会非常缓慢。

答案 2 :(得分:7)

  

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

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

还有:Are standard output streams in C++ thread-safe (cout, cerr, clog)?

<强>更新

请查看@Martinho Fernandes的回答,了解新标准C ++ 11对此的了解。

答案 3 :(得分:6)

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

GCC并未就线程安全和I / O做出很多承诺。但它所做的承诺的文件在这里:

关键的东西可能是:

  

__basic_file类型只是一个   收集小包装   C stdio层(再次,请参阅链接   根据结构)。我们没有锁定   我们自己,但只是通过   调用fopen,fwrite等等。

     

因此,对于3.0,“问题是”   多线程安全的I / O“必须是   回答说,“是你的平台的C   I / O的库线程安全吗?“有些是   默认情况下,有些不是;许多提议   C的多个实现   具有不同权衡的图书馆   线程安全和效率。你,   程序员,总是需要   小心多线程。

     

(例如,POSIX标准   需要C stdio FILE *操作   是原子的。 POSIX符合C   库(例如,在Solaris和   GNU / Linux)有一个内部互斥体   序列化FILE *上的操作。   但是,你仍然不需要这样做   调用fclose(fs)等愚蠢的事情   在一个线程中,然后访问   fs在另一个。)

     

所以,如果您的平台的C库是   线程安全,然后你的fstream I / O.   操作将是线程安全的   最低级别。对于更高级别   操作,如操纵   流中包含的数据   格式化类(例如,设置   std :: ofstream中的回调,   你需要保护这样的访问   任何其他关键的共享资源。

我不知道所提到的3.0时间框架是否有任何改变。

MSVC的iostreams线程安全文档可在此处找到:http://msdn.microsoft.com/en-us/library/c9ceah3b.aspx

  

单个对象是线程安全的   从多个线程中读取。对于   例如,给定一个对象A,它是安全的   从线程1和从中读取A.   线程2同时。

     

如果要写入单个对象   通过一个线程,然后所有读取和   写入该对象或相同或   其他线程必须受到保护。对于   给定一个对象A,如果是线程   1写入A,然后线程2必须   被禁止阅读或   写给A。

     

读取和写入一个是安全的   一个类型的实例,即使是另一个   线程正在读取或写入   不同类型的实例。   例如,给定对象A和B.   相同的类型,如果A是安全的   用线程1和B写的是   正在线程2中阅读。

     

...

     

iostream课程

     

iostream类遵循相同的规则   规则作为其他类,有一个   例外。写一个是安全的   来自多个线程的对象。对于   例如,线程1可以写入cout at   与线程2同时。但是,   这可能导致输出   两个线程混合在一起。

     

注意:从流缓冲区读取是   不被认为是读操作。   它应该被认为是一种写作   操作,因为这改变了   班级的状态。

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