bthread代码跟读

之前已经看过两轮bthread源码了。相对而言,coroutine更像是一个简单的学习工具。

从提供的demo开始

static void* sender(void* arg) {
}

int main(int argc, char* argv[]) {
```
//根据命令行参数FLAGS_use_bthread的值,选择使用pthread还是bthread来创建线程。每个线程都会调用sender函数,并将通道对象作为参数传递
    if (!FLAGS_use_bthread) {
        pids.resize(FLAGS_thread_num);
        for (int i = 0; i < FLAGS_thread_num; ++i) {
            if (pthread_create(&pids[i], NULL, sender, &channel) != 0) {
                LOG(ERROR) << "Fail to create pthread";
                return -1;
            }
        }
    } else {
        bids.resize(FLAGS_thread_num);
        for (int i = 0; i < FLAGS_thread_num; ++i) {
            if (bthread_start_background(
                    &bids[i], NULL, sender, &channel) != 0) {
                LOG(ERROR) << "Fail to create bthread";
                return -1;
            }
        }
    }
```
    for (int i = 0; i < FLAGS_thread_num; ++i) {	//等待线程退出
        if (!FLAGS_use_bthread) {
            pthread_join(pids[i], NULL);
        } else {
            bthread_join(bids[i], NULL);
        }
    }

    return 0;
}

在这个客户端的示例中使用了多线程发起请求。FLAGS_use_bthread为true就会调用bthread_start_background(),默认使用系统调用pthread_create。

int bthread_start_background(bthread_t* __restrict tid,
                             const bthread_attr_t* __restrict attr,
                             void * (*fn)(void*),
                             void* __restrict arg) {
    bthread::TaskGroup* g = bthread::tls_task_group;
    //获取当前线程的TaskGroup指针,并存储到g中。
    if (g) {
        // start from worker
        return g->start_background<false>(tid, attr, fn, arg);
        //如果当前线程属于一个taskgroup,则启动一个后台线程
    }
    return bthread::start_from_non_worker(tid, attr, fn, arg);
}

调用bthread_start_background创建任务,如果能获取到当前线程的tls_task_ground(即关联的TaskGroup,在worker_thread创建TaskGroup时关联),就执行start_background。第一次进来相关组件都没有创建,所以先执行的bthread::start_from_non_worker(tid, attr, fn, arg)。这里参数tid还只是一个将来存放线程id的指针、attr为NULL、fn是任务函数指针sender、arg是channel对象地址。

#define BUTIL_FORCE_INLINE    __forceinline	//内联函数关键字
BUTIL_FORCE_INLINE int
start_from_non_worker(bthread_t* __restrict tid,
                      const bthread_attr_t* __restrict attr,
                      void * (*fn)(void*),
                      void* __restrict arg) {
    TaskControl* c = get_or_new_task_control();//获取taskcontrol对象指针
    if (NULL == c) {
        return ENOMEM;
    }
    if (attr != NULL && (attr->flags & BTHREAD_NOSIGNAL)) {
        TaskGroup* g = tls_task_group_nosignal;
        if (NULL == g) {
            //return _groups[butil::fast_rand_less_than(ngroup)];
            //task_group指针数组中的一个TaskGroup指针
            g = c->choose_one_group();
            tls_task_group_nosignal = g;
        }
        return g->start_background<true>(tid, attr, fn, arg);
    }
    return c->choose_one_group()->start_background<true>(
        tid, attr, fn, arg);
}

函数进来就先执行get_or_new_task_control()创建TaskControl对象,获取返回指针赋值给c。

inline TaskControl* get_or_new_task_control() {
    // 获取指向TaskControl*的原子指针
    butil::atomic<TaskControl*>* p = (butil::atomic<TaskControl*>*)&g_task_control;
    // 加载TaskControl*指针的值
    TaskControl* c = p->load(butil::memory_order_consume);
    if (c != NULL) {
        // 如果指针不为空,则返回该指针
        return c;
    }
    // 加锁,保证线程安全
    BAIDU_SCOPED_LOCK(g_task_control_mutex);
    //如果c为空,函数会尝试获取g_task_control_mutex锁,这是一个互斥锁,
    //用于保护多线程环境下的数据一致性。
    // 在获取锁之后,再次尝试从原子指针中加载TaskControl的指针。
    //这是为了处理可能的并发情况,确保在获取锁之后,TaskControl对象没有被其他线程创建。
    c = p->load(butil::memory_order_consume);
    if (c != NULL) {
        // 如果指针不为空,则返回该指针
        return c;
    }
    // 使用std::nothrow分配内存创建TaskControl对象,失败不抛出异常,后面自己判断
    c = new (std::nothrow) TaskControl;
    if (NULL == c) {
        // 如果内存分配失败,则返回NULL
        return NULL;
    }
    // 根据全局标志FLAGS_bthread_min_concurrency和FLAGS_bthread_concurrency来确定并发级别,
    //并尝试初始化TaskControl对象。如果初始化失败,则记录错误日志,释放对象,并返回NULL
    int concurrency = FLAGS_bthread_min_concurrency > 0 ?
        FLAGS_bthread_min_concurrency :
        FLAGS_bthread_concurrency;
    if (c->init(concurrency) != 0) {
        LOG(ERROR) << "Fail to init g_task_control";
        delete c;
        return NULL;
    }
    // 将TaskControl对象的指针存储到原子指针中
    p->store(c, butil::memory_order_release);
    // 返回TaskControl对象的指针
    return c;
}

g_task_control为全局的TaskControl指针,先将其转换成原子指针p,确保在多线程环境对其安全访问。然后赋值给普通指针c方便函数内部操作,这种分离允许函数在保持线程安全的同时,也能在单个线程的执行上下文中高效地进行操作。

此时TaskControl还没创建,c还是无效指针,所以直接到new一个TaskControl对象,TaskControl构造函数除了一下参数初始化,主体部分就是检查_groups组创建是否成功。

TaskControl::TaskControl()
    : _ngroup(0)// 初始化成员变量,所有字段必须在vars之前初始化
    // 使用calloc为TaskGroup指针数组分配内存,并初始化为0
    , _groups((TaskGroup**)calloc(BTHREAD_MAX_CONCURRENCY, sizeof(TaskGroup*)))
    , _stop(false)// 停止标志,初始化为false
    , _concurrency(0)// 并发数,初始化为0
    , _nworkers("bthread_worker_count")// 读取配置文件中bthread_worker_count的值
    , _pending_time(NULL)// 延迟暴露以下两个变量,因为它们依赖于尚未初始化的TC
    // 从当前TaskControl对象获取累计worker时间
    // 使用函数指针和当前对象指针作为参数进行初始化
    , _cumulated_worker_time(get_cumulated_worker_time_from_this, this)
    , _worker_usage_second(&_cumulated_worker_time, 1)// 计算worker使用率的对象,传入累计worker时间对象和间隔秒数
    // 从当前TaskControl对象获取累计切换次数
    // 使用函数指针和当前对象指针作为参数进行初始化
    , _cumulated_switch_count(get_cumulated_switch_count_from_this, this)
    , _switch_per_second(&_cumulated_switch_count)// 计算每秒切换次数的对象,传入累计切换次数对象
    // 从当前TaskControl对象获取累计信号次数
    // 使用函数指针和当前对象指针作为参数进行初始化
    , _cumulated_signal_count(get_cumulated_signal_count_from_this, this)
    , _signal_per_second(&_cumulated_signal_count)// 计算每秒信号次数的对象,传入累计信号次数对象
    , _status(print_rq_sizes_in_the_tc, this)// 打印TaskControl对象请求队列大小的函数对象,传入当前TaskControl对象
    , _nbthreads("bthread_count")// 读取配置文件中bthread_count的值
{
    // calloc分配的内存会自动初始化为0
    CHECK(_groups) << "Fail to create array of groups";
}

TaskControl创建完成后回到get_or_new_task_control,下一步就是对其初始化init(concurrency),传入参数代表并发级别,这里是一个预定义的bthread_min_concurrency,如果按默认值0,延迟创建工作线程行为就被禁止。在三目运算符中会bthread_concurrency(=8 + BTHREAD_EPOLL_THREAD_NUM=8+1)传入。

int TaskControl::init(int concurrency) {
    if (_concurrency != 0) {
        LOG(ERROR) << "Already initialized";
        return -1;
    }
    if (concurrency <= 0) {
        LOG(ERROR) << "Invalid concurrency=" << concurrency;
        return -1;
    }
    _concurrency = concurrency;
    // 确保TimerThread已就绪
    if (get_or_create_global_timer_thread() == NULL) {
        LOG(ERROR) << "Fail to get global_timer_thread";
        return -1;
    }
    // 根据并发级别concurrency调整_workers数组大小,vector存放线程id
    _workers.resize(_concurrency);
    for (int i = 0; i < _concurrency; ++i) {
        // 创建工作线程
        const int rc = pthread_create(&_workers[i], NULL, worker_thread, this);
        if (rc) {
            LOG(ERROR) << "Fail to create _workers[" << i << "], " << berror(rc);
            return -1;
        }
    }
    // 暴露指标
    _worker_usage_second.expose("bthread_worker_usage");
    _switch_per_second.expose("bthread_switch_second");
    _signal_per_second.expose("bthread_signal_second");
    _status.expose("bthread_group_status");

    // 等待至少有一个组被添加,以确保choose_one_group()不会返回NULL
    // TODO: 处理在add_group之前工作线程退出的情况
    while (_ngroup == 0) {
        usleep(100);  // TODO: 详细说明
    }
    return 0;
}

_concurrency参数在TaskControl构造函数中列表初始化为0。TaskControl是全局唯一对象,init也只会在get_or_new_task_control函数执行到TaskControl创建后执行一次,后续因为TaskControl已存在获取到对象地址就会返回。

pthread_create(&_workers[i], NULL, worker_thread, this)循环创建系统线程执行worker_thread

void* TaskControl::worker_thread(void* arg) {
    // 运行工作线程启动函数
    run_worker_startfn();//{if (g_worker_startfn) {g_worker_startfn();}}
#ifdef BAIDU_INTERNAL
    // 初始化日志组件
    logging::ComlogInitializer comlog_initializer;
#endif

    // 将参数转换为TaskControl指针
    TaskControl* c = static_cast<TaskControl*>(arg);
    // 创建TaskGroup同时初始化
    TaskGroup* g = c->create_group();
    // 任务统计信息
    TaskStatistics stat;
    if (NULL == g) {
        // 创建任务组失败,打印错误信息
        LOG(ERROR) << "Fail to create TaskGroup in pthread=" << pthread_self();
        return NULL;
    }
    // 打印日志,输出创建的工作线程和主任务的bthread信息
    BT_VLOG << "Created worker=" << pthread_self()
            << " bthread=" << g->main_tid();

    // 将任务组赋值给线程局部存储变量
    tls_task_group = g;
    // 增加工作线程数量
    c->_nworkers << 1;
    // 运行主任务
    g->run_main_task();

    // 获取主任务的统计信息
    stat = g->main_stat();
    // 打印日志,输出销毁的工作线程和主任务的bthread信息,以及空闲时间和运行时间
    BT_VLOG << "Destroying worker=" << pthread_self() << " bthread="
            << g->main_tid() << " idle=" << stat.cputime_ns / 1000000.0
            << "ms uptime=" << g->current_uptime_ns() / 1000000.0 << "ms";
    // 将线程局部存储变量置为空
    tls_task_group = NULL;
    // 销毁任务组
    g->destroy_self();
    // 减少工作线程数量
    c->_nworkers << -1;
    return NULL;
}

worker_thread就是在没worker的时候创建worker。

TaskGroup* TaskControl::create_group() {
    // 创建一个新的TaskGroup对象
    TaskGroup* g = new (std::nothrow) TaskGroup(this);
    // 如果创建失败
    if (NULL == g) {
        // 打印致命错误日志
        LOG(FATAL) << "Fail to new TaskGroup";
        // 返回空指针
        return NULL;
    }
    // 初始化TaskGroup对象
    if (g->init(FLAGS_task_group_runqueue_capacity) != 0) {
        // 如果初始化失败
        LOG(ERROR) << "Fail to init TaskGroup";
        // 释放TaskGroup对象
        delete g;
        // 返回空指针
        return NULL;
    }
    // 将TaskGroup对象添加到任务控制类中
    if (_add_group(g) != 0) {
        // 如果添加失败
        // 释放TaskGroup对象
        delete g;
        // 返回空指针
        return NULL;
    }
    // 返回创建成功的TaskGroup对象
    return g;
}

g->init(FLAGS_task_group_runqueue_capacity),FLAGS_task_group_runqueue_capacity控制着TaskGroup中两个队列的大小。(在TaskControl中宏定义)

TaskGroup::init()中就是对成员赋值。(包括2个队列,一个主TaskMeta)

int TaskGroup::init(size_t runqueue_capacity) {
    if (_rq.init(runqueue_capacity) != 0) {
        LOG(FATAL) << "Fail to init _rq";
        return -1;
    }
    if (_remote_rq.init(runqueue_capacity / 2) != 0) {
        LOG(FATAL) << "Fail to init _remote_rq";
        return -1;
    }
    ContextualStack* stk = get_stack(STACK_TYPE_MAIN, NULL);
    if (NULL == stk) {
        LOG(FATAL) << "Fail to get main stack container";
        return -1;
    }
    butil::ResourceId<TaskMeta> slot;
    TaskMeta* m = butil::get_resource<TaskMeta>(&slot);
    if (NULL == m) {
        LOG(FATAL) << "Fail to get TaskMeta";
        return -1;
    }
    m->stop = false;
    m->interrupted = false;
    m->about_to_quit = false;
    m->fn = NULL;
    m->arg = NULL;
    m->local_storage = LOCAL_STORAGE_INIT;
    m->cpuwide_start_ns = butil::cpuwide_time_ns();
    m->stat = EMPTY_STAT;
    m->attr = BTHREAD_ATTR_TASKGROUP;
    m->tid = make_tid(*m->version_butex, slot);
    m->set_stack(stk);

    _cur_meta = m;
    _main_tid = m->tid;
    _main_stack = stk;
    _last_run_ns = butil::cpuwide_time_ns();
    return 0;
}

这里可以看到初始化两个队列后的操作都是跟主TaskMeta绑定相关的。

ContextualStack* stk = get_stack(STACK_TYPE_MAIN, NULL);这是第一次调用get_stack()的地方,看参数名就知道这是给主TaskMeta(bthread)创建的栈空间,另外在TaskGroup::ending_sched()中也有调用(所处条件是在下一个任务没有栈空间,且类型与当前任务栈空间不相同,为下个任务创建栈空间),先看get_stack怎么做的。

inline ContextualStack* get_stack(StackType type, void (*entry)(intptr_t)) {
    switch (type) {
    case STACK_TYPE_PTHREAD:
        return NULL;
    case STACK_TYPE_SMALL:
        return StackFactory<SmallStackClass>::get_stack(entry);
    case STACK_TYPE_NORMAL:
        return StackFactory<NormalStackClass>::get_stack(entry);
    case STACK_TYPE_LARGE:
        return StackFactory<LargeStackClass>::get_stack(entry);
    case STACK_TYPE_MAIN:
        return StackFactory<MainStackClass>::get_stack(entry);
    }
    return NULL;
}

首先是一个内联函数,根据栈类型不同,调用不同的工厂函数做实际的get_stack()操作。

这里的栈类型分四种:SmallStackClass 、 NormalStackClass 、 LargeStackClass 、 MainStackClass。

其中SmallStackClass 、 NormalStackClass 、 LargeStackClass 使用通用模板template struct StackFactory

MainStackClass使用特化模板template <> struct StackFactory

template <> struct StackFactory<MainStackClass> {
    static ContextualStack* get_stack(void (*)(intptr_t)) {
        ContextualStack* s = new (std::nothrow) ContextualStack;
        if (NULL == s) {
            return NULL;
        }
        s->context = NULL;
        s->stacktype = STACK_TYPE_MAIN;
        s->storage.zeroize();
        return s;
    }
    
    static void return_stack(ContextualStack* s) {
        delete s;
    }
};

这里给main bthread创建了一个ContextualStack对象后返回指针。

回到TaskGroup::init继续流程,从资源池中获取一个TaskMeta,并为其初始化,因为是主TaskMeta不执行其他任务,所以很多参数都是0或者NULL,有效参数就cpu时间、tid、stack。

相应的TaskGroup中的参数也做初始化:_cur_meta:存储主TaskMeta地址, _main_tid:主TaskMeta的tid, _main_stack = stk, _last_run_ns存储TaskMeta创建完成时间。

初始化完成后,就将TaskGroup添加到TaskControl控制中

int TaskControl::_add_group(TaskGroup* g) {
    if (__builtin_expect(NULL == g, 0)) {
        return -1;
    }
    // 加锁,确保线程安全
    std::unique_lock<butil::Mutex> mu(_modify_group_mutex);
    // 检查是否已停止
    if (_stop) {
        return -1;
    }
    // 获取当前任务组数量
    size_t ngroup = _ngroup.load(butil::memory_order_relaxed);
    // 如果任务组数量未达到最大并发数
    if (ngroup < (size_t)BTHREAD_MAX_CONCURRENCY) {
        // 将传入的TaskGroup添加到任务组数组中
        _groups[ngroup] = g;
        // 更新任务组数量
        _ngroup.store(ngroup + 1, butil::memory_order_release);
    }
    // 解锁
    mu.unlock();

    // 注释:参考_destroy_group中的注释
    // TODO:由于非工作线程不能有TaskGroup,这里不再需要
    // 发送信号给任务,通知有新的TaskGroup添加
    signal_task(65536);
    return 0;
}

主要操作就是将传入的TaskGroup地址写入TaskControl的_groups数组中,并更新 _ngroup值。signal_task是发出信号让控制器管理和调度worker,后面再说。

create_group执行完后返回TaskGroup地址到worker_thread,将TaskGroup赋值给线程局部变量tls_task_group

c->_nworkers << 1;这个代码应该是增加工作组数量。类型是bvar::Adder<int64_t>,应该是对<<做了重载,不是整型的左移,本身左移也不保存更新变量。

继续执行到run_main_task

void TaskGroup::run_main_task() {
    //获取cpu时间累计值记录到this的TaskGroup上
    bvar::PassiveStatus<double> cumulated_cputime(    
        get_cumulated_cputime_from_this, this);
    std::unique_ptr<bvar::PerSecond<bvar::PassiveStatus<double> > > usage_bvar;    //std::unique_ptr独占智能指针
    
    TaskGroup* dummy = this;
    bthread_t tid;
    //死循环执行wait_task来等待有效的任务,如果能等到任务,wait_task的出参tid(bthread_t类型)会记录这个任务的ID号。
    while (wait_task(&tid)) {    //这个tid在_remote_rq.pop会指向(T*)_items + _start
        TaskGroup::sched_to(&dummy, tid);    //拿到任务ID号tid后,执行sched_to函数来切换栈。
        DCHECK_EQ(this, dummy);
        DCHECK_EQ(_cur_meta->stack, _main_stack);
        if (_cur_meta->tid != _main_tid) {
            TaskGroup::task_runner(1/*skip remained*/);//当前tid不是main_tid就执行TaskGroup::task_runner,task_runner的入参不为0时会循环执行剩余任务
        }
        if (FLAGS_show_per_worker_usage_in_vars && !usage_bvar) {
            char name[32];
#if defined(OS_MACOSX)
            snprintf(name, sizeof(name), "bthread_worker_usage_%" PRIu64,
                     pthread_numeric_id());
#else
            snprintf(name, sizeof(name), "bthread_worker_usage_%ld",
                     (long)syscall(SYS_gettid));
#endif
            //创建一个新的usage_bvar对象,保存cumulated_cputime
            usage_bvar.reset(new bvar::PerSecond<bvar::PassiveStatus<double> >
                             (name, &cumulated_cputime, 1));
        }
    }
    // stop_main_task() was called.
    // Don't forget to add elapse of last wait_task.
    current_task()->stat.cputime_ns += butil::cpuwide_time_ns() - _last_run_ns;//这里将当前任务的 CPU 时间(纳秒)更新为自上次运行以来的 CPU 时间差
}

主任务函数就是死循环等待新任务到来后分配执行。到这启动阶段或者说准备工作完成了。

worker_thread函数是以多线程方式启动的,在worker创建到等待任务的时候,get_or_new_task_control在TaskControl创建并初始化后返回其地址。回到start_from_non_worker()函数继续执行。时间线上在TaskControl执行init的时候会循环等待至少一个工作组添加完成。

attr在调用时传入的是NULL,所以if (attr != NULL && (attr->flags & BTHREAD_NOSIGNAL))这个判断直接不看,到最后一个return表达式。return c->choose_one_group()->start_background(tid, attr, fn, arg);

TaskGroup* TaskControl::choose_one_group() {
    // 获取任务组数量
    const size_t ngroup = _ngroup.load(butil::memory_order_acquire);
    // 如果任务组数量不为0
    if (ngroup != 0) {
        // 随机选择一个任务组并返回
        return _groups[butil::fast_rand_less_than(ngroup)];
    }
    // 如果任务组数量为0,则触发断言错误
    CHECK(false) << "Impossible: ngroup is 0";
    // 返回空指针
    return NULL;
}

随机从_groups组中返回一个(前面add_group时将TaskGroup的地址加入到里面了),执行start_background

int TaskGroup::start_background(bthread_t* __restrict th,
                                const bthread_attr_t* __restrict attr,
                                void * (*fn)(void*),
                                void* __restrict arg) {
    // 检查函数指针是否为空,__builtin_expect是编译器的内建命令分支预测,返回!fn表达式的值,并给了一个预期值0,如果表达式值符合预期会提升性能,反之降低性能
    if (__builtin_expect(!fn, 0)) {
        return EINVAL;
    }
    // 获取当前时间的纳秒数
    const int64_t start_ns = butil::cpuwide_time_ns();

    // 使用传入的属性或默认属性
    const bthread_attr_t using_attr = (attr ? *attr : BTHREAD_ATTR_NORMAL);

    // 获取TaskMeta资源的槽位
    butil::ResourceId<TaskMeta> slot;
    // 获取TaskMeta资源
    TaskMeta* m = butil::get_resource(&slot);
    // 检查是否成功获取到TaskMeta资源
    if (__builtin_expect(!m, 0)) {
        return ENOMEM;
    }

    // 检查当前没有等待者
    CHECK(m->current_waiter.load(butil::memory_order_relaxed) == NULL);

    // 初始化TaskMeta对象的成员变量
    m->stop = false;
    m->interrupted = false;
    m->about_to_quit = false;
    m->fn = fn;
    m->arg = arg;
    CHECK(m->stack == NULL);
    m->attr = using_attr;
    m->local_storage = LOCAL_STORAGE_INIT;
    m->cpuwide_start_ns = start_ns;
    m->stat = EMPTY_STAT;
    m->tid = make_tid(*m->version_butex, slot);
    *th = m->tid;

    // 如果启用了日志记录,打印日志
    if (using_attr.flags & BTHREAD_LOG_START_AND_FINISH) {
        LOG(INFO) << "Started bthread " << m->tid;
    }

    // 线程数增加
    _control->_nbthreads << 1;

    // 根据是否远程执行,调用不同的函数准备线程运行
    if (REMOTE) {
        ready_to_run_remote(m->tid, (using_attr.flags & BTHREAD_NOSIGNAL));
    } else {
        ready_to_run(m->tid, (using_attr.flags & BTHREAD_NOSIGNAL));
    }
    // 返回成功
    return 0;
}

创建TaskMeta作为任务执行单元并初始化,然后判断是否在本地(当前worker)执行。分别对应push到_rq和_remote_rq后发送信号等待调度。一个bthread调用流程大概就是这样。

  • 25
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值