cout同步/线程安全吗?

edA*_*a-y 108 c++ gcc c++11

一般来说,我假设流不同步,由用户做适当的锁定.但是,做cout标准库中的特殊处理吗?

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

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


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

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

R. *_*des 102

C++ 03标准没有说明任何内容.当你不能保证某些东西的线程安全时,你应该把它视为不是线程安全的.

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

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

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

// in another thread
cout << "Hello world! Hello " << name << "!";
Run Code Online (Sandbox Code Playgroud)

您可能希望此处的每一行都互相排斥.但是如何实现保证呢?

在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).[注意:如果用户希望避免交错字符,则仍必须通过多个线程同步这些对象和流的并发使用. - 结束说明]

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

  • @ildjarn - 不,@ edA-qa mort-ora-y是正确的.只要`cout.sync_with_stdio()`为真,使用`cout`从多个线程输出字符而没有额外的同步是明确定义的,但仅限于单个字节的级别.因此,`cout <<"ab";`和`cout <<"cd"`在不同的线程中执行可能会输出`acdb`,但可能不会导致未定义的行为. (10认同)
  • @JohannesD:我们在那里达成一致 - 它与底层的C API同步.我的观点是它没有以有用的方式"同步",即如果他们不想要垃圾数据,仍然需要手动同步. (4认同)
  • 从技术上讲,对于C ++ 98 / C ++ 03来说是正确的,但是我想每个人都知道这一点。但这不能回答两个有趣的问题:C ++ 0x呢?典型的实现实际上是做什么的? (2认同)
  • @ildjarn,我对垃圾数据很好,我理解这一点.我只是对数据竞争条件感兴趣,现在似乎很清楚. (2认同)

Nem*_*emo 15

这是一个很好的问题.

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

那么C++ 0x呢?看马蒂纽的答案(我承认让我感到惊讶).

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

 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;
 }
Run Code Online (Sandbox Code Playgroud)

显然,这不执行锁定.也没有xsputn.这绝对是cout使用的streambuf的类型.

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

所以这个实现,显然是可能的两个线程输出腐败对方(只是交错).

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

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

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

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


pho*_*xis 7

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

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

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

UPDATE

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

  • 我想因为C++ 11现在是标准,所以这个答案现在实际上是错误的. (3认同)

Mic*_*urr 6

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

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

关键的东西可能是:

__basic_file类型只是C stdio层周围的小包装器的集合(再次,请参阅Structure下的链接).我们没有锁定自己,只是简单地通过调用fopen,fwrite等等.

因此,对于3.0,"多线程安全I/O"的问题必须回答,"您的平台的C库线程是否适用于I/O?" 有些是默认的,有些则不是; 许多提供C库的多个实现,具有不同的线程安全性和效率.作为程序员,您始终需要注意多个线程.

(作为一个例子,POSIX标准要求C STDIO FILE*操作都是原子.符合POSIX标准C库(例如,在Solaris和GNU/Linux)有一个内部互斥锁序列化对FILE*S的操作.但是,你仍然需要不做愚蠢的事情,比如在一个线程中调用fclose(fs),然后在另一个线程中调用fs.)

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

我不知道在提到的3.0时间范围内是否有任何改变.

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

单个对象对于从多个线程读取是线程安全的.例如,给定对象A,可以安全地从线程1和线程2同时读取A.

如果一个线程正在写入单个对象,则必须保护对相同或其他线程上该对象的所有读写操作.例如,给定对象A,如果线程1写入A,则必须阻止线程2读取或写入A.

即使另一个线程正在读取或写入同一类型的不同实例,也可以安全地读取和写入一个类型的实例.例如,给定相同类型的对象A和B,如果在线程1中写入A并且在线程2中读取B,则是安全的.

...

iostream类

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

注意:从流缓冲区读取不被视为读取操作.它应该被视为写操作,因为这会改变类的状态.

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