通常,我假定流不同步,这取决于用户进行适当的锁定。但是,诸如此类的事情cout
在标准库中得到特殊对待吗?
也就是说,如果正在写入多个线程,它们是否cout
可以破坏cout
对象?我了解,即使同步,您仍然会获得随机交错的输出,但是可以保证交错。也就是说,cout
从多个线程使用安全吗?
该供应商依赖吗?gcc是做什么的?
重要提示:如果您回答“是”,请提供某种参考,因为我需要某种证明。
我也不在乎底层的系统调用,这些很好,但是流在顶部增加了一层缓冲。
通常,我假定流不同步,这取决于用户进行适当的锁定。但是,诸如此类的事情cout
在标准库中得到特殊对待吗?
也就是说,如果正在写入多个线程,它们是否cout
可以破坏cout
对象?我了解,即使同步,您仍然会获得随机交错的输出,但是可以保证交错。也就是说,cout
从多个线程使用安全吗?
该供应商依赖吗?gcc是做什么的?
重要提示:如果您回答“是”,请提供某种参考,因为我需要某种证明。
我也不在乎底层的系统调用,这些很好,但是流在顶部增加了一层缓冲。
Answers:
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)。[注意:如果用户希望避免插入字符,则仍必须同步多个线程对这些对象和流的并发使用。—尾注]
因此,您不会受到损坏的流,但是如果您不希望输出成为垃圾,则仍然需要手动对其进行同步。
cout.sync_with_stdio()
是正确的,cout
就可以很好地定义用于从多个线程输出字符而无需进行额外同步的功能,但是只能在单个字节的级别上使用。因此,cout << "ab";
和cout << "cd"
在不同的线程执行时可以输出acdb
,例如,但可能不会导致未定义的行为。
这是一个很好的问题。
首先,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;
}
显然,这不会执行锁定。而且也不是xsputn
。这绝对是cout使用的streambuf的类型。
据我所知,libstdc ++不会对任何流操作进行锁定。我不会指望任何东西,因为那样会很慢。
因此,使用此实现,很明显两个线程的输出可能会相互破坏(而不仅仅是交错)。
这段代码会破坏数据结构本身吗?答案取决于这些功能的可能相互作用。例如,如果一个线程尝试刷新缓冲区而另一个线程尝试调用缓冲区xsputn
或其他原因,会发生什么。这可能取决于您的编译器和CPU如何决定对内存加载和存储进行重新排序。需要仔细分析才能确定。如果两个线程尝试同时修改同一位置,这也取决于您的CPU的工作。
换句话说,即使它在您当前的环境中正常运行,当您更新任何运行时,编译器或CPU时,它也可能会中断。
内容提要:“我不会”。构建一个执行适当锁定的日志记录类,或移至C ++ 0x。
作为一种较弱的选择,您可以将cout设置为unbuffered。可能(尽管不能保证)会跳过与缓冲区有关的所有逻辑并write
直接调用。尽管那可能会太慢了。
cout
。
C ++标准未指定写入流是否是线程安全的,但通常不是。
www.techrepublic.com/article/use-stl-streams-for-easy-c-plus-plus-thread-safe-logging
并且:C ++线程安全的标准输出流(cout,cerr,clog)是否安全?
更新
请查看@Martinho Fernandes的答案,以了解有关新标准C ++ 11的内容。
就像其他答案提到的那样,这肯定是特定于供应商的,因为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.exe
16.x)。您可以使用页面上的下拉控件选择MSVC较早版本的信息(该信息对于较早版本是不同的)。