可变增强绑定类型分辨率

时间:2015-01-31 18:24:18

标签: c++ templates bind boost-asio variadic-templates

我正在尝试编写一个异步记录器,它接受可变参数,然后使用可变参数stringer将它们串在一起,然后推送到单个生产者单个使用者队列。

我陷入了我的Log结构的enqueue函数部分,如下所示:

template <typename T>
std::string Log::stringer(T const & t){
    return boost::lexical_cast<std::string>(t);
}

template<typename T, typename ... Args>
std::string Log::stringer(T const & t, Args const & ... args){
    return stringer(t) + stringer(args...);
}

 template<typename T, typename ... Args>
 void Log::enqueue(T & t, Args & ... args){
     boost::function<std::string()> f 
        = boost::bind(&Log::stringer<T &, Args & ...>,this,
                      boost::ref(t),
                      boost::forward<Args>(args)...);
 /// the above statement fails to compile though if i use 'auto f' it works ->
 /// but then it is unclear to me what the signature of f really is ?                              

 // at this point i would like to post the functor f onto my asio::io_service, 
 // but not able to cause it's not clear to me what the type of f is.
 // I think it should be of type boost::function<std::string()>

 }

在main()中,我调用

  Log t_log;
  t_log.enqueue("hello"," world");

1 个答案:

答案 0 :(得分:5)

我建议你提出的功能:

template <typename T, typename... Args> void enqueue(T &t, Args const&... args) {
    this->io_service->post([=]{ 
                auto s = stringer(t, args...);
                //std::fprintf(stderr, "%s\n", s.c_str()); 
            });
}

这适用于GCC和Clang(GCC 4.9或更高版本,因为捕获的可变参数包存在已知问题)。

但实际上,我重新考虑手头的设计,并且在你知道哪些领域需要进一步优化之前,肯定会开始很多

Questionables

我对这段代码有很多不了解的事情:

  • 为什么非const引用
  • 采用了这些参数
  • 你为什么随后在他们身上使用std::forward<>(你现在已经是价值类别了,而且它不会改变)
  • 为什么要将字符串化传递给io_service

    • 队列将引入锁定(反驳lockfree队列的种类)和
    • 字符串化将忽略其结果......
  • 为什么在这里使用boost::function?这导致(另一个)动态分配和间接调度......只需发布f

  • 为什么参数首先受引用约束?如果您要在不同的线程上处理参数,则会导致未定义的行为。例如。想象一下来电者在做什么

    char const msg[] = "my message"; // perhaps some sprintf output
    l.enqueue(cat.c_str(), msg);
    

    c_str()enqueue返回并且msg很快超出范围,或被其他数据覆盖后失效。

  • 为什么在明确获得bind支持时使用c++11方法(因为您使用了std::forward<>和属性)?

  • 为什么使用无锁队列(预计会不断记录最大CPU?在这种情况下,日志记录是您应用程序的核心功能,您可能应该考虑一下(很多)严格(例如写入预先分配的交替缓冲区并决定最大积压等)。

    在所有其他情况下,您可能希望在无锁队列上运行最多1个单线程。这可能已经过度杀戮(旋转线程不断昂贵)。相反,如果没有任何事情可以进行n次循环,你可以优雅地回退到产量/同步。

  • 您可以绑定到shared_ptr。与<{1}}

    绑定相比,批次更安全,更方便
      

    在我的下面的示例中,我通过不从堆中分配所有内容来删除对.get()的需求(为什么会这样?)。 (如果您需要工作,可以使用scoped_ptr。)

  • 明确的内存订单加载/存储也给我带来了不好的感受。只有当标志中涉及两个线程时,它们的写入方式才有意义,但目前这对我来说并不明显(线程是全部创建的)。

    在大多数平台上都没有区别,鉴于上述情况,显式内存排序的存在突出了明显的代码气味

  • 同样的事情适用于强行内联某些功能的尝试。你可以信任你的编译器,你可能应该避免再次猜测它,直到你知道你有一个由次优生成代码引起的瓶颈

  • 由于您打算为线程提供线程关联,执行使用线程本地。在C ++ 03(__thread)中使用GCC / MSVC扩展或使用c ++ 11 boost::optional<work>,例如在thread_local

    pop()

    这极大地减少了分配数量(以使pop()不可重入为代价,这不是必需的。

      

    我后来注意到这个thread_local std::string s; s.reserve(1000); s.resize(0); 仅限于一个帖子

  • 如果你所做的就是拥有那个无锁队列有什么用?手动围绕它旋转锁?

    pop()

清理建议

<强> Live On Coliru

void push(std::string const &s) {
    while (std::atomic_flag_test_and_set_explicit(&this->lock, std::memory_order_acquire))
        ;
    while (!this->q->push(s))
        ;
    std::atomic_flag_clear_explicit(&this->lock, std::memory_order_release);
}

正如您所看到的,我已将代码减少了三分之一。我已经记录了这样一个事实,即它只能安全​​地从单个线程中使用。

阿西奥走了。词汇演员也不见了。事情有意义的名字。没有更多的内存订单摆弄。没有更多的线程亲和力摆弄。不再有内联嫉妒。不再需要繁琐的字符串分配。

您可能从中获益最多的是

  • 将array_sinks / buffers合并并通过引用
  • 存储在队列中
  • 每个日志都不会刷新