Threading and Tasks in Chrome

来源

https://chromium.googlesource.com/chromium/src/+/master/docs/threading_and_tasks.md

概述

Chrome有一个多进程架构,每个进程都是多线程的。在本文中,我们将回顾每个进程共享的基本线程系统。主要目标是保持主线程(又名浏览器进程中的“UI”线程)和IO线程(用于处理IPC的每个进程的线程)响应。

这意味着将任何阻塞I/O或其他昂贵的操作将被转移到其他线程。我们的方法是使用消息传递作为线程之间通信的方式。我们不鼓励锁定和线程安全的对象。

相反,对象只存在于一个线程上(通常是虚拟的--我们稍后会讲到!),我们在这些线程之间传递消息以进行通信。本文档假定您熟悉计算机科学线程概念。


命名法

核心概念

任务:要处理的工作单元。就是具有可选关联状态的函数指针。在Chrome中,这是通过base::Bind创建的base::Callback(https://source.chromium.org/chromium/chromium/src/+/main:docs/callback.md实现的


base::Bind和base::Callback类似boost中的Bind和Callback,其绑定方式示例:

int Return5() { return 5; } 
base::Callback<int(void)> func_cb = base::Bind(&Return5); 
func_cb.Run();

//------------

class Ref : public base::RefCountedThreadSafe<Ref> { 
  public:     
  int Foo() { return 3; }
};
scoped_refptr<Ref> ref = new Ref();
base::Callback<void()> ref_cb = base::Bind(&Ref::Foo, ref);
LOG(INFO) << ref_cb.Run();  // Prints out 3.

//-------------

// unbound 
parameters void MyFunc(int i, const std::string& str) {} 
base::Callback<void(int, const std::string&)> cb = base::Bind(&MyFunc); 
cb.Run(23, "hello, world");

// Closure 
void MyFunc(int i, const std::string& str) {} 
base::Closure cb = base::Bind(&MyFunc, 23, "hello world");  //<--类型定义为base::Closure
cb.Run();

绑定类成员的时候,被绑定的类必须具有RefCounted,如果要跨线程传递则还需要有RefCountedThreadSafe。



任务队列:要处理的任务队列。

物理线程:操作系统提供的线程(例如POSIX上的pthread或Windows上的CreateThread())。Chrome跨平台抽象是base::PlatformThread。你几乎不应该直接使用这个。

base::Thread:物理线程永远处理来自专用任务队列的消息,直到Quit()。您几乎不应该创建自己的base::thread。

线程池:具有共享任务队列的物理线程池。在Chrome中,这是base::ThreadPoolInstance。每个Chrome进程只有一个实例,它为通过base/task/post_task.h发布的任务提供服务,因此您应该很少需要直接使用base:ThreadPoolInstance API(稍后会有更多关于发布任务的内容)。

序列或虚拟线程:Chrome管理的执行线程。就像物理线程一样,在任何给定的时刻,只有一个任务可以在给定的序列/虚拟线程上运行,并且每个任务都会看到前面任务的副作用。任务按顺序执行,但可能会在每个任务之间跳跃物理线程。

任务运行器:可以通过其发布任务的接口。在Chrome中,这是BASE::TaskRunner。

顺序任务运行器:一个任务运行器,它保证投递到它的任务将按过程顺序运行。每个这样的任务都保证看到它之前的任务的副作用。发布到顺序任务运行器的任务通常由单个线程(虚拟或物理)处理。在Chrome中,这是base::SequencedTaskRunner,也就是base::TaskRunner。

单线程任务运行器:顺序任务运行器,它保证所有任务都将由相同的物理线程处理。

在Chrome中,这是base::SingleThreadTaskRunner,即base::SequencedTaskRunner。只要有可能,我们更喜欢序列而不是线程。


线程词典

读者注意:以下术语试图弥合常见线程术语与我们在Chrome中使用它们的方式之间的差距。如果你刚刚开始,可能会有点难懂。如果这很难看懂,可以考虑跳到下面更详细的部分,并在必要时重新参考它。


线程不安全:Chrome中的绝大多数类型都是线程不安全的(通过设计)。对此类类型/方法的访问必须在外部同步。通常,线程不安全类型要求所有访问其状态的任务都发布到相同的base::SequencedTaskRunner,并且它们在调试生成中使用sequence_Checker成员验证这一点。锁也是同步访问的一个选项,但在Chrome中,我们强烈喜欢序列而不是锁。


仿线程:此类类型/方法需要始终从相同的物理线程(即,从相同的base::SingleThreadTaskRunner)访问,并且通常具有Thread_Checker成员来验证它们是否存在。缺少使用第三方API或具有仿线程的叶依赖关系:在Chrome中几乎没有理由让一个类型是仿线程的。

请注意,base::SingleThreadTaskRunner是base::SequencedTaskRunner,因此仿线程是线程不安全的子集。仿线程有时也被称为敌对线程。


线程安全:可以同时安全地访问此类类型/方法。


线程兼容:这类型提供对const方法的安全并发访问,但需要对非const(或混合const/non-const访问)进行同步。Chrome不公开读写器锁;因此,唯一的用例是对象(通常是全局),它们以线程安全的方式初始化一次(或者在启动的单线程阶段,或者通过线程安全的静态-局部-初始化范例(通过base::NoDestructor)懒惰地初始化一次),并且在不变之后永远初始化。


Immutable:线程兼容类型的子集,在构造后不能修改。


序列友好的:这种类型/方法是线程不安全的类型,支持从base::SequencedTaskRunner调用。理想情况下,所有线程不安全类型都是如此,但是遗留代码有时有过度热心的检查,在仅仅是线程不安全的情况下强制执行线程亲和性有关更多详细信息,请参见下面的:优先使用序列而非线程。


线程

每个Chrome进程都有:

主线程

      在浏览器进程中(BrowserThread::UI):更新UI。

      在渲染器进程中(Blink主线程):运行大部分Blink。

IO线程

      在浏览器进程(BrowserThread::IO)中:处理IPC和网络请求。

      在渲染器进程中:处理IPC。

还有几个特殊用途的线程和一个通用线程池。大多数线程都有一个从队列中获取任务并运行它们的循环(队列可以在多个线程之间共享)。