iostream 线程安全,必须分别锁定 cout 和 cerr 吗?
Posted
技术标签:
【中文标题】iostream 线程安全,必须分别锁定 cout 和 cerr 吗?【英文标题】:iostream thread safety, must cout and cerr be locked separately? 【发布时间】:2013-01-16 06:02:22 【问题描述】:我了解,为避免输出混合,多个线程对 cout 和 cerr 的访问必须同步。在同时使用 cout 和 cerr 的程序中,单独锁定它们是否足够?还是同时写入 cout 和 cerr 仍然不安全?
编辑说明:我知道 cout 和 cerr 在 C++11 中是“线程安全的”。我的问题是,不同线程同时写入 cout 和写入 cerr 是否会像两次写入 cout 那样相互干扰(导致交错输入等)。
【问题讨论】:
它永远不会“不安全”。你可能只是没有得到你所期望的。 我想澄清一下。使用一个全局锁写入 cout 和 cerr 与分别使用单独的锁在行为上是否存在差异? 您可以使用不同的锁。它们不相互依赖。 或许将这个问题表述为“可能 cout 和 cerr 被分别锁定?” 另见Is cout synchronized/thread-safe? 【参考方案1】:如果你执行这个函数:
void f()
std::cout << "Hello, " << "world!\n";
从多个线程中,您将获得两个字符串"Hello, "
和"world\n"
或多或少的随机交错。那是因为有两个函数调用,就像你写的代码是这样的:
void f()
std::cout << "Hello, ";
std::cout << "world!\n";
为了防止这种交错,你必须添加一个锁:
std::mutex mtx;
void f()
std::lock_guard<std::mutex> lock(mtx);
std::cout << "Hello, " << "world!\n";
也就是说,交织问题与cout
无关。这是关于使用它的代码:有两个单独的函数调用插入文本,所以除非你阻止多个线程同时执行相同的代码,否则函数调用之间可能会发生线程切换,这就是给你的交错。
请注意,互斥锁不会阻止线程切换。在前面的代码sn-p中,它防止两个线程同时执行f()
的内容;其中一个线程必须等到另一个线程完成。
如果你也写信给cerr
,你会遇到同样的问题,并且你会得到交错输出,除非你确保你永远不会有两个线程在同时,这意味着两个函数必须使用相同的互斥锁:
std::mutex mtx;
void f()
std::lock_guard<std::mutex> lock(mtx);
std::cout << "Hello, " << "world!\n";
void g()
std::lock_guard<std::mutex> lock(mtx);
std::cerr << "Hello, " << "world!\n";
【讨论】:
应该注意的是,如果操作系统将cout
和cerr
指向同一个源(比如...控制台),那么文本将被交错,除非它们共享相同互斥体。
操作系统和库与它有很大关系。 Linux 中的线程可以随意输出到标准输出,无需锁定,但单独的文本行不会被另一个线程的标准输出打乱。到 stderr 的输出没有以同样的方式缓冲,所以它会被弄乱。这种行为差异会导致某些版本的 Eclipse CDT 出现问题;编译器错误出现在 stderr 上,并且没有出现在 Eclipse 控制台中编译器的 stdout 的正确位置。【参考方案2】:
在 C++11 中,与 C++03 不同,全局流对象(cout
、cin
、cerr
和 clog
)的插入和提取是线程安全的。无需提供手动同步。但是,由不同线程插入的字符可能会在输出时发生不可预测的交错;同样,当多个线程从标准输入读取时,无法预测哪个线程将读取哪个令牌。
全局流对象的线程安全默认是激活的,但是可以通过调用流对象的sync_with_stdio
成员函数并将false
作为参数传递来关闭它。在这种情况下,您将不得不手动处理同步。
【讨论】:
【参考方案3】:同时写入 cout 和 cerr 可能不安全! 这取决于 cout 是否绑定到 cerr。见std::ios::tie。
"绑定流是一个输出流对象,它在之前被刷新 此流对象中的每个 i/o 操作。”
这意味着,cout.flush() 可能会被写入 cerr 的线程无意中调用。 我花了一些时间弄清楚,这就是我的一个项目中 cout 输出中随机缺少行尾的原因:(
对于 C++98,cout 不应绑定到 cerr。但是,尽管有标准,但在使用 MSVC 2008(我的经验)时它是绑定的。使用以下代码时,一切正常。
std::ostream *cerr_tied_to = cerr.tie();
if (cerr_tied_to)
if (cerr_tied_to == &cout)
cerr << "DBG: cerr is tied to cout ! -- untying ..." << endl;
cerr.tie(0);
另见:why cerr flushes the buffer of cout
【讨论】:
有趣。可以肯定的是,这并不像 UB 那样不安全,它“只是”表现不佳。 哇...这让我发现了一些难以发现的错误。【参考方案4】:这里已经有几个答案了。我将总结并解决它们之间的交互。
通常,
std::cout
和std::cerr
通常会合并到一个文本流中,因此将它们锁定在共同的位置会导致最有用的程序。
如果您忽略该问题,cout
和 cerr
默认别名为它们的 stdio
对应物,它们是线程安全的 as in POSIX,直到标准 I/O 函数 (C++14 §27.4. 1/4,比单独使用 C 的保证更强)。如果您坚持使用这种功能选择,您会得到垃圾 I/O,但不会出现未定义的行为(语言律师可能会将其与“线程安全”联系起来,而不管有用性如何)。
但是,请注意,虽然标准格式的 I/O 函数(例如读取和写入数字)是线程安全的,但用于更改格式的操纵器(例如用于十六进制的 std::hex
或用于限制输入字符串的 std::setw
大小)不是。因此,通常不能假设省略锁是安全的。
如果选择单独锁定,事情就复杂了。
单独锁定
为了提高性能,可以通过分别锁定cout
和cerr
来减少锁争用。它们是单独缓冲(或非缓冲)的,它们可能会刷新到单独的文件中。
默认情况下,cerr
在每次操作之前刷新cout
,因为它们是“绑定的”。这会破坏分离和锁定,所以记得在使用它之前调用cerr.tie( nullptr )
。 (这同样适用于cin
,但不适用于clog
。)
从stdio
解耦
标准规定cout
和cerr
上的操作不会引入种族,但这并不是它的确切含义。流对象并不特殊;它们的底层streambuf
缓冲区是。
此外,调用std::ios_base::sync_with_stdio
旨在删除标准流的特殊方面——允许它们像其他流一样被缓冲。虽然标准没有提到 sync_with_stdio
对数据竞争的任何影响,但快速浏览一下 libstdc++ 和 libc++(GCC 和 Clang)std::basic_streambuf
类表明它们不使用原子变量,因此它们可能会在以下情况下产生竞争条件用于缓冲。 (另一方面,libc++ sync_with_stdio
实际上什么都不做,所以调用它也没关系。)
如果您想获得额外的性能而不考虑锁定,sync_with_stdio(false)
是一个好主意。但是,在这样做之后,锁定是必要的,如果锁定是分开的,还需要锁定 cerr.tie( nullptr )
。
【讨论】:
如果你确实想要单独的锁定,我有 created 一个库。【参考方案5】:这可能有用;)
inline static void log(std::string const &format, ...)
static std::mutex locker;
std::lock_guard<std::mutex>(locker);
va_list list;
va_start(list, format);
vfprintf(stderr, format.c_str(), list);
va_end(list);
【讨论】:
【参考方案6】:我使用这样的东西:
// Wrap a mutex around cerr so multiple threads don't overlap output
// USAGE:
// LockedLog() << a << b << c;
//
class LockedLog
public:
LockedLog() m_mutex.lock();
~LockedLog() *m_ostr << std::endl; m_mutex.unlock();
template <class T>
LockedLog &operator << (const T &msg)
*m_ostr << msg;
return *this;
private:
static std::ostream *m_ostr;
static std::mutex m_mutex;
;
std::mutex LockedLog::m_mutex;
std::ostream* LockedLog::m_ostr = &std::cerr;
【讨论】:
以上是关于iostream 线程安全,必须分别锁定 cout 和 cerr 吗?的主要内容,如果未能解决你的问题,请参考以下文章