线程安全的cout技术。我错过了什么吗?

时间:2012-03-02 02:21:32

标签: c++ multithreading thread-safety iostream cout

我正在为游戏项目使用一些多线程代码,并且厌倦了使用cout同时对两个线程创建的stdout呕吐进行排序。我做了一些研究,在拿出“东西”之前盯着墙壁呆了一两个小时。以下代码使用SFML进行计时和线程处理。 SFML互斥锁只是窗口中的关键部分。

标题:

#include <SFML\System.hpp>
#include <iostream>

class OutputStreamHack
{
    public:
    OutputStreamHack();
    ~OutputStreamHack();

    ostream& outputHijack(ostream &os);

    private:
    sf::Clock myRunTime;
    sf::Mutex myMutex;
};

static OutputStream OUTHACK;

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue);

实现:

#include <SFML\System.hpp>
#include <iostream>

#include "OutputStreamHack.h"

using namespace std;

OutputStreamHack::OutputStreamHack()
{
    myMutex.Unlock();
    myRunTime.Reset();
}

OutputStreamHack::~OutputStreamHack()
{
    myMutex.Unlock();
    myRunTime.Reset();
}

ostream& OutputStreamHack::outputHijack(ostream &os)
{

    sf::Lock lock(myMutex);
    os<<"<"<<myRunTime.GetElapsedTime()<<","<<GetCurrentThreadId()<<"> "<<flush;
    return os;
}

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue)
{
    OUTHACK.outputHijack(os);
    return os;
}

用法:

cout<<OUTHACK<<val1<<val2<<val3....<<endl;

好的,所以它的工作方式是通过重载的插入操作符,通过将迭代器锁定在静态对象中,然后刷新缓冲区来强制执行线程安全。如果我正确地理解了这个过程(我主要是一个自学成才的程序员),cout会从头到尾处理其插入链的元素,在链中传递一个ostream变量,为每个元素预先添加到流中。一旦到达OUTHACK元素,就会调用重载的运算符,锁定互斥锁,并刷新流。

我已将一些时间/线程ID调试信息添加到输出中以进行验证。到目前为止,我的测试显示此方法有效。我有几个线程用多个参数敲击cout,一切都以正确的顺序出现。

根据我在研究这个问题时所阅读的内容,cout中缺乏线程安全性似乎是人们在进入线程编程时遇到的一个非常常见的问题。我想弄清楚的是,如果我使用的技术是解决问题的简单方法,或者我认为我很聪明但缺少重要的东西。

根据我的经验,用于描述编程的聪明一词只是延迟疼痛的代码词。我在这里做些什么,或者只是在圈子里追逐糟糕的黑客?

谢谢!

1 个答案:

答案 0 :(得分:21)

这里的线程安全不是cout本身。它按顺序调用两个函数调用。 std::cout << a << b大致相当于调用operator<<(std::cout, a)后跟operator<<(std::cout, b)。按顺序调用两个函数并不能保证它们将以原子方式执行。

原样,只有时间和线程ID的输出受互斥锁保护。在插入OUTHACKval1之间插入另一个线程是完全可能的,因为在插入OUTHACK后锁定不再被保留。

您可以operator<<OutputStreamHack返回按值在析构函数中解锁的对象auto_ptr。由于临时数据存活到每个完整表达式的结尾,代码将保持锁定“直到分号”。但是,由于可能涉及副本,如果没有移动构造函数(或C ++ 03中的自定义复制构造函数,类似于cout gasp ),这可能会出现问题。

另一种选择是使用std::stringstream的现有线程安全性(由C ++ 11中的语言保证,但许多实现之前都是线程安全的)。创建一个将所有内容流式传输到class FullExpressionAccumulator { public: explicit FullExpressionAccumulator(std::ostream& os) : os(os) {} ~FullExpressionAccumulator() { os << ss.rdbuf() << std::flush; // write the whole shebang in one go } template <typename T> FullExpressionAccumulator& operator<<(T const& t) { ss << t; // accumulate into a non-shared stringstream, no threading issues return *this; } private: std::ostream& os; std::stringstream ss; // stringstream is not copyable, so copies are already forbidden }; // using a temporary instead of returning one from a function avoids any issues with copies FullExpressionAccumulator(std::cout) << val1 << val2 << val3; 成员的对象,然后在销毁时立即将其全部写出来。

{{1}}