草庐IT

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

coder 2024-02-19 原文

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

我被困在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/

    有关c++ - 可变参数增强绑定(bind)类型解析的更多相关文章

    1. Ruby 解析字符串 - 2

      我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

    2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    3. ruby - 用逗号、双引号和编码解析 csv - 2

      我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

    4. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

      exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

    5. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    6. ruby - RSpec - 使用测试替身作为 block 参数 - 2

      我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

    7. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

      它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

    8. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

      我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

    9. ruby - Infinity 和 NaN 的类型是什么? - 2

      我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

    10. ruby - 检查方法参数的类型 - 2

      我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

    随机推荐