【发布时间】:2012-03-20 14:12:36
【问题描述】:
我正在为一个游戏项目处理一些多线程代码,并且有点厌倦了对两个线程使用 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 中缺乏线程安全似乎是人们在尝试线程编程时遇到的一个相当普遍的问题。我想弄清楚的是我使用的技术是解决问题的简单方法,还是我认为我很聪明但错过了一些重要的东西。
根据我的经验,用于描述编程的聪明这个词只是延迟痛苦的代名词。我是在这里做某事,还是只是在兜圈子追逐糟糕的黑客行为?
谢谢!
【问题讨论】:
-
它的工作原理纯属运气。只有时间和线程 ID 的输出受互斥锁保护。完全有可能在
OUTHACK和val1之间潜入另一个线程。 -
考虑先写入
ostringstream,然后在一次操作中将内容转储到cout。cout通常是线程安全的,只是锁定显然是每次调用,并且每个<<操作都是一个不同的调用......这样,您的应用程序代码中就没有额外的锁定 - 这只会降低并行性.
标签: c++ multithreading thread-safety iostream cout