【问题标题】:Controlling output stream in multi-threaded program控制多线程程序中的输出流
【发布时间】:2018-06-04 12:21:15
【问题描述】:

我正在尝试控制模拟中的输出打印。它打印了很多输出流信息。这是我如何尝试控制输出流的示例代码。有时我想为每个线程打印信息,有时我不想从线程中单独打印以减少模拟中的系统调用。我通过命令行参数来控制流。参数v 表示没有打印。问题是它在整个模拟器中需要很多 if 语句。有什么简单的方法可以解决这个问题吗?

#include <iostream>
#include <thread>

void work_to_do_1(char ch)
{
//work for thread 1
if(ch != 'v')
std::cout << "-:Thread 1:-" << std::endl;
}

void work_to_do_2(char ch)
{
if (ch != 'v')
std::cout << "-:Thread 2:-" << std::endl;
}

void work_to_do_3(char ch)
{
if (ch != 'v')
std::cout << "-:Thread 3:-" << std::endl; 
}

int main(int argc, char *arg[])
{
std::cout << "You have entered " << argc
    << " arguments:" << "\n";

for (int i = 0; i < argc; ++i)
{
    std::cout << arg[i] << "\n";
}
char t = *arg[1];
std::cout << "manager is running" << std::endl;

std::thread t1(work_to_do_1, t);
std::thread t2(work_to_do_2, t);
std::thread t3(work_to_do_3, t);
t1.join();
t2.join();
t3.join();
system("pause");
return 0;
}

【问题讨论】:

  • 为什么是!(ch == 'v') 而不是更常见(和平等)的ch != 'v'
  • 只需使用my_cout &lt;&lt; "whatever";,其中my_cout 包含std::cout 和条件。无论如何,在代码中的任何地方都明确显示std::cout 并不是最好的事情
  • Here's an old answer 关于如何创建“空”流。如果不应该输出任何内容,则传递一个“null”流,或者如果你想要输出,则传递一个标准的std::cout
  • 还要注意std::cout afaik 不是线程安全的
  • 旁注:afaik 你应该在线程完成执行之前加入或分离线程,因为既不加入也不分离线程的析构函数会导致 ub。

标签: c++ multithreading


【解决方案1】:

制作自己的 nul 流:

struct cnul_t : std::basic_ostream<char> {} cnul;
template<class T> std::ostream& operator<<(cnul_t& os, T const&) { return os; }

并将您的输出重定向到它以忽略它:

#include <ostream>
#include <iostream>

struct cnul_t : std::basic_ostream<char> {} cnul;
template<class T> std::ostream& operator<<(cnul_t& os, T const&) { return os; }

void maybe_log(bool b)
{
    std::ostream& out = b == true ? std::cout : cnul;
    out << "Hello, World!\n";
}

int main()
{
    maybe_log(true);  // outputs Hello, World!
    maybe_log(false); // no output
}

演示:http://coliru.stacked-crooked.com/a/362ecb660283cbff

【讨论】:

  • 您可以轻松制作wcnul ;)
  • 如果有 10 个不同的类,我必须将这个结构传递给我对每个类的引用,不是吗?
  • @ZeeshanHayat 不,将cnul 设为全局变量。这种情况很少见。
【解决方案2】:

好吧,好吧,如果你已经阅读并理解了 cmets,你会发现真正的问题并不是你想的那样。真正的问题是您的日志记录代码不是线程安全的。

This answer很好的解释了这个问题。尽管ostreams 本身是线程安全的(自 C++11 起),但像 std::cout &lt;&lt; "-:Thread 1:-" &lt;&lt; std::endl; 这样的东西实际上是对std::cout.operator&lt;&lt;两次 调用,并且另一个线程可能会潜入它们之间,从而混淆你的输出。这个,我想,你可以不用。

所以,厚颜无耻地从this post 窃取代码我谦虚地提交以下解决方案(它还有一个全局标志gLogging,用于打开或关闭登录)。这将在您登录std::endl原子地 将行写入std::cout。我写这篇文章是为了培养我自己的个人技能,我想你可能会喜欢它。

有关如何检测std::endl 的说明,请参阅链接的帖子,但基本原理是每个线程都有一个单独的日志缓冲区,当它有完整的输出行要摆脱时,它会刷新到std::cout。代码包括一个管理器类 (Logger) 来处理创建、销毁和访问这些缓冲区的细节。如图所示,您只需在每个线程的开头放置两行初始化代码,然后登录到logstream 而不是std::cout

#include <iostream>
#include <sstream>
#include <mutex>
#include <map>
#include <thread>

bool gLogging = true;
constexpr int bufsize = 512;        // needs to be big enough for longest logging line expected

// A streambuf that writes atomically to std::cout when (indirectly) it sees std::endl
class LogBuf : public std::stringbuf
{
public:
     LogBuf () { setbuf (m_buf = new char [bufsize], bufsize); str (""); }
     ~LogBuf () { delete [] m_buf; }

protected:
     // This gets called when the ostream we are serving sees endl
     int sync() override
     {
         if (gLogging)
         {
             std::cout << str();
             std::cout.flush();
         }
         str("");
         return 0;
     }

private:
    char *m_buf;
};

// An ostream that uses LogBuf
class LogStream : public std::ostream
{
public:
    LogStream () : std::ostream (m_LogBuf = new LogBuf ()) { }
    ~LogStream () { delete m_LogBuf; }

private:
    LogBuf *m_LogBuf;
};

// A class to manage LogStream objects (one per thread)
class Logger
{
public:
    void AddThread (void)
    {
        mutex.lock ();
        m_logstreams [std::this_thread::get_id ()] = new LogStream ();
        mutex.unlock ();
    }

    void RemoveThread ()
    {
        mutex.lock ();
        std::thread::id thread_id = std::this_thread::get_id ();
        LogStream *logstream = m_logstreams [thread_id];
        m_logstreams.erase (m_logstreams.find (thread_id));
        mutex.unlock ();
        delete logstream;
    }

    LogStream& GetLogStream ()
    {
        mutex.lock ();
        LogStream *logstream = m_logstreams [std::this_thread::get_id ()];
        mutex.unlock ();
        return *logstream;
    }

    private:
    static std::mutex mutex;
    std::map<const std::thread::id, LogStream *> m_logstreams;
};

std::mutex Logger::mutex;
Logger logger;

// A simple class to make sure we remember to call RemoveThread
class LogStreamHelper
{
public:
    LogStreamHelper () { logger.AddThread (); }
    ~LogStreamHelper () { logger.RemoveThread (); }
    inline LogStream &GetLogStream () { return logger.GetLogStream (); }
};

// Test program
void work_to_do_1()
{
    LogStreamHelper logstream_helper;
    LogStream& logstream = logstream_helper.GetLogStream ();
    logstream << "-:Thread 1:-" << std::endl;
}

void work_to_do_2()
{
    LogStreamHelper logstream_helper;
    LogStream& logstream = logstream_helper.GetLogStream ();
    logstream << "-:Thread 2:-" << std::endl;
}

int main ()
{
    LogStreamHelper logstream_helper;
    LogStream& logstream = logstream_helper.GetLogStream ();
    logstream << "Main thread" << std::endl;

    std::thread t1 (work_to_do_1);
    std::thread t2 (work_to_do_2);

    t1.join ();
    t2.join ();

    return 0;
}

输出:

Main thread
-:Thread 1:-
-:Thread 2:-

Wandbox 运行它。

【讨论】:

  • 谢谢,我知道代码不是线程安全的,它不会以正确的方式给出结果,但正在考虑的问题只是控制输出(根据传递的参数打开或关闭它)。
  • 您说要登录子线程。我已经给了你工具,你为什么不好好做呢?
  • 没错,每个线程都在打印一些东西,我只是想控制输出流。问题是,如果我有 100 个线程并且每个线程有多个不同的类,那么在各处初始化记录器的工作量太大。我只是在寻找一个可扩展的解决方案:)
  • 好吧,这是你的电话,但为了清楚起见,LoggerHelper 每个线程只需要实例化一次,然后保持在范围内直到线程退出。事实上,我犯了一个错误。 LogStreamHelper:: GetLogStream () 可以设为static,这意味着您可以随时在任何线程中调用它,只要您在每个线程的开头创建了LoggerHelper 的实例即可。所以你可以这样做:#define LOGGER_OUT LogStreamHelper:: GetLogStream () 然后LOGGER_OUT &lt;&lt; "foo" &lt;&lt; std::endl; 在你的代码中的任何地方并且是适当的线程安全的。
猜你喜欢
  • 2011-07-26
  • 1970-01-01
  • 1970-01-01
  • 2013-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-14
  • 1970-01-01
相关资源
最近更新 更多