通常,我假定流不同步,这取决于用户进行适当的锁定。但是,像cout这样的东西在标准库中是否得到特殊对待?

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

此供应商依赖吗?重要提示:gcc的作用是什么?重要提示:如果您回答“是”,请提供某种参考,因为我需要某种证明。

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

评论

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

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

是否有任何供应商使其具有线程安全性?

是否有人链接到最新的C ++ 2011建议标准?

从某种意义上说,这是printf闪耀的地方,因为完整的输出被一次性写入stdout。使用std :: cout时,表达式链的每个链接将分别输出到stdout;在它们之间可能会有一些其他线程写入stdout,这导致最终输出的顺序混乱。

#1 楼

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


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

评论


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

– Nemo
2011年6月16日15:46

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

–ildjarn
11年6月16日在18:02

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

– JohannesD
2011年6月16日18:31



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

–ildjarn
11年6月16日在18:35

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

–edA-qa mort-ora-y
2011年6月16日19:55

#2 楼

这是一个很大的问题。

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

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

C ++ 0x之前的特定实现如何?好吧,例如,这是来自GCC 4.5.2的basic_streambuf<...>:sputc的源代码(“ 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。尽管那可能会慢得令人难以置信。

评论


很好的答案,但请看一下Martinho的回答,它表明C ++ 11确实定义了cout的同步。

–edA-qa mort-ora-y
2011年6月16日16:59

#3 楼


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现在是标准,所以这个答案现在实际上是错误的。

–edA-qa mort-ora-y
2011年6月16日17:00

#4 楼

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

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


http://gcc.gnu.org/onlinedocs/libstdc++/manual/using_concurrency.html#manual.intro.using.concurrency .io

关键的东西可能是:


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

因此,对于3.0,“ is
必须对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/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.exe 16.x)。您可以使用页面上的下拉控件选择MSVC的较早版本的信息(该信息对于较旧的版本是不同的)。

评论


“我不知道上述3.0时间表是否有任何变化。”确实做到了。在过去的几年中,g ++流实现已执行了自己的缓冲。

– Nemo
2011年6月16日17:13