gpt4 book ai didi

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

转载 作者:可可西里 更新时间:2023-11-01 16:27:46 26 4
gpt4 key购买 nike

我正在为一个游戏项目处理一些多线程代码,对同时使用 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 中缺乏线程安全似乎是人们在冒险进行线程编程时遇到的一个非常普遍的问题。我想弄清楚的是,如果我使用的技术是解决问题的简单方法,或者我认为我很聪明但遗漏了一些重要的东西。

根据我的经验,用于描述编程的“聪明”一词只是延迟痛苦的代名词。我是要在这里做点什么,还是只是在兜圈子里追逐糟糕的黑客?

谢谢!

最佳答案

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

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

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

另一种选择是使用现有的线程安全 cout (由 C++11 中的语言保证,但之前许多实现都是线程安全的)。制作一个将所有内容流式传输到 std::stringstream 中的对象成员,销毁时一次性全部写出。

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;

关于c++ - 线程安全的 cout 技术。我错过了什么吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9527127/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com