将输出同步到std :: cout

时间:2015-02-22 17:03:21

标签: c++ multithreading c++11

我有一个应用程序,其中多个线程写入std::cout,我正在寻找一个简单的解决方案,以防止发送到std::cout的数据被乱码。

例如,如果我有2个线程并且都输出:

std::cout << "Hello" << ' ' << "from" << ' ' << "thread" << ' ' << n << '\n';

我可能会看到类似的内容:

HelloHello from  fromthread  2
thread 1

我希望看到的是:

Hello from thread 2
Hello from thread 1

显示这些行的顺序并不是很重要,只要它们不会混合在一起。

我提出了以下相当简单的实现:

class syncstream : public std::ostringstream {
public:
    using std::ostringstream::ostringstream;

    syncstream& operator<<(std::ostream&  (*pf)(std::ostream&) ) { pf(*this); return *this; }
    syncstream& operator<<(std::ios&      (*pf)(std::ios&)     ) { pf(*this); return *this; }
    syncstream& operator<<(std::ios_base& (*pf)(std::ios_base&)) { pf(*this); return *this; }

    template<typename T>
    syncstream& operator<<(T&& token) {
        static_cast<std::ostringstream&>(*this) << std::forward<T>(token);
        return *this;
    }
};

inline std::ostream& operator&&(std::ostream& s, const syncstream& g) { return s << g.str(); }
#define synced(stream) stream && syncstream()

抱歉宏。

所以,现在在我的主题中,我可以做到:

synced(std::cout) << "Hello"  << ' ' << "from" << ' ' << "thread" << ' ' << n << '\n';

由于我最初对§27.4.1的误解,我写了上述内容。但是,令人惊讶的是它的效果非常好。

我写了以下测试用例:

void proc(int n) {
    synced(std::cout) << "Hello" << ' ' << "world" << ' ' << "from" << ' ' << "thread" << ' ' << n << '\n';
}

int main() {
    std::vector<std::thread> threads;
    for(int n = 0; n < 1000; ++n) threads.push_back(std::thread(std::bind(proc, n)));

    for(std::thread& thread: threads) thread.join();
    return 0;
}

(完整版here)并在我的系统上使用g ++ 4.8.3和clang ++ 3.5.1(使用libstdc ++和libc ++)运行它。

使用script进行测试,运行测试用例1000次,生成100万个输出行,然后解析任何乱码行的输出。

无法使工作(即产生乱码)。

所以我的问题是:

为什么上述实施有效?

2 个答案:

答案 0 :(得分:2)

在不产生乱码的意义上,这似乎是线程安全的,如果每个输出都以新行结束。但是,它确实改变了流输出的性质,特别是在刷新方面。

1 synced(std::cerr)将被缓冲(进入syncstream),而std::cerr永远不会被缓冲。

2无法保证

synced(std::cout) << "a=" << 128 << std::endl;

实际上刷新了std::cout的缓冲区,因为所有std::cout获取的是字符串"a=128\n"

对线程安全的更强解释是输出顺序反映了输出调用的顺序(如果有的话)。那就是

synced(std::cout) << "a=" << 128 << std::endl;
线程A上的

保证(例如通过锁)在线程B上的相同调用之前,然后A的输出应始终优先于{{1}的输出}}。我不认为你的代码可以实现这一点。

答案 1 :(得分:2)

关于线程安全:它的线程是安全的 不会导致数据竞争。但只要目标是其中之一 标准流对象(std::cout等),并且只有它们 与stdio保持同步。这是所有标准保证。和 即使这样,你仍然可以得到交错的字符。

我过去不得不经常处理这个问题。我的解决方案有 始终是一个包装类,带有指向实际的指针 std::ostream和模板:

template <typename T>
SynchedOutput& operator<<( T const& obj )
{
    if ( myStream != nullptr ) {
        (*myStream) << obj;
    }
    return *this;
}

SynchedOutput的构造函数然后获取互斥锁,并且 析构函数释放它,所以你可以写:

SynchedOutput( stream, mutex ) << ...;

(在我的情况下,我从一个函数返回临时函,而且是 在C ++ 11及其移动语义之前这样做,所以我的代码更多一些 复杂;我不得不支持复制,并跟踪计数 复制,以便在最后一个被破坏时我可以解锁。今天, 只要实现移动语义,如果要返回,则不需要复制 来自函数的实例。))

这里的问题是确保每个人都使用相同的互斥锁。一 可能的可能是让构造函数在一个中查找互斥锁 std::map索引流对象的地址。这个查找 需要一个全局锁,所以你甚至可以构造一个新的互斥锁 流对象没有。真正的问题是确保 当流被破坏时,互斥锁将从地图中删除。