前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >把bthread_start_background封装成现代C++的风格!

把bthread_start_background封装成现代C++的风格!

作者头像
果冻虾仁
发布2021-12-08 13:32:11
9070
发布2021-12-08 13:32:11
举报
文章被收录于专栏:后台公论

在基于brpc开发服务的时候,bthread_start_background()一定是高频函数。bthread_start_background()是brpc框架提供给我们的API,让我们可以方便使用brpc的协程bthread

然而在brpc的设计思想中,bthread_start_background()需要和pthread_create()兼容,在某些情况下直接用pthread_create()来执行bthread的回调函数。所以bthread_start_background(是声明在extern "C"中。并且有和POSIX的C标准函数pthread_create()相似函数参数。

代码语言:javascript
复制
int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr,
                   void *(*start_routine) (void *), 
                   void *arg);
                   
int bthread_start_background(bthread_t* __restrict tid,
                             const bthread_attr_t* __restrict attr,
                             void * (*fn)(void*),
                             void* __restrict args);

它们两个的第三个参数,即线程/协程回调函数的类型是完全一样的。回调函数类型必须是参数为void*,返回值也为void*的函数。所以如果我们想执行的函数是多个参数的只能通过struct来中转。比如,我们有一个函数:

代码语言:javascript
复制
void foo(int a, int b, std::string s);

想要在bthread中执行,只能这样:

代码语言:javascript
复制
struct Args {
    int a;
    int b;
    std::string s;
};
void* call_back(void* ori_args) {
    Args* args = (Args*)ori_args;

    foo(args->a, args->b, args->s);

    delete args;
    return nullptr;
}
// 在需要调用的地方
...
    Args* args = new Args;
    // a、b、s是已有的int、int和string类型变量
    args->a = a;
    args->b = b;
    args->s = s;
    
    bthread_t id;
    bthread_start_background(&id, NULL, call_back, (void*)args);

类似上述代码,我和同事在工作中还出过几次bug,比如在回调函数中漏了delete(把delete操作放到了某个if条件中)或者写成了delete ori_args;从而导致了内存泄露。当然问题也不难排查,不过还是浪费时间,而且这个API用起来也不方便。

回想起C++11使用到std::thread,却可以不用这么麻烦,它可以直接:

代码语言:javascript
复制
    std::thread(foo, a, b, s);

并且foo可以是任意的callable类型,不仅是函数,还能是lambda,函数对象、std::bind的返回值等。

那么bthread能封装成类似的不经过void*中转的API么?

答案是

因为std::thread在Linux/Unix环境上其实也是对pthread的封装。其本质通过tuple的中转来实现的,所以我们的需求理论上也是能实现的,不过代码还是比较多,涉及到很多模板元编程的知识,过于复杂了。

可以看下编译器实现的std::thread的源码:

  • gcc源码: https://code.woboq.org/gcc/libstdc++-v3/include/std/thread.html#ZNSt6threadC1EOT_DpOT0
  • llvm源码: https://github.com/llvm-mirror/libcxx/blob/master/include/thread#L287

那有没有一种更为简洁的实现方案呢?

答案是

不过需要编译器的版本比较高,但gcc版本需要8以上

代码不多直接看代码:

代码语言:javascript
复制
template<class Fn, class... Args>
void call_bthread(bthread_t& th, const bthread_attr_t* attr, Fn&& fn, Args&&... args) {
    auto p_wrap_fn = new auto([=]{ fn(args...);   });
    auto call_back = [](void* ar) ->void* {
        auto f = reinterpret_cast<decltype(p_wrap_fn)>(ar);
        (*f)();
        delete f;
        return nullptr;
    };

    bthread_start_background(&th, attr, call_back, p_wrap_fn);
}

这里定义的call_bthread函数,就是能直接把参数打散来调用。比如这样:

代码语言:javascript
复制
    bthread_t th;
    call_bthread(th, NULL, echo, "hello brpc");
    bthread_join(th, NULL);

怎么样,是不是方便很多了呢?

也许你会问,std::thread可是一个类啊,你这里能不能封装成一个类呢?可以,下面我来演示一下。

由于bthread_start_background是需要接收属性参数的,而std::thread不需要,所以我实现的这个类会额外多一个属性参数,需要外部传入。另外第一个参数bthread_t类型的id,其实我们一般是不关心的,所以就不传入了。

直接看代码:

代码语言:javascript
复制
class Bthread {
public:
    template<class Fn, class... Args>
    Bthread(const bthread_attr_t* attr, Fn&& fn, Args&&... args) {
        auto p_wrap_fn = new auto([=]{ fn(args...);   });
        auto call_back = [](void* ar) ->void* {
            auto f = reinterpret_cast<decltype(p_wrap_fn)>(ar);
            (*f)();
            delete f;
            return nullptr;
        };

        bthread_start_background(&th_, attr, call_back, (void*)p_wrap_fn);
        joinable_ = true;
    }

    void join() {
        if (joinable_) {
            bthread_join(th_, NULL);
            joinable_ = false;
        }
    }

    bool joinable() const noexcept {
        return joinable_;
    }

    bthread_t get_id() {
        return th_;
    }
private:
    bthread_t th_;
    bool joinable_ = false;
};

这里我实现了一个类Bthread,里面实现了几个简单的接口,当然关于其他构造函数与运算符或支持或禁止等细节,这里没做考虑(不是本文重点)

有了这个Bthread类后,我们就可以这样创建bthread任务啦!

代码语言:javascript
复制
    Bthread bt(NULL, echo, "hello brpc");
    bt.join();

如果你工作的编译器版本是8.1及以上的话,完全可以把这段代码放到项目中,让你的brpc工具箱再添一员猛将!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程往事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档