gpt4 book ai didi

c++ - 可变参数增强绑定(bind)类型解析

转载 作者:塔克拉玛干 更新时间:2023-11-03 01:44:27 25 4
gpt4 key购买 nike

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

我被困在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");

最佳答案

我对您所问功能的建议:

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或更高版本,因为捕获的可变参数包存在已知问题)。

但是,实际上,我会重新考虑手头的设计,并且在您知道哪些区域需要进一步优化之前,当然可以更简单地开始。

有问题的

我对这段代码有很多不了解的地方:
  • 为什么非常量引用
  • 接受参数
  • 您随后为何在它们上使用std::forward<>(您现在已经是值类别,并且不会更改)
  • 为什么将字符串化传递给io_service
  • 队列将引入锁定(有点类似于无锁队列)和
  • 字符串化的结果将被忽略...
  • 您为什么在这里使用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个单线程。这可能已经过大了(不断旋转线程很昂贵)。相反,如果无事可做,则可以从容地回退到 yield /同步。
  • 您可以仅绑定(bind)到shared_ptr。与绑定(bind).get()相比,这是很多更安全,更方便

    In my sample below I've just removed the need for scoped_ptrs by not allocating everything from the heap (why was that?). (You can use boost::optional<work> if you needed work.)

  • 显式的内存顺序加载/存储也使我感到不适。仅当标志中恰好涉及两个线程时,它们的编写方式才有意义,但这对我而言目前还不是显而易见的(线程是在周围创建的)。

    在大多数平台上,两者之间没有任何区别,并且鉴于上述情况,显式内存顺序的出现以清晰的代码气味
  • 突出
  • 这同样适用于强制内联某些功能的尝试。您可以信任您的编译器,并且您可能应该避免第二次猜测它,直到您知道由于生成的次优代码
  • 造成瓶颈
  • 由于您打算赋予线程线程亲和性,因此使用线程本地变量。在C++ 03(__thread)中使用GCC / MSVC扩展名,或者使用c++ 11 thread_local,例如用pop()
    thread_local std::string s;
    s.reserve(1000);
    s.resize(0);

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

    I later noticed this pop() is limited to a single thread

  • 如果您只需要...在周围手动旋转锁,那么拥有该无锁队列有什么用?
    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);
    }


  • 清理建议

    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";
    }

    如您所见,我将代码减少了三分之一。我已经记录了一个事实,那就是它只能在单个线程中安全使用。

    阿西欧不见了。词法转换不见了。事物具有有意义的名称。没有更多的内存顺序摆弄。不再需要线程相似性摆弄。没有更多的内联嫉妒。不再需要繁琐的字符串分配。

    您可能会从中最大受益的是
  • 通过引用
  • 使array_sinks / buffers池化并存储在队列中
  • 不会在每个日志上刷新
  • 关于c++ - 可变参数增强绑定(bind)类型解析,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28254996/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com