【问题标题】:Synchronizing STD cout output multi-thread同步 STD cout 输出多线程
【发布时间】:2012-03-09 02:09:09
【问题描述】:

最近我一直在使用多线程编码,写了一段时间后我意识到如果我在不同的 boost::threads 中使用 std::cout,输出将没有逻辑顺序,我的程序m 测试类似于:

#include <boost/thread/thread.hpp>
#include <iostream>

int my01( void )
{
    std::cout << "my01" << std::endl;
    return 0;
}
/* my02, my03 and my04 are the same with different outputs*/
[...]
int main( void )
{
    boost::thread t1(&my01);
    boost::thread t2(&my02);
    boost::thread t3(&my03);
    boost::thread t4(&my04);

    while(!t1.joinable() || !t2.joinable() || !t3.joinable() || !t4.joinable());

    t1.join();
    t2.join();
    t3.join();
    t4.join();

    std::cout << "The end!" << std::endl;
    getchar();
    return 0;
}


并且输出通常是这样的(它会改变):

my02my01
我的04
我的03
空白线
结束!

考虑到这个问题,我正在考虑创建一个线程来管理所有输出,因此它们的顺序如下:

我的01
我的02
我的03
我的04
结束!

编写此类线程或管理这些输出的最佳方式是什么?
也请阅读此问题的答案:Is cout synchronized/thread-safe?

Ps:我使用的是 Visual C++ 2010 Express,我的 cpu 有 8 个不同的内核。

感谢您的宝贵时间!

【问题讨论】:

  • 我有完全相同的问题,但我无法控制使用 boost::threads 的代码,所以对我来说,下面发布的解决方案不是一个选项。如何在不重写 boost::thread 的使用的情况下管理输出同步性?

标签: c++ multithreading synchronization cout


【解决方案1】:

首先,您可以考虑避免所有显式线程管理,而是使用std::async 在任意数量的单独线程中启动您的任务。

其次,不是在线程本身中执行 I/O,而是要创建结果并串行执行输出本身。这意味着线程函数只是创建了一些数据,并将其留给调用者实际写出:

std::string process(int value) {
     std::ostringstream buffer;
     buffer << "my" << std::setfill('0') << std::setw(2) << value;
     return buffer.str();
}

然后我们需要异步启动四个副本:

std::vector<std::future<std::string> > results;

for (int i=0; i<4; i++)
    results.push_back(std::async(std::launch::async, process, i));

然后我们得到结果并按顺序打印出来:

for (auto &r : results)
    std::cout << r.get() << "\n";

把它们放在一起,我们可以得到这样的代码:

#include <string>
#include <iostream>
#include <thread>
#include <future>
#include <sstream>
#include <vector>
#include <iomanip>

std::string process(int value) {
     std::ostringstream buffer;
     buffer << "my" << std::setfill('0') << std::setw(2) << value;
     return buffer.str();
}

int main() { 
    std::vector<std::future<std::string>> rets;

    for (int i=0; i<4; i++)
        rets.push_back(std::async(std::launch::async, process, i));

    for (auto & t : rets) {
        t.wait();
        std::cout << t.get() << "\n";
    }
}

我应该补充一点:我基于标准 C++11 futures。我相信基本思想也应该适用于 Boost futures(标准所依据的),但我还没有测试过。我预计需要对 Boost 的未来进行一些小的调整(例如,对名称)。

【讨论】:

    【解决方案2】:

    我通过编写一个瘦包装器解决了这个问题,该包装器在开始写入流时锁定互斥锁,并在写入语句完成后释放它,同时刷新流。

    用法:将 std::cout 替换为 safe_cout。

    请记住,它不支持花哨的 std::cout 功能,例如 std::endl。

    查看下面的代码或从这里获取:https://github.com/dkorolev/felicity/blob/master/safe_ostream.h

    #include <cassert>
    #include <iostream>
    #include <mutex>
    #include <memory>
    
    struct safe_ostream {
      struct guarded_impl {
        guarded_impl() = delete;
        guarded_impl(const guarded_impl&) = delete;
        void operator=(const guarded_impl&) = delete;
        guarded_impl(std::ostream& ostream, std::mutex& mutex) : ostream_(ostream), guard_(mutex) {
        }
        ~guarded_impl() {
          ostream_.flush();
        }
        template<typename T> void write(const T& x) {
          ostream_ << x;
        }
        std::ostream& ostream_;
        std::lock_guard<std::mutex> guard_;
      };
      struct impl {
        impl() = delete;
        void operator=(const impl&) = delete;
        impl(std::ostream& ostream, std::mutex& mutex) : unique_impl_(new guarded_impl(ostream, mutex)) {
        }
        impl(const impl& rhs) {
          assert(rhs.unique_impl_.get());
          unique_impl_.swap(rhs.unique_impl_);
        }
        template<typename T> impl& operator<<(const T& x) {
          guarded_impl* p = unique_impl_.get();
          assert(p);
          p->write(x);
          return *this;
        }
        mutable std::unique_ptr<guarded_impl> unique_impl_;
      };
      explicit safe_ostream(std::ostream& ostream) : ostream_(ostream) {
      }
      template<typename T> impl operator<<(const T& x) {
        return impl(ostream_, mutex_) << x;
      }
      std::ostream& ostream_;
      std::mutex mutex_;
    };
    safe_ostream safe_cout(std::cout);
    safe_ostream safe_cerr(std::cerr);
    

    【讨论】:

    • Dima,锁定是邪恶的。
    【解决方案3】:

    使用锁定。如果您可以使用 boost,请执行例如

    int my01(boost::mutex *coutGuard)
    {
      {
         // lock cout until the closing brace
         boost::mutex::scoped_lock lock(*coutGuard);
    
         std::cout << "my01" << std::endl;
      }
    
      return 0;
    }
    
    int main( void )
    {
       boost::mutex coutGuard;
    
       boost::thread t1(boost::bind(&my01, &coutGuard));
       ...
    }
    

    可以使用lock_guard 代替scoped_lock

    【讨论】:

    • 我认为它几乎就在那里,但使用这个互斥模式你没有办法保证顺序,必须使用其他东西。
    【解决方案4】:

    给每个线程一个std::ostringstream 来写入输出。在程序结束时,按顺序打印每个线程的输出。

    考虑到线程 4 可能会在线程 1 之前很久完成,你会怎么做呢?

    【讨论】:

    • 程序会运行很长时间,所以我想写一个线程来管理一个线程安全的缓冲区来输出所有内容。
    • 您的要求(“按顺序”)看起来必须在打印线程 2 的输出之前打印线程 1 的整个输出。您不妨从线程 1 打印到 cout,并按照 Zan 的建议在所有其他线程中使用 std::ostringstream
    【解决方案5】:

    您要么需要对线程强加一个顺序,以便输出的顺序符合您的要求,(可能通过将线程实例或事件传递给适当的线程,以便它们只能按您的顺序执行),或者您可以给所有输出一个线程序列号,将所有输出排队到一个“打印”线程,并在其中保留所有乱序行的列表,以便打印输出如您所愿。

    对于“真正的”应用程序(即不是滥用线程的普通测试应用程序),线程在必须保留顺序的顺序缓冲区上并行执行大量工作,从而迫使线程等待彼此通常不是一个合理的选择。通常使用序列号并在之后重新组装缓冲流。

    【讨论】:

      猜你喜欢
      • 2015-04-01
      • 2013-02-08
      • 2020-10-02
      • 2015-02-03
      • 2013-08-19
      • 2011-09-16
      • 1970-01-01
      • 2017-04-30
      相关资源
      最近更新 更多