可变增强绑定类型分辨率

Adi*_*hag 4 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()>

 }
Run Code Online (Sandbox Code Playgroud)

在main()里面,我打电话

  Log t_log;
  t_log.enqueue("hello"," world");
Run Code Online (Sandbox Code Playgroud)

seh*_*ehe 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()); 
            });
}
Run Code Online (Sandbox Code Playgroud)

这适用于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);
    
    Run Code Online (Sandbox Code Playgroud)

    c_str()之后的是陈旧的enqueue返回,msg超出范围很快,或者被其他数据覆盖.

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

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

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

  • 你可以绑定到shared_ptr.这是一个很多比结合,更安全,更方便.get()

    在我的下面的示例中,我刚刚scoped_ptr从堆中分配了所有内容,从而删除了对s 的需求(为什么会这样?).(boost::optional<work>如果你需要工作,你可以使用.)

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

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

  • 同样的事情适用于强行内联某些功能的尝试.您可以信任您的编译器,在您知道由于次优生成的代码导致瓶颈之前,您应该避免再次猜测它.

  • 由于您打算为线程提供线程亲和性,使用线程本地.在C++ 03(__thread)中使用GCC/MSVC扩展或使用c ++ 11 thread_local,例如在pop()

    thread_local std::string s;
    s.reserve(1000);
    s.resize(0);
    
    Run Code Online (Sandbox Code Playgroud)

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

    我后来注意到这pop()仅限于一个线程

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

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

清理建议

Live On Coliru

#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/atomic.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/thread/thread.hpp>

/*
 * safe for use from a single thread only
 */
template <unsigned line_maxchars = 1000>
class Log {
  public:
    Log(std::string const &logFileName, int32_t queueSize)
      : fp(stderr), // std::fopen(logFileName.c_str(),"w")
        _shutdown(false),
        _thread(&Log::pop, this),
        _queue(queueSize)
    { }

    void pop() {
        std::string s;
        s.reserve(line_maxchars);

        struct timeval ts;
        while (!_shutdown) {
            while (_queue.pop(s)) {
                gettimeofday(&ts, NULL);
                std::fprintf(fp, "%li.%06li %s\n", ts.tv_sec, ts.tv_usec, s.c_str());
            }
            std::fflush(fp); // RECONSIDER HERE?
        }

        while (_queue.pop(s)) {
            gettimeofday(&ts, NULL);
            std::fprintf(fp, "%li.%06li %s\n", ts.tv_sec, ts.tv_usec, s.c_str());
        }
    }

    template <typename S, typename T> void stringer(S& stream, T const &t) {
        stream << t;
    }

    template <typename S, typename T, typename... Args>
    void stringer(S& stream, T const &t, Args const &... args) {
        stringer(stream, t);
        stringer(stream, args...);
    }

    template <typename T, typename... Args> void enqueue(T &t, Args const&... args) {
        thread_local char buffer[line_maxchars] = {};
        boost::iostreams::array_sink as(buffer);
        boost::iostreams::stream<boost::iostreams::array_sink> stream(as);

        stringer(stream, t, args...);

        auto output = as.output_sequence();
        push(std::string(output.first, output.second));
    }

    void push(std::string const &s) {
        while (!_queue.push(s));
    }

    ~Log() {
        _shutdown = true;
        _thread.join();

        assert(_queue.empty());
        std::fflush(fp);
        std::fclose(fp);

        fp = NULL;
    }

  private:
    FILE *fp;
    boost::atomic_bool _shutdown;

    boost::thread _thread;
    boost::lockfree::spsc_queue<std::string> _queue;
};

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    auto start = high_resolution_clock::now();

    {
        Log<> l("/tmp/junk.log", 1024);

        for (int64_t i = 0; i < 10; ++i) {
            l.enqueue("hello ", i, " world");
        }
    }

    std::cout << duration_cast<microseconds>(high_resolution_clock::now() - start).count() << "?s\n";
}
Run Code Online (Sandbox Code Playgroud)

如您所见,我已将代码减少了三分之一.我已经记录了这样一个事实,即从单个线程中使用它是安全的.

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

你可能从中获益最多的是

  • 使array_sinks/buffers合并并通过引用存储在队列中
  • 没有在每个日志上刷新