我有一个应用程序,其中多个线程写入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万个输出行,然后解析任何乱码行的输出。
我无法使不工作(即产生乱码)。
所以我的问题是:
为什么上述实施有效?
答案 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
索引流对象的地址。这个查找
需要一个全局锁,所以你甚至可以构造一个新的互斥锁
流对象没有。真正的问题是确保
当流被破坏时,互斥锁将从地图中删除。