之前已经看过两轮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调用流程大概就是这样。