cout
这样的东西在标准库中是否得到特殊对待?,就是说,如果多个线程正在写入
cout
,它们是否可以破坏cout
对象?我了解,即使同步,您仍然会获得随机交错的输出,但是可以保证交错。也就是说,从多个线程使用cout
是否安全?此供应商依赖吗?重要提示:gcc的作用是什么?重要提示:如果您回答“是”,请提供某种参考,因为我需要某种证明。
我关心的也不是底层系统调用,这些调用很好,但是流在顶部增加了一层缓冲。
#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类型只是围绕
)。我们不会自己锁定
,而只是传递给
调用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
评论
这取决于供应商。 C ++(在C ++ 0x之前)没有多线程的概念。那么c ++ 0x呢?它定义了一个内存模型以及什么是线程,那么也许这些东西会滴落在输出中?
是否有任何供应商使其具有线程安全性?
是否有人链接到最新的C ++ 2011建议标准?
从某种意义上说,这是printf闪耀的地方,因为完整的输出被一次性写入stdout。使用std :: cout时,表达式链的每个链接将分别输出到stdout;在它们之间可能会有一些其他线程写入stdout,这导致最终输出的顺序混乱。