從base/task_scheduler/task_traits.h中的枚舉量TaskShutdownBehavior可以看到罚舱,chromium針對投遞的task在瀏覽器退出時(shí)應(yīng)該表現(xiàn)的行為分為三類,CONTINUE_ON_SHUTDOWN皮迟、SKIP_ON_SHUTDOWN、BLOCK_SHUTDOWN存炮。值得一提的是base::SequencedWorkerPool::WorkerShutdown中同樣聲明了三個(gè)一樣的enum玻孟,一般我們在開發(fā)過程中應(yīng)該盡量避免這種同樣的常量在不同的位置定義兩次的行為,因?yàn)楹芸赡艽a在不同的人手上改著改著這兩份東西就不一樣了锤躁,這樣就埋了個(gè)深坑。如果實(shí)在萬不得已的情況下只能定義兩份,那就學(xué)習(xí)一下chromium的做法吧,用一個(gè)static_assert保證兩份定義的值是一樣的:
bool SequencedWorkerPool::Inner::PostTaskToTaskScheduler(...) {
...
static_assert(
static_cast<int>(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN) ==
static_cast<int>(CONTINUE_ON_SHUTDOWN),
"TaskShutdownBehavior and WorkerShutdown enum mismatch for "
"CONTINUE_ON_SHUTDOWN.");
...
然后先看一下這三種分類到底是幾個(gè)意思玩敏,其實(shí)代碼里已經(jīng)有了非常詳細(xì)的注釋。簡單來說大概就是這樣:
- CONTINUE_ON_SHUTDOWN類型的任務(wù)不會(huì)影響退出流程觉啊,它跟退出流程各走各的拣宏,所以可能任務(wù)執(zhí)行的時(shí)候退出流程已經(jīng)走得七七八八了沈贝,所以很多單例啊全局對象啊之類的已經(jīng)析構(gòu)了,這個(gè)要比較注意勋乾;
- SKIP_ON_SHUTDOWN類型的任務(wù)宋下,在開始退出流程前已經(jīng)在執(zhí)行的會(huì)繼續(xù)執(zhí)行且執(zhí)行時(shí)阻塞退出流程,否則會(huì)被忽略辑莫;
- BLOCK_SHUTDOWN類型的任務(wù)学歧,不論在退出流程開始前后投遞都會(huì)被執(zhí)行,且執(zhí)行時(shí)阻塞退出流程各吨。換句話說就是這些任務(wù)執(zhí)行完前退出流程會(huì)等在那里枝笨。
那么這個(gè)又是怎么實(shí)現(xiàn)的呢?
首先揭蜒,在向線程池投遞的每個(gè)任務(wù)都會(huì)由TaskTracker來跟蹤横浑,在正式加入線程池的任務(wù)隊(duì)列之前會(huì)經(jīng)過TaskTracker::WillPostTask,如果返回false則不會(huì)被加入隊(duì)列(是否已經(jīng)開始退出流程是在TaskTracker::State里面屉更,在一個(gè)int值的最低位存的徙融,估計(jì)是跟任務(wù)數(shù)量合在一個(gè)變量里面存有助于用原子操作優(yōu)化吧):
bool TaskTracker::WillPostTask(const Task* task) {
if (!BeforePostTask(task->traits.shutdown_behavior()))
return false;
……
bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) {
if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN) {
// BLOCK_SHUTDOWN tasks block shutdown between the moment they are posted
// and the moment they complete their execution.
const bool shutdown_started = state_->IncrementNumTasksBlockingShutdown();
if (shutdown_started) {
AutoSchedulerLock auto_lock(shutdown_lock_);
// A BLOCK_SHUTDOWN task posted after shutdown has completed is an
// ordering bug. This aims to catch those early.
DCHECK(shutdown_event_);
if (shutdown_event_->IsSignaled()) {
……
return false;
}
……
}
return true;
}
// A non BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't
// started.
return !state_->HasShutdownStarted();
}
可以看出,在開始退出流程之后瑰谜,除了BLOCK_SHUTDOWN類型的任務(wù)就不會(huì)再被加入任務(wù)隊(duì)列了欺冀;
同樣,在開始執(zhí)行任務(wù)之前也有類似的判斷萨脑,這里BeforeRunTask里的代碼就不貼了:
bool TaskTracker::RunTask(std::unique_ptr<Task> task,
const SequenceToken& sequence_token) {
DCHECK(task);
DCHECK(sequence_token.IsValid());
const TaskShutdownBehavior shutdown_behavior =
task->traits.shutdown_behavior();
const bool can_run_task = BeforeRunTask(shutdown_behavior);
const bool is_delayed = !task->delayed_run_time.is_null();
if (can_run_task) {
PerformRunTask(std::move(task), sequence_token);
AfterRunTask(shutdown_behavior);
}
...
最后隐轩,退出流程是在哪里進(jìn)行的呢?始于TaskTracker::PerformShutdown渤早,由于某些任務(wù)會(huì)阻塞退出流程龙助,在它們執(zhí)行完成之前需要等待,所以這里用了一個(gè)Event對象進(jìn)行同步:
void TaskTracker::PerformShutdown() {
{
AutoSchedulerLock auto_lock(shutdown_lock_);
shutdown_event_.reset(
new WaitableEvent(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED));
const bool tasks_are_blocking_shutdown = state_->StartShutdown();
if (!tasks_are_blocking_shutdown) {
shutdown_event_->Signal();
return;
}
}
// It is safe to access |shutdown_event_| without holding |lock_| because the
// pointer never changes after being set above.
{
base::ThreadRestrictions::ScopedAllowWait allow_wait;
shutdown_event_->Wait();
}
...
}
TaskTracker::State里用一個(gè)int值的最低位存放是否已經(jīng)開始退出流程,其他位存放阻塞類型任務(wù)的數(shù)量(對這個(gè)計(jì)數(shù)的維護(hù)可以在TaskTracker中查找IncrementNumTasksBlockingShutdown與DecrementNumTasksBlockingShutdown的調(diào)用)提鸟,在TaskTracker::StartShutdown里面會(huì)將退出狀態(tài)標(biāo)志位置位军援,并判斷目前是否有阻塞類型任務(wù)。如果沒有称勋,則可以直接結(jié)束退出流程胸哥;否則需要等待這些任務(wù)完成∩南剩可以看一下StartShutdown里面的實(shí)現(xiàn):
class TaskTracker::State {
// Sets a flag indicating that shutdown has started. Returns true if there are
// tasks blocking shutdown. Can only be called once.
bool StartShutdown() {
const auto new_value =
subtle::NoBarrier_AtomicIncrement(&bits_, kShutdownHasStartedMask);
// Check that the "shutdown has started" bit isn't zero. This would happen
// if it was incremented twice.
DCHECK(new_value & kShutdownHasStartedMask);
const auto num_tasks_blocking_shutdown =
new_value >> kNumTasksBlockingShutdownBitOffset;
return num_tasks_blocking_shutdown != 0;
}
…
}
那么如果需要等待其他任務(wù)完成的話空厌,最終這些任務(wù)執(zhí)行完后會(huì)在哪里重新喚醒退出流程呢?每個(gè)任務(wù)在執(zhí)行前會(huì)經(jīng)過TaskTracker::BeforeRunTask银酬,執(zhí)行完成后會(huì)經(jīng)過TaskTracker::AfterRunTask嘲更,在這兩個(gè)過程中都會(huì)對阻塞類型任務(wù)的數(shù)量與是否已經(jīng)開始退出流程進(jìn)行判斷,如果判斷成立則會(huì)喚醒原來的退出流程繼續(xù)執(zhí)行揩瞪,以AfterRunTask為例:
void TaskTracker::AfterRunTask(TaskShutdownBehavior shutdown_behavior) {
if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN ||
shutdown_behavior == TaskShutdownBehavior::SKIP_ON_SHUTDOWN) {
const bool shutdown_started_and_no_tasks_block_shutdown =
state_->DecrementNumTasksBlockingShutdown();
if (shutdown_started_and_no_tasks_block_shutdown)
OnBlockingShutdownTasksComplete();
}
}
void TaskTracker::OnBlockingShutdownTasksComplete() {
AutoSchedulerLock auto_lock(shutdown_lock_);
// This method can only be called after shutdown has started.
DCHECK(state_->HasShutdownStarted());
DCHECK(shutdown_event_);
shutdown_event_->Signal();
}