cout是否同步/线程安全?


112

通常,我假定流不同步,这取决于用户进行适当的锁定。但是,诸如此类的事情cout在标准库中得到特殊对待吗?

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

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


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

我也不在乎底层的系统调用,这些很好,但是流在顶部增加了一层缓冲。


2
这取决于供应商。C ++(在C ++ 0x之前)没有多线程的概念。

2
那么c ++ 0x呢?它定义了一个内存模型以及什么是线程,那么也许这些东西会滴落在输出中?
rubenvb 2011年

2
是否有任何供应商使其具有线程安全性?
edA-qa mort-ora-y

有人链接到最新的C ++ 2011建议标准吗?
edA-qa mort-ora-y

4
从某种意义上说,这是一次完整的输出写入后printf发光的地方stdout。当使用std::cout表达链的每个链接时,将分别输出到stdout; 在它们之间可能会有其他线程写入,stdout最终输出的顺序被弄乱了。
legends2k

Answers:


106

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)。[注意:如果用户希望避免插入字符,则仍必须同步多个线程对这些对象和流的并发使用。—尾注]

因此,您不会受到损坏的流,但是如果您不希望输出成为垃圾,则仍然需要手动对其进行同步。


2
从技术上讲,C ++ 98 / C ++ 03是正确的,但我想每个人都知道。但这不能回答两个有趣的问题:C ++ 0x呢?典型的实现实际上是什么的?
Nemo

1
@ edA-qa mort-ora-y:不,你错了。C ++ 11明确定义了标准流对象可以同步并保留定义良好的行为,而不是默认情况下它们是。
ildjarn 2011年

12
@ildjarn-不,@ edA-qa mort-ora-y是正确的。只要cout.sync_with_stdio()是正确的,cout就可以很好地定义用于从多个线程输出字符而无需进行额外同步的功能,但是只能在单个字节的级别上使用。因此,cout << "ab";cout << "cd"在不同的线程执行时可以输出acdb,例如,但可能不会导致未定义的行为。
JohannesD

4
@JohannesD:我们在这里达成协议-它与基础C API同步。我的观点是,它不是以一种有用的方式“同步”的,即如果他们不需要垃圾数据,则仍然需要手动同步。
ildjarn 2011年

2
@ildjarn,我对垃圾数据没问题,这一点我了解。我只是对数据竞争状况感兴趣,现在看来这很清楚。
edA-qa mort-ora-y

16

这是一个很好的问题。

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

C ++ 0x呢?请参阅马蒂纽的答案(我承认让我感到惊讶)。

C ++ 0x之前的具体实现如何?好吧,例如,这是basic_streambuf<...>:sputcGCC 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;
 }

显然,这不会执行锁定。而且也不是xsputn。这绝对是cout使用的streambuf的类型。

据我所知,libstdc ++不会对任何流操作进行锁定。我不会指望任何东西,因为那样会很慢。

因此,使用此实现,很明显两个线程的输出可能会相互破坏(不仅仅是交错)。

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

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

内容提要:“我不会”。构建一个执行适当锁定的日志记录类,或移至C ++ 0x。

作为一种较弱的选择,您可以将cout设置为unbuffered。可能(尽管不能保证)会跳过与缓冲区有关的所有逻辑并write直接调用。尽管那可能会太慢了。


1
好的答案,但是请看一下Martinho的回答,它表明C ++ 11确实为定义了同步cout
edA-qa mort-ora-y


6

就像其他答案提到的那样,这肯定是特定于供应商的,因为C ++标准没有提及线程(C ++ 0x中的更改)。

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

关键的东西可能是:

__basic_file类型只是围绕C stdio层的小包装的集合(同样,请参见“结构”下的链接)。我们不会锁定自己,而只是传递给对fopen,fwrite等的调用。

因此,对于3.0,必须回答“您的C语言库线程对I / O安全吗?”的问题“对于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提到的时间框架内是否有任何变化。

iostreams可以在以下位置找到MSVC的线程安全文档:http : //msdn.microsoft.com/zh-cn/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较早版本的信息(该信息对于较早版本是不同的)。


1
“我不知道上述3.0时间表是否有任何变化。” 确实做到了。在过去的几年中,g ++流实现已执行了自己的缓冲。
Nemo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.