学习资料:链接
理解上还有一些混乱,限制可以实战看代码了
Chrome的线程和任务
大纲
Chrome拥有多进程架构以及每个进程都是一个庞大的多线程架构。在这篇文章,我们将浏览每个进程的基本线程共享系统。主要目标是为了保持main thread(UI thread)和IO thread(每个进程中处理IPC的线程)实现 快速反应。这意味着将原先线程中,会阻塞IO或者昂贵的操作,移动到新的线程中去。我们的方法是使用message passing做为线程交流数据的方式。我们不鼓励使用lock和thread-safe objects(估计是性能消耗巨大)。实际上,objects 只出现在一个thread(virtual thread)。本文假设读者熟悉threading conceptes
关于并发(Concurrency)和并行(parallelism)的解释
专业术语
核心概念
- Task:执行工作的单元。具有可选地关联状态的函数指针,在chrome中,可以通过base::BindOnce和base::BindRepeating来创建base::OnceCallback和base::RepeatingCallback
- Task queue:task队列,保存待处理的tasks
- Physical thread:操作系统提供的线程,不建议直接使用
- base::Thread:是一个物理线程,专门用来处理消息。我们将不会主动创建这个线程
- Thread pool:一池子的物理线程,每个线程拥有一个共享任务队列。在chrome中,每个process拥有一个实例
- Sequence or Virtual thread:由chrome管理的线程。像物理线程(physical thread)一样,一个task跑在一个thread上
- Task runner:post task的接口,在chrome中是base::TaskRunner
- Sequenced task runner(顺序任务执行器):保证按照先到先执行的顺序来执行任务,通常由单线程执行task(virtual或者physical)。在chrome中是base::SequencedTaskRunner,由base::TaskRunner派生而来
- Single-thread task runner:保证是单线程,在chrome中是base::SingleThreadTaskRunner,由base::SequencedTaskRunner派生而来,跑在physical thread。顺序执行线程比单线程执行更受欢迎
线程术语
读者注意:下面的术语是为了构建通常的thread terms和chrome中使用的不同。如果是刚起步会有点难以理解,考虑跳过细节的内容,在需要时,再返回阅读
- Thread-unsafe:在Chrome中大多数的线程类型都是 线程不安全(设计如此),使用此类型/方法,必须要在外部同步。线程不安全类型要求所有访问其状态的任务要被发送到同一个base::Sequenced task runner。锁也是一种同步访问的方法,当我们更偏爱Sequence而不是锁
- Thread-affine:这个类型/方法需要被相同的physical thread访问(即base::SingleThreadTaskRunner),并且拥有THREAD_CHECKR成员确认是否使用正确。在Chrome中没有理由使用该类型
- Thread-safe:这种类型/方法可以被安全的并发使用
- Thread-compatible:这个类型提供安全并行使用const methods,non-const需要sync。Chrome没有暴露读写锁,唯一的使用案例是在线程安全行为中的初始化使用
- Immutable:thread-compatible的子集
- Seqence-friendly:这个类型/方法 是线程不安全的类型,支持从base::SequencedTaskRunner调用。理想情况下所有thread-unsafe都是这个类型,但有些老代码,仅会在thread-unsafe的条件下,强制进行thread-affine检查
线程
每个Chrome Process都有
- 一个主线程
- 在browser process中:UI线程
- 在render process中:执行大多数blink code
- 一个I/O线程
- 在browser process中:处理IPC和network requests
- 在render process中:处理IPC
- 一些特殊目的的线程
- 一个通用线程池
大部分的线程有一个循环,从queue(也可能是multi-thread 共享的任务队列)获取task然后执行
Task
一个task存储了一个函数指针。task可以通过 parallel,Seqenced,Single thread这三种方式执行
void TaskA() {}
void TaskB(int v) {}
auto task_a = base::BindOnce(&TaskA);
auto task_b = base::BindOnce(&TaskB, 42);
prefer sequences to physical threads
除了绑定到主线程(UI)和IO线程 的类型和方法,Sequenced execution(virtual thread)比single thread execution(physical thread)要好
所有暴漏给 physical thread的API,在squence thread都有对应的API
有一些情况会出现seqence-friendly在thread-affinity check失败的情况,了解有这个现象就好
Posting a Parallel Task
直接丢到Thread Pool
一个task假如可以在任意thread执行,没有顺序/互斥需求,就可以通过base::ThreadPool::PostTask(FROM_HERE,base::BindOnce(&task))的方式执行
###通过TaskRunner执行
假如不知道当前task是被post到seqence或者是single-thread。因为TaskRunner是SequencedTaskRunner和SingleThreadTskRunner的基类,类比较通用
class A {
public:
A() = default;
void PostSomething() {
task_runner_->PostTask(FROM_HERE, base::BindOnce(&A, &DoSomething));
}
void DoSomething() {
}
private:
scoped_refptr<base::TaskRunner> task_runner_ =
base::ThreadPool::CreateTaskRunner({base::TaskPriority::USER_VISIBLE});
};
除非测试需要精确控制task的execute方式,否则推荐使用base::ThreadPool::PostTask
Posting a Sequenced Task
顺序执行task,但不一定在同一个线程上,base::SequencedTaskRunner
scoped_refptr<SequencedTaskRunner> sequenced_task_runner =
base::ThreadPool::CreateSequencedTaskRunner(...);
// TaskB runs after TaskA completes.
sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskA));
sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskB));
Posting to Current(Virtual) Thread
可以通过 base::SequencedTaskRunnerHandle::Get()返回当前virtual thread的默认队列。当线程有多队列的情况下,会有一些不同的逻辑处理
使用Sequence代替Lock
在Chrome中不鼓励使用Lock。因为Sequences 提供线程安全功能。相比于自己的方式实现类中的访问线程安全,在同一个队列访问该类会更好
class A {
public:
A() {
// Do not require accesses to be on the creation sequence.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
void AddValue(int v) {
// Check that all accesses are on the same sequence.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
values_.push_back(v);
}
private:
SEQUENCE_CHECKER(sequence_checker_);
// No lock required, because all accesses are on the
// same sequence.
std::vector<int> values_;
};
A a;
scoped_refptr<SequencedTaskRunner> task_runner_for_a = ...;
task_runner_for_a->PostTask(FROM_HERE,
base::BindOnce(&A::AddValue, base::Unretained(&a), 42));
task_runner_for_a->PostTask(FROM_HERE,
base::BindOnce(&A::AddValue, base::Unretained(&a), 27));
// Access from a different sequence causes a DCHECK failure.
scoped_refptr<SequencedTaskRunner> other_task_runner = ...;
other_task_runner->PostTask(FROM_HERE,
base::BindOnce(&A::AddValue, base::Unretained(&a), 1));
LOCK应当仅用于交换多线程的共享数据结构。如果一个线程有昂贵的计算或者昂贵的磁盘访问,不应该使用LOCK。仅当交换共享数据的性能是可接受的,才会使用
为了编写非阻塞代码,许多Chrome的APIs是异步的,意意味着task不需要执行在同一个thread上,只会在完成后返回一个callbacki(例如 base::OnceCallback<>或者是 base::RepeatingCallback<>)对象。上面的代码演示了如何只执行在一个thread/sequence上
Posting Multiple Tasks to the Same Thread
使用base::SingleThreadTaskRunner去保证multi task执行在同一个线程上。因为Seqenced Task只保证顺序以及一次只执行一个task,不保证都在一个thread上
Posting to the Main Thread or to the IO tThread in the Browser Process
通过content::GetUIThreadTaskRunner或者content::GetIOThreadTaskRunner post tasks到main thread(UI thread)和IO Thread上
base::PostTask(FROM_HERE, {content::BrowserThread::UI}, ...);
base::CreateSingleThreadTaskRunner({content::BrowserThread::IO})
->PostTask(FROM_HERE, ...);
main thread和io thread是很繁忙了,所以尽可能把task投到其他通用的线程上,除非真的有必要
在Renderer Process post到main thread
TODO ???
Post到自定义的SingleThreadTaskRunner
先创建一个,然后扔进去
scoped_refptr<SingleThreadTaskRunner> single_thread_task_runner =
base::Threadpool::CreateSingleThreadTaskRunner(...);
// TaskB runs after TaskA completes. Both tasks run on the same thread.
single_thread_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskA));
single_thread_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskB));
sequence/virutual-thread 比 single/physical-thread要好,所以上面的逻辑很少会这么用
Posting to the Current(physical) Thread
// The task will run on the current thread in the future.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&Task));
Posting Tasks to a COM Single-Thread Apartment(STA) Thread(Windows)
不太了解的一种线程类型,流程也是create,然后post
给Task添加属性
base::TskTraits封装了一些信息,这些信息可被用于thread pool 做调度决策
默认情况下属性是这样的
- 不可阻塞
- user-blocking 优先级
- 可以阻塞进程退出或者被完全跳过
// This task has no explicit TaskTraits. It cannot block. Its priority is
// USER_BLOCKING. It will either block shutdown or be skipped on shutdown.
base::ThreadPool::PostTask(FROM_HERE, base::BindOnce(...));
// This task has the highest priority. The thread pool will schedule it before
// USER_VISIBLE and BEST_EFFORT tasks.
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::USER_BLOCKING},
base::BindOnce(...));
// This task has the lowest priority and is allowed to block (e.g. it
// can read a file from disk).
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
base::BindOnce(...));
// This task blocks shutdown. The process won't exit before its
// execution is complete.
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(...));
保持浏览器相应
不要在IO thread 和 main thread上做昂贵的操作
同步(会阻塞用户输入)
// GetHistoryItemsFromDisk() may block for a long time.
// AddHistoryItemsToOmniboxDropDown() updates the UI and therefore must
// be called on the main thread.
AddHistoryItemsToOmniboxDropdown(GetHistoryItemsFromDisk("keyword"));
利用线程池异步(不会阻塞用户输入)
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&GetHistoryItemsFromDisk, "keyword"),
base::BindOnce(&AddHistoryItemsToOmniboxDropdown));
延迟提交任务
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT});
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&Task), base::TimeDelta::FromHours(1));
重复提交延迟任务
class A {
public:
~A() {
// The timer is stopped automatically when it is deleted.
}
void StartDoingStuff() {
timer_.Start(FROM_HERE, TimeDelta::FromSeconds(1),
this, &A::DoStuff);
}
void StopDoingStuff() {
timer_.Stop();
}
private:
void DoStuff() {
// This method is called every second on the sequence that invoked
// StartDoingStuff().
}
base::RepeatingTimer timer_;
};
取消任务
使用Using base::WeakPtr
int Compute() { … }
class A {
public:
void ComputeAndStore() {
// Schedule a call to Compute() in a thread pool followed by
// a call to A::Store() on the current sequence. The call to
// A::Store() is canceled when |weak_ptr_factory_| is destroyed.
// (guarantees that |this| will not be used-after-free).
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&Compute),
base::BindOnce(&A::Store, weak_ptr_factory_.GetWeakPtr()));
}
private:
void Store(int value) { value_ = value; }
int value_;
base::WeakPtrFactory<A> weak_ptr_factory_{this};
};
WeakPtr不是线程安全的,因此GetWeakPtr,~WeakPtrFactory,Store(),必须跑在同一个sequence tasker里
使用base::CancleableTaskTracker 可以跨进程取消还没开始的task
auto task_runner = base::ThreadPool::CreateTaskRunner({});
base::CancelableTaskTracker cancelable_task_tracker;
cancelable_task_tracker.PostTask(task_runner.get(), FROM_HERE,
base::DoNothing());
// Cancels Task(), only if it hasn't already started running.
cancelable_task_tracker.TryCancelAll();
Posting a Job to run in parallel
The base::PostJob可以并行一堆task,减少posttask的开销,有利于调度。具体不做了解
Testing
暂不做了解
在新进程中使用线程池
新进程需要在入口函数初始化ThreadPoolInstance
// This initializes and starts ThreadPoolInstance with default params.
base::ThreadPoolInstance::CreateAndStartWithDefaultParams(“process_name”);
// The base/task/post_task.h API can now be used with base::ThreadPool trait.
// Tasks will be scheduled as they are posted.
// This initializes ThreadPoolInstance.
base::ThreadPoolInstance::Create(“process_name”);
// The base/task/post_task.h API can now be used with base::ThreadPool trait. No
// threads will be created and no tasks will be scheduled until after Start() is
// called.
base::ThreadPoolInstance::Get()->Start(params);
// ThreadPool can now create threads and schedule tasks.
也可以关闭线程池
base::ThreadPoolInstance::Get()->Shutdown();