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

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

在基于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()相似函数参数。


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来中转。比如,我们有一个函数:


void foo(int a, int b, std::string s);


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


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,却可以不用这么麻烦,它可以直接:


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以上


代码不多直接看代码:


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函数,就是能直接把参数打散来调用。比如这样:


bthread_t th;
    call_bthread(th, NULL, echo, "hello brpc");
    bthread_join(th, NULL);


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


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


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


直接看代码:


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任务啦!


Bthread bt(NULL, echo, "hello brpc");
    bt.join();


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


我手头其实只有4.8.5和10.2的gcc编译器,我10.2的gcc编译是没问题的。这里之所以说gcc版本8以上就能支持,是我在 https://godbolt.org 上给pthread_create做类似封装,测试得出的结论,如果大家实际测试8.1或10.2以下版本编译器编译不过的话,欢迎反馈。

相关文章
|
6月前
|
C++
C++中的封装、继承与多态:深入理解与应用
C++中的封装、继承与多态:深入理解与应用
150 1
|
5月前
|
缓存 网络协议 Linux
c++实战篇(三) ——对socket通讯服务端与客户端的封装
c++实战篇(三) ——对socket通讯服务端与客户端的封装
118 0
|
2月前
|
数据安全/隐私保护 C语言 C++
C++(七)封装
本文档详细介绍了C++封装的概念及其应用。封装通过权限控制对外提供接口并隐藏内部数据,增强代码的安全性和可维护性。文档首先解释了`class`中的权限修饰符(`public`、`private`、`protected`)的作用,并通过示例展示了如何使用封装实现栈结构。接着介绍了构造器和析构器的使用方法,包括初始化列表的引入以及它们在内存管理和对象生命周期中的重要性。最后,通过分文件编程的方式展示了如何将类定义和实现分离,提高代码的模块化和复用性。
|
4月前
|
C++ 容器
【C++】map和set封装
【C++】map和set封装
38 2
|
4月前
|
存储 开发框架 Java
|
5月前
|
存储 Java C#
C++语言模板类对原生指针的封装与模拟
C++|智能指针的智能性和指针性:模板类对原生指针的封装与模拟
|
5月前
|
数据安全/隐私保护 C++
C++语言深入理解类的封装与数据隐藏
深入理解类的封装与数据隐藏
|
4月前
|
存储 C++ 容器
【C++】开散列实现unordered_map与unordered_set的封装
【C++】开散列实现unordered_map与unordered_set的封装
52 0
|
5月前
|
C++
【c++】map和set的封装
【c++】map和set的封装
42 0
|
5月前
|
存储 安全 编译器
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
44 0