RocksDB——线程管理
调用入口:
env::Schedule() -> ThreadPoolImpl::Schedule()
env初始化的时候会构造两个线程池(ThreadPoolImpl),并分别赋予LOW和HIGH的优先度
ThreadPool Interface
源文件:include/rocksdb/threadpool.h
Interface Defination | Function |
---|---|
void JoinAllThreads() |
等待所有线程完成并丢弃没有开始执行的线程 |
void SetBackgroundThreads(int num) |
设置线程池的线程数 |
int GetBackgroundThreads() |
获取线程池的线程数 |
unsigned int GetQueueLen() |
获取线程池中队列中被schedule的任务数 |
void WaitForJobsAndJoinAllThreads() |
等待所有的任务完成 |
void SubmitJob(const std::function<void()>&) |
|
void SubmitJob(std::function<void()>&&) |
|
ThreadPool* NewThreadPool(int num_threads) |
共外部调用创建一个线程池 |
ThreadPoolImpl
源文件:util/threadpool_imp.h, util/threadpool_imp.cc
ThreadPoolImpl继承自定义接口的ThreadPool基本类,同时增加了一些新的接口,但是ThreadPoolImpl并不直接实现各个函数的功能,而是由其包含的Impl结构体再去具体定义每个功能的实现。
Additional Interface
Interaface Defination | Function |
---|---|
void LowerIOPriority() |
使线程运行在一个低内核IO优先级下 |
void LowerCPUPriority() |
使线程运行在一个低内核CPU优先级下 |
void IncBackgroundThreadsIfNeeded(int num) |
保证线程池中至少有num个线程 |
void Schedule(void (*function)(void* arg1), void* arg, void* tag, void (*unschedFunction)(void* arg)) |
安排一个任务,unscheduleFunction作用是取消一个已经安排但是还未执行的任务 |
int UnSchedule(void* tag) |
将指定任务从队列中移除 |
void SetHostEnv(Env* env) |
|
Env* GetHostEnv() |
|
Env::Priority GetThreadPriority() |
返回线程的优先度 |
void SetThreadPriority(Env::Priority priority) |
设置线程的优先度 |
Impl的实现
源文件:util/threadpool_imp.cc
ThreadPoolImpl的真正实现是封装到Impl结构中的,所以主要接口也和ThreadPoolImpl基本一致,数据成员增加了不少状态相关的变量,除了几个表示线程状态的bool类型,其余包含存放被schedule的任务的队列queue_,实现上使用的是std::deque;信号量bgsignal_,使用了std::condition_variable;所有的线程存在一个名为bgthreads_的vector中。线程由port封装
任务定义为BGItem(与LevelDB一致),定义为:
struct BGItem {
void* tag = nullptr;
std::function<void()> function;
std::function<void()> unschedFunction;
};
每个BGItem代表一个schedule的任务,BGItem保存在queue_中
初始化
Impl初始化的priority为LOW
如何schedule一个任务
首先由外部调用ThreadPoolImpl的Schedule接口,传入任务执行函数,对应参数,unschedFunction以及tag,ThreadPoolImpl::Schedule对这些参数进行封装然后调用Impl的Submit函数提交任务。当unschedFunction为空的时候,Schedule会传入一个空的std::Function
Impl接收到被提交的任务之后使用传入的参数构造BGItem,并添加到queue_中。
每次进入Submit函数都会首先调用一次StartBGThreads,StartBGThread中会循环向bgthreads_中添加thread直到达到total_threads_limit_(所以应该是首次调用才有效,后面调用也许会直接退出)
while ((int)bgthreads_.size() < total_threads_limit_) {
port::Thread p_t(&BGThreadWrapper,
new BGThreadMetadata(this, bgthreads_.size()));
bgthreads_.push_back(std::move(p_t));
}
添加完BGItem之后根据当前bgthreads_中的线程数,如果没有超过线程数限制(total_threads_limit_)则只唤醒一个线程,否则需要唤醒所有线程(这样多余的线程才能被detach掉)
任务是怎样开始执行的
在StartBGThreads函数构造BGItem的过程中,会传入BGThreadWrapper函数
BGThreadWrapper函数在完成一系列关于thread属性的设置之后调用BGThread函数,传入参数thread_id
BGThread函数应该是整个线程的主体,函数包含一个while-true循环不停地执行从queue中取出任务并执行的过程
BGThread主体:
如果没有exit_all_threads_信号,不是最后一个超过限制的线程,以及满足任务队列为空或者当前线程超过了线程数量限制的线程的时候,等待
while (!exit_all_threads_ && !IsLastExcessiveThread(thread_id) &&
(queue_.empty() || IsExcessiveThread(thread_id))) {
bgsignal_.wait(lock);
}
如果收到了exit_all_thread信号,在不需要等待全部任务完成的时候直接退出或者当任务队列清空之后再退出
if (exit_all_threads_) { // mechanism to let BG threads exit safely
if(!wait_for_jobs_to_complete_ || queue_.empty()) {
break;
}
}
如果当前线程是最新生成的一个超出数量限制的线程,则将该线程从bgthreads_中pop出来并detach,如果此时仍旧有超出限制的线程数,则唤醒所有的线程重复该过程(超出部分的线程就会被detach掉,以此来维护整个线程池中线程数量的稳定)
if (IsLastExcessiveThread(thread_id)) {
// Current thread is the last generated one and is excessive.
// We always terminate excessive thread in the reverse order of
// generation time.
auto& terminating_thread = bgthreads_.back();
terminating_thread.detach();
bgthreads_.pop_back();
if (HasExcessiveThread()) {
// There is still at least more excessive thread to terminate.
WakeUpAllThreads();
}
break;
}
获取BGItem中的function,并且调用这个function
网友评论