保存fprintf调用的变量参数列表

时间:2015-02-20 23:57:42

标签: multithreading c++11 variadic-functions

我正在编写一个繁重的多线程[> 170个线程] c ++ 11程序。每个线程都将信息记录到所有线程使用的一个文件中。出于性能原因,我想创建一个 log 线程,该线程通过fprintf()将信息写入全局文件。我不知道如何组织 worker 线程正在编写信息的结构,然后 log 线程可以读取该信息。

为什么我不在每个 worker 线程中调用sprintf(),然后只将输出缓冲区提供给 log 线程?对于格式化输出到日志文件中,我在locale函数中使用fprintf(),这与线程的其余部分不同。因此,我必须永久切换和锁定/保护xprintf()次调用,以便区分locale输出。 在 log 线程中,我有一个locale设置用于整个输出,而 worker 线程有locale版本。

log 主题的另一个原因是我必须" group"输出,否则来自每个 worker 线程的信息将不在块中:

错:

Information A Thread #1
Information A Thread #2
Information B Thread #1
Information B Thread #2

正确:

Information A Thread #1
Information B Thread #1
Information A Thread #2
Information B Thread #2

为了实现这种分组,我必须保护每个 worker 线程中的输出,这会减慢线程执行时间。

如何将va_list保存到 log 线程可以读取并传回fprintf()的结构中?

1 个答案:

答案 0 :(得分:2)

我不知道如何使用带有vprintf的旧版C va_list轻松完成此操作。由于你想在线程之间传递东西,迟早你需要以某种方式使用堆。

以下是使用Boost.Format进行格式化并使用Boost.Variant进行参数传递的解决方案。如果按顺序连接以下代码块,则示例已完成并正常工作。如果使用GCC进行编译,则需要传递-pthread链接器标志。当然,您还需要两个仅限标题的Boost库。以下是我们将使用的标题。

#include <condition_variable>
#include <iostream>
#include <list>
#include <locale>
#include <mutex>
#include <random>
#include <string>
#include <thread>
#include <utility>
#include <vector>

#include <boost/format.hpp>
#include <boost/variant.hpp>

首先,我们需要一些机制来异步执行某些任务,在这种情况下,打印我们的日志消息。由于这个概念是通用的,我为此使用了“抽象”基类Spooler。它的代码基于Herb Sutter在CppCon 2014(part 1part 2上的谈话“无锁编程(或,Juggling Razor Blades)”。我没有详细介绍这个代码,因为它主要是脚手架与你的问题没有直接关系,我假设你已经有了这个功能。我的Spooler使用受std::list保护的std::mutex作为任务队列。考虑使用无锁数据结构可能是值得的。

class Spooler
{
private:

  bool done_ {};
  std::list<std::function<void(void)>> queue_ {};
  std::mutex mutex_ {};
  std::condition_variable condvar_ {};
  std::thread worker_ {};

public:

  Spooler() : worker_ {[this](){ work(); }}
  {
  }

  ~Spooler()
  {
    auto poison = [this](){ done_ = true; };
    this->submit(std::move(poison));
    if (this->worker_.joinable())
      this->worker_.join();
  }

protected:

  void
  submit(std::function<void(void)> task)
  {
    // This is basically a push_back but avoids potentially blocking
    // calls while in the critical section.
    decltype(this->queue_) tmp {std::move(task)};
    {
      std::unique_lock<std::mutex> lck {this->mutex_};
      this->queue_.splice(this->queue_.cend(), tmp);
    }
    this->condvar_.notify_all();
  }

private:

  void
  work()
  {
    do
      {
        std::unique_lock<std::mutex> lck {this->mutex_};
        while (this->queue_.empty())
          this->condvar_.wait(lck);
        const auto task = std::move(this->queue_.front());
        this->queue_.pop_front();
        lck.unlock();
        task();
      }
    while (!this->done_);
  }
};

Spooler开始,我们现在派生Logger(私有)从Spooler继承其异步功能,并添加特定于日志记录的功能。它只有一个名为log的函数成员,它将一个格式字符串作为参数,并将零个或多个参数作为std::vector的{​​{1}}格式化。{/ p>

不幸的是,这限制了我们可以支持的固定数量的类型,但这不应该是一个大问题,因为C boost::variant也不支持任意类型。为了这个例子,我只使用printfint,但您可以使用double s,std::string指针扩展列表或者你有什么。

void *函数构造一个lambda表达式,该表达式创建一个log对象,将其提供给所有参数,然后将其写入boost::format或您希望格式化消息发送到的任何位置。< / p>

std::log的构造函数有一个重载,它接受格式字符串和语言环境。您可能对此感兴趣,因为您已经提到在注释中设置自定义区域设置。通常的构造函数只接受一个参数,格式字符串。

注意如何在假脱机程序的线程上完成所有格式化和输出。

boost::format

这就是全部。我们现在可以在此示例中使用class Logger : Spooler { public: void log(const std::string& fmt, const std::vector<boost::variant<int, double>>& args) { auto task = [fmt, args](){ boost::format msg {fmt, std::locale {"C"}}; // your locale here for (const auto& arg : args) msg % arg; // feed the next argument std::clog << msg << std::endl; // print the formatted message }; this->submit(std::move(task)); } }; 。重要的是在Logger被破坏之前join()编辑所有工作线程,否则它将不会处理所有消息。

Logger

可能的输出:

int
main()
{
  Logger logger {};
  std::vector<std::thread> threads {};
  std::random_device rnddev {};
  for (int i = 0; i < 4; ++i)
    {
      const auto seed = rnddev();
      auto task = [&logger, i, seed](){
        std::default_random_engine rndeng {seed};
        std::uniform_real_distribution<double> rnddist {0.0, 0.5};
        for (double p = 0.0; p < 1.0; p += rnddist(rndeng))
          logger.log("thread #%d is %6.2f %% done", {i, 100.0 * p});
        logger.log("thread #%d has completed its work", {i});
      };
      threads.emplace_back(std::move(task));
    }
  for (auto& thread : threads)
    thread.join();
}