内容
注意:有关更多示例,请参阅线程和任务常见问题解答。
概述
Chrome 采用多进程架构,每个进程都高度多线程化。本文档将介绍每个进程共享的基本线程系统。我们的主要目标是保持浏览器的高响应速度。在没有外部延迟或工作负载要求的情况下,Chrome 致力于成为一个高并发系统,但不一定是并行系统。
关于 Chromium 并发(尤其是序列)方式的基本介绍可以在这里找到。
本文档假设您熟悉计算机科学线程概念。
快速入门指南
- 不要在主线程(即浏览器进程中的“UI”线程)或 IO 线程(每个进程用于接收 IPC 的线程)上执行昂贵的计算或阻塞 IO。繁忙的 UI / IO 线程可能会导致用户可见的延迟,因此最好将这些工作放在线程池中运行。
- 始终避免从不同的线程或序列读取/写入内存中的同一位置。这会导致数据争用!建议跨序列传递消息。不建议使用锁等消息传递的替代方案。
- 如果您需要协调存在于不同序列中的多个对象,请注意对象的生命周期。
- 为了防止意外的数据争用,大多数类最好只用于单个序列。您应该使用SEQUENCE_CHECKER或base::SequenceBound等实用程序来帮助强制执行此约束。
- 根据经验法则,避免使用base::Unretained。弱指针通常可以被替代。
std::unique_ptr
最好通过以下方式明确所有权。- scoped_refptrs可用于跨多个序列拥有多个所有者的对象。这通常是错误的设计模式,不建议在新代码中使用。
核心概念
- 任务:待处理的工作单元。实际上是一个带有可选关联状态的函数指针。在 Chrome 中,它
base::OnceCallback
分别通过和 来base::RepeatingCallback
创建。(文档)。base::BindOnce
base::BindRepeating
- 任务队列:待处理的任务队列。
- 物理线程:操作系统提供的线程(例如 POSIX 上的 pthread 或 Windows 上的 CreateThread())。Chrome 的跨平台抽象是
base::PlatformThread
。您几乎不应该直接使用它。 base::Thread
:一个物理线程永远处理来自专用任务队列的消息,直到 Quit() 为止。您几乎不应该创建自己的base::Thread
线程。- 线程池:一个包含共享任务队列的物理线程池。在 Chrome 中,线程池就是一个线程池
base::ThreadPoolInstance
。每个 Chrome 进程只有一个线程池实例,用于处理通过线程池发送的任务base/task/thread_pool.h,因此您很少需要base::ThreadPoolInstance
直接使用 API(稍后会详细介绍发送任务)。 - 序列线程或虚拟线程:Chrome 管理的执行线程。与物理线程类似,任何时刻,给定序列/虚拟线程上只能运行一个任务,并且每个任务都会受到前一个任务的影响。任务按顺序执行,但可以在各个任务之间跳过物理线程。
- 任务运行器:用于发布任务的界面。在 Chrome 中,它是
base::TaskRunner
。 - 顺序任务运行器:一种任务运行器,它保证提交的任务按提交顺序依次运行。每个此类任务都保证能够看到其前一个任务的副作用。提交到顺序任务运行器的任务通常由单个线程(虚拟线程或物理线程)处理。在 Chrome 中,这就是
base::SequencedTaskRunner
which is-abase::TaskRunner
。 - 单线程任务运行器:一种顺序任务运行器,保证所有任务都由同一个物理线程处理。在 Chrome 中,它就是
base::SingleThreadTaskRunner
which is-abase::SequencedTaskRunner
。我们尽可能优先使用序列而不是线程。
线程词典
读者须知:以下术语旨在弥合常见线程命名法与我们在 Chrome 中使用它们的方式之间的差距。如果您刚开始学习,可能会觉得有点难懂。如果难以理解,请考虑跳至下文更详细的部分,并在必要时再参考本部分。
- 线程不安全:Chrome 中的绝大多数类型都是线程不安全的(设计如此)。访问此类类型/方法必须进行外部同步。通常,线程不安全的类型要求所有访问其状态的任务都发布到同一个线程
base::SequencedTaskRunner
,并在调试版本中使用成员进行验证SEQUENCE_CHECKER
。锁也是一种同步访问的选项,但在 Chrome 中,我们强烈建议使用序列而不是锁。 - 线程亲和性:此类类型/方法需要始终从同一物理线程(即从同一个
base::SingleThreadTaskRunner
)访问,并且通常有一个THREAD_CHECKER
成员来验证它们是否属于线程亲和性。除非使用第三方 API 或具有线程亲和性的叶子依赖项,否则在 Chrome 中,几乎没有理由让类型成为线程亲和性。请注意,base::SingleThreadTaskRunner
is-abase::SequencedTaskRunner
类型是线程不安全的子集。线程亲和性有时也称为线程敌对性。 - 线程安全:此类类型/方法可以安全地并行访问。
- 线程兼容:此类类型提供对 const 方法的安全并行访问,但对非常量(或混合 const/非常量访问)需要同步。Chrome 不提供读写锁;因此,唯一的用例是对象(通常是全局变量),它们以线程安全的方式初始化一次(可以在启动的单线程阶段初始化,也可以通过线程安全的静态局部初始化范式延迟初始化
base::NoDestructor
),并且此后永远不可变。 - 不可变:线程兼容类型的子集,构造后无法修改。
- 序列友好:此类类型/方法是线程非安全类型,支持从 调用
base::SequencedTaskRunner
。理想情况下,所有线程非安全类型都应该如此,但遗留代码有时会在单纯的线程非安全场景中强制执行线程亲和性检查,从而过度严格。有关更多详细信息,请参阅下文的“优先使用序列而非线程” 。
线程
每个 Chrome 进程都有
- 主线程
- 在浏览器进程中(BrowserThread::UI):更新UI
- 在渲染器进程(Blink 主线程)中:运行大部分 Blink 功能
- IO线程
- 在所有进程中:所有 IPC 消息都到达此线程。处理该消息的应用程序逻辑可能位于不同的线程中(例如,IO 线程可能会将消息路由到绑定到不同线程的Mojo 接口)。
- 更一般地,大多数异步 I/O 发生在这个线程上(例如,通过 base::FileDescriptorWatcher)。
- 在浏览器进程中:这被称为BrowserThread::IO。
- 一些特殊用途的线程
- 以及一个通用线程池
大多数线程都有一个循环,从队列中获取任务并运行它们(该队列可能由多个线程共享)。
任务
任务被base::OnceClosure
添加到队列中以便异步执行。
Abase::OnceClosure
存储一个函数指针和参数。它有一个Run()
方法,可以使用绑定的参数调用该函数指针。它是使用 创建的base::BindOnce
。(参考Callback<> 和 Bind() 文档)。
<span style="background-color:#fafafa"><span style="color:#000000">无效任务A(){}
无效 TaskB(int v){}
自动任务_a = base::BindOnce(&TaskA);
自动任务b = base::BindOnce(&TaskB,42);
</span></span>
一组任务可以通过以下方式之一执行:
- 并行:没有任务执行顺序,可能在任何线程上同时执行
- 顺序:按发布顺序执行任务,在任何线程上一次执行一个任务。
- 单线程:按发布顺序执行任务,一次在单个线程上执行一个任务。
- COM 单线程:COM 初始化的单线程变体。
优先使用序列而不是物理线程
强烈建议在虚拟线程上进行顺序执行,而不是在物理线程上进行单线程执行。除了绑定到主线程 (UI) 或 IO 线程的类型/方法外,base::SequencedTaskRunner
通过管理自己的物理线程(参见下文的“发布顺序任务”)可以更好地实现线程安全。
为“当前物理线程”公开的所有 API 都具有与“当前序列”等效的 API(映射)。
如果您发现自己编写了一个序列友好的类型,但它THREAD_CHECKER
在叶子依赖项中未通过线程亲和性检查(例如),请考虑将该依赖项也设置为序列友好的。Chrome 中的大多数核心 API 都是序列友好的,但某些遗留类型可能仍然过度使用 ThreadChecker/SingleThreadTaskRunner,而实际上它们可以依赖于“当前序列”,不再是线程亲和性的。
发布并行任务
直接发布到线程池
可以运行在任何线程上并且与其他任务没有排序或互斥要求的任务应该使用base::ThreadPool::PostTask*()
中定义的函数之一来发布base/task/thread_pool.h。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">));</span>
</span></span>
这会发布具有默认特征的任务。
这些base::ThreadPool::PostTask*()
函数允许调用者通过 TaskTraits 提供有关任务的更多详细信息(参考使用 TaskTraits 注释任务)。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">,</span><span style="color:#660066">MayBlock </span><span style="color:#666600">()},</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">));</span>
</span></span>
通过 TaskRunner 发布
并行base::TaskRunner是直接调用的替代方案base::ThreadPool::PostTask*()
。这主要适用于无法预先知道任务是并行、顺序还是单线程发送的情况(参见发送顺序任务、将多个任务发送到同一线程)。由于是和base::TaskRunner
的基类,因此成员可以包含、或。base::SequencedTaskRunner
base::SingleThreadTaskRunner
scoped_refptr<TaskRunner>
base::TaskRunner
base::SequencedTaskRunner
base::SingleThreadTaskRunner
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">类</span><span style="color:#000000">A </span><span style="color:#666600">{ </span><span style="color:#000088">public </span><span style="color:#666600">: </span><span style="color:#000000">
A </span><span style="color:#666600">() </span><span style="color:#666600">= </span><span style="color:#000088">default </span><span style="color:#666600">;</span>
<span style="color:#000088">void </span><span style="color:#660066">PostSomething </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#000000">
task_runner_ </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">A </span><span style="color:#666600">,</span><span style="color:#666600">& </span><span style="color:#660066">DoSomething </span><span style="color:#666600">)); </span><span style="color:#666600">}</span>
<span style="color:#000088">无效的</span><span style="color:#660066">执行某事</span><span style="color:#666600">()</span><span style="color:#666600">{ </span><span style="color:#666600">}</span>
<span style="color:#000088">私有</span><span style="color:#666600">:</span><span style="color:#000000">
scoped_refptr </span><span style="color:#666600">< </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskRunner </span><span style="color:#666600">> </span><span style="color:#000000">task_runner_ </span><span style="color:#666600">= </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateTaskRunner </span><span style="color:#666600">({ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">USER_VISIBLE </span><span style="color:#666600">}); </span><span style="color:#666600">};</span>
</span></span>
除非测试需要精确控制任务的执行方式,否则最好base::ThreadPool::PostTask*()
直接调用(参考测试以了解在测试中以较少侵入的方式控制任务)。
发布顺序任务
序列是一组按发布顺序逐个运行的任务(不一定在同一线程中)。要将任务发布为序列的一部分,请使用base::SequencedTaskRunner。
发布到新序列
base::SequencedTaskRunner
可以通过 创建A base::ThreadPool::CreateSequencedTaskRunner()
。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">scoped_refptr </span><span style="color:#666600"><SequencedTaskRunner> </span><span style="color:#660066">sequenced_task_runner </span><span style="color:#000000">= </span><span style="color:#666600">base </span><span style="color:#000000">
:: </span><span style="color:#666600">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSequencedTaskRunner </span><span style="color:#666600">(... </span><span style="color:#666600">)</span><span style="color:#660066">;</span>
<span style="color:#880000">// TaskB 在 TaskA 完成后运行。</span><span style="color:#000000">
sequenced_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskA </span><span style="color:#666600">)); </span><span style="color:#000000">
sequenced_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskB </span><span style="color:#666600">));</span>
</span></span>
发布到当前(虚拟)主题
发布到当前(虚拟)线程的首选方式是通过base::SequencedTaskRunner::GetCurrentDefault()
。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// 该任务将在当前(虚拟)线程的默认任务队列上运行。base </span><span style="color:#000000">
:: </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">( </span><span style="color:#666600">& </span><span style="color:#660066">Task </span><span style="color:#666600">));</span>
</span></span>
请注意,SequencedTaskRunner::GetCurrentDefault()
返回的是当前虚拟线程的默认队列。在具有多个任务队列的线程(例如 BrowserThread::UI)中,该队列可能与当前任务所属的队列不同。“当前”任务运行器有意不通过静态 getter 公开。您要么已经知道它并可以直接向其发送消息,要么不知道,唯一合理的目的地是默认队列。有关详细讨论,请参阅https://bit.ly/3JvCLsX 。
使用序列代替锁
Chrome 不鼓励使用锁。序列本身就提供了线程安全。建议您使用始终从同一序列访问的类,而不是使用锁来管理您自己的线程安全。
线程安全但不线程亲和;这是为什么呢?发送到同一序列的任务将按顺序运行。一个已排序的任务完成后,下一个任务可能会被另一个工作线程执行,但该任务肯定会受到前一个任务对其序列造成的任何副作用的影响。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">class </span><span style="color:#000000">A </span><span style="color:#666600">{ </span><span style="color:#000088">public </span><span style="color:#666600">: </span><span style="color:#000000">
A </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// 不需要访问创建序列。DETACH_FROM_SEQUENCE </span><span style="color:#666600">( </span><span style="color:#000000">
sequence_checker_ </span><span style="color:#000000">) </span><span style="color:#666600">; </span><span style="color:#666600">}</span>
<span style="color:#000088">void </span><span style="color:#660066">AddValue </span><span style="color:#666600">( </span><span style="color:#660066">int </span><span style="color:#000000">v </span><span style="color:#666600">) </span><span style="color:#666600">{ </span><span style="color:#880000">// 检查所有访问是否在同一个序列上。DCHECK_CALLED_ON_VALID_SEQUENCE </span><span style="color:#666600">( </span><span style="color:#000000">sequence_checker_ </span><span style="color:#666600">); </span><span style="color:#000000">
values_ </span><span style="color:#666600">. </span><span style="color:#000000">push_back </span><span style="color:#666600">( </span><span style="color:#000000">v </span><span style="color:#000000">
) </span><span style="color:#666600">; </span><span style="color:#666600">}</span>
<span style="color:#000088">私有</span><span style="color:#666600">:</span><span style="color:#000000">
SEQUENCE_CHECKER </span><span style="color:#666600">(</span><span style="color:#000000">sequence_checker_ </span><span style="color:#666600">);</span>
<span style="color:#880000">// 不需要锁,因为所有访问都在 // 同一个序列上</span><span style="color:#666600">。std </span><span style="color:#880000">:: </span><span style="color:#000000">
vector </span><span style="color:#660066"><int> </span><span style="color:#008800">values_ </span><span style="color:#000000">; </span><span style="color:#666600">} </span><span style="color:#666600">;</span>
<span style="color:#000000">
A a </span><span style="color:#666600">; </span><span style="color:#000000">
scoped_refptr </span><span style="color:#666600">< </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">> </span><span style="color:#000000">task_runner_for_a </span><span style="color:#666600">= </span><span style="color:#666600">...; </span><span style="color:#000000">
task_runner_for_a </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#660066">AddValue </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">Unretained </span><span style="color:#666600">(&a </span><span style="color:#000000">a </span><span style="color:#666600">), </span><span style="color:#006666">42 </span><span style="color:#666600">)); </span><span style="color:#000000">
task_runner_for_a </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#660066">AddValue </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">Unretained </span><span style="color:#666600">(&a </span><span style="color:#000000">a </span><span style="color:#666600">), </span><span style="color:#006666">27 </span><span style="color:#666600">));</span>
<span style="color:#880000">// 从不同的序列访问会导致 DCHECK 失败。scoped_refptr </span><span style="color:#666600"><SequencedTaskRunner> </span><span style="color:#660066">other_task_runner </span><span style="color:#666600">= </span><span style="color:#666600">...; </span><span style="color:#000000">other_task_runner </span><span style="color:#000000">
- </span><span style="color:#666600">> </span><span style="color:#000000">
PostTask </span><span style="color:#660066">( </span><span style="color:#666600">FROM_HERE </span><span style="color:#000000">, </span><span style="color:#666600">base </span><span style="color:#000000">
:: </span><span style="color:#666600">BindOnce </span><span style="color:#660066">( </span><span style="color:#666600">& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#666600">AddValue </span><span style="color:#660066">, </span><span style="color:#666600">base </span><span style="color:#000000">:: </span><span style="color:#666600">Unretained </span><span style="color:#660066">( </span><span style="color:#666600">&a </span><span style="color:#000000">a </span><span style="color:#666600">), </span><span style="color:#006666">1 </span><span style="color:#666600">));</span>
</span></span>
锁应该仅用于交换可在多个线程上访问的共享数据结构。如果一个线程基于昂贵的计算或通过磁盘访问来更新该结构,那么这项缓慢的工作应该在不持有锁的情况下完成。只有当结果可用时,才应该使用锁来交换新数据。PluginList::LoadPlugins ( ) 中就有一个这样的示例content/browser/plugin_list.cc。如果必须使用锁,以下是一些最佳实践和需要避免的陷阱。
为了编写非阻塞代码,Chrome 中的许多 API 都是异步的。通常,这意味着它们要么需要在特定的线程/序列上执行,并通过自定义委托接口返回结果,要么接受一个base::OnceCallback<>
(或base::RepeatingCallback<>
) 对象,该对象在请求的操作完成时被调用。上文 PostTask 部分介绍了如何在特定的线程/序列上执行工作。
将多个任务发布到同一线程
如果多个任务需要在同一个线程上运行,请将它们发布到base::SingleThreadTaskRunner。发布到同一个线程的所有任务将按照base::SingleThreadTaskRunner
发布顺序在同一个线程上运行。
发布到浏览器进程中的主线程或 IO 线程
要将任务发布到主线程或 IO 线程,请使用content::GetUIThreadTaskRunner({})
或content::GetIOThreadTaskRunner({})
从content/public/browser/browser_thread.h
您可以向这些方法提供额外的 BrowserTaskTraits 作为参数,尽管这在 BrowserThreads 中通常并不常见,应该保留用于高级用例。
目前正在进行从以前的 base-API-with-traits 的迁移(任务 API v3 ),您可能仍会在整个代码库中找到它(它是等效的):
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{</span><span style="color:#000000">内容</span><span style="color:#666600">::</span><span style="color:#660066">浏览器线程</span><span style="color:#666600">:: </span><span style="color:#000000">UI </span><span style="color:#666600">},</span><span style="color:#666600">...);</span> <span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSingleThreadTaskRunner </span><span style="color:#666600">({ </span><span style="color:#000000">content </span><span style="color:#666600">:: </span><span style="color:#660066">BrowserThread </span><span style="color:#666600">:: </span><span style="color:#000000">IO </span><span style="color:#666600">})</span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">...);</span>
</span></span>
注意:在迁移期间,很遗憾,您需要继续手动包含content/public/browser/browser_task_traits.h. 以使用 browser_thread.h API。
主线程和 IO 线程已经非常繁忙。因此,尽可能将任务发布到通用线程(参见发布并行任务、发布顺序任务)。将任务发布到主线程的良好理由是更新 UI 或访问绑定到主线程的对象(例如Profile
)。将任务发布到 IO 线程的良好理由是访问绑定到主线程的组件内部(例如 IPC、网络)。注意:无需显式地将任务发布到 IO 线程即可发送/接收 IPC 或在网络上发送/接收数据。
发送到渲染进程的主线程
TODO(blink-dev)
发布到自定义 SingleThreadTaskRunner
如果多个任务需要在同一个线程上运行,并且该线程不必是主线程或 IO 线程,则将它们发布到base::SingleThreadTaskRunner
由...创建的base::Threadpool::CreateSingleThreadTaskRunner
。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">scoped_refptr </span><span style="color:#666600"><SingleThreadTaskRunner> </span><span style="color:#660066">single_thread_task_runner </span><span style="color:#666600">= </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#666600">Threadpool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSingleThreadTaskRunner </span><span style="color:#666600">(... </span><span style="color:#660066">)</span><span style="color:#000000">;</span>
<span style="color:#880000">// TaskB 在 TaskA 完成后运行。两个任务在同一个线程上运行。single_thread_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskA </span><span style="color:#666600">)); </span><span style="color:#000000">
single_thread_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskB </span><span style="color:#666600">)) </span><span style="color:#000000">
;</span>
</span></span>
请记住,我们更喜欢序列而不是物理线程,因此这很少是必要的。
发帖至当前主题
base::SequencedTaskRunner::GetCurrentDefault()
而不是base::SingleThreadTaskRunner::GetCurrentDefault()
(请参阅发布到当前序列)。这样可以更好地记录发布任务的要求,并避免不必要地使 API 依赖于物理线程。在单线程任务中,base::SequencedTaskRunner::GetCurrentDefault()
相当于base::SingleThreadTaskRunner::GetCurrentDefault()
。
如果您仍然必须将任务发布到当前物理线程,请使用base::SingleThreadTaskRunner::CurrentDefaultHandle。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">
// 该任务</span><span style="color:#880000">将来会在当前线程上运行。base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">));</span>
</span></span>
将任务发布到 COM 单线程单元 (STA) 线程 (Windows)
需要在 COM 单线程单元 (STA) 线程上运行的任务必须发布到base::SingleThreadTaskRunner
返回的base::ThreadPool::CreateCOMSTATaskRunner()
。如将多个任务发布到同一线程中所述,所有发布到同一线程的任务均按base::SingleThreadTaskRunner
发布顺序在同一线程上运行。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// Task(A|B|C)UsingCOMSTA 将在同一个 COM STA 线程上运行。</span>
<span style="color:#000088">void </span><span style="color:#660066">TaskAUingCOMSTA </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// [这在 COM STA 线程上运行。]</span>
<span style="color:#880000">// 进行 COM STA 调用。</span><span style="color:#880000">// ...</span>
<span style="color:#666600">// 将另一个</span><span style="color:#880000">任务发布到当前 COM STA 线程。base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#000000">
:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">TaskCUsingCOMSTA </span><span style="color:#666600">)); </span><span style="color:#666600">} </span><span style="color:#000088">void </span><span style="color:#660066">TaskBUsingCOMSTA </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#666600">} </span><span style="color:#000088">void </span><span style="color:#660066">TaskCUsingCOMSTA </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#666600">}</span>
<span style="color:#000088">自动</span><span style="color:#000000">com_sta_task_runner </span><span style="color:#666600">= </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateCOMSTATaskRunner </span><span style="color:#666600">(...); </span><span style="color:#000000">
com_sta_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(&</span><span style="color:#660066">TaskAUsingCOMSTA </span><span style="color:#666600">)); </span><span style="color:#000000">
com_sta_task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(&</span><span style="color:#660066">TaskBUsingCOMSTA </span><span style="color:#666600">));</span>
</span></span>
已发布任务的内存排序保证
此任务系统保证在任务开始运行时,所有在发布任务之前顺序执行的内存效应都对任务可见PostTask()
。更正式地说,对的调用和发布任务的执行彼此之间存在先行发生关系::base
。这适用于所有以 方式发布任务的变体,包括PostTaskAndReply()
。同样,对于作为同一 SequencedTaskRunner 的一部分按顺序运行的任务,也存在先行发生关系。
了解这一保证至关重要,因为 Chrome 任务通常会访问复制到 中的直接数据以外的内存base::OnceCallback
,而这种先行关系可以避免任务本身内部的额外同步。举一个非常具体的例子,考虑一个回调,它将一个指针绑定到刚刚在发布任务的线程中初始化的内存上。
一个更受约束的模型也值得注意。执行可以拆分成多个任务,分别运行在不同的任务运行器上,每个任务都以独占方式访问内存中的特定对象,无需显式同步。发布另一个任务会将(对象的)“所有权”转移给下一个任务。这样,对象所有权的概念通常可以扩展到任务运行器的级别,从而提供有用的不变量来推理。该模型可以避免竞争条件,同时避免锁和原子操作。由于其简单性,该模型在 Chrome 中被广泛使用。
使用 TaskTraits 注释任务
base::TaskTraits封装有关任务的信息,帮助线程池做出更好的调度决策。
base::TaskTraits
当默认特征足够时,可以传递需要的方法{}
。默认特征适用于以下任务:
- 不要阻塞(参考 MayBlock 和 WithBaseSyncPrimitives);
- 与用户阻止活动有关;(通过与执行该操作的组件建立排序依赖关系来明确或隐含地表示)
- 可以阻止关机或在关机时跳过(线程池可以自由选择合适的默认值)。
与该描述不匹配的任务必须使用明确的 TaskTraits 进行发布。
base/task/task_traits.h提供所有可用特征的详尽文档。内容层还提供了其他特征,content/public/browser/browser_task_traits.h以便于将任务发布到浏览器线程 (BrowserThread)。
下面是一些如何指定的示例base::TaskTraits
。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// 此任务没有显式的 TaskTraits。它无法阻塞。其优先级为</span><span style="color:#880000">// USER_BLOCKING。它将阻塞关闭或在关闭时被跳过。base </span><span style="color:#660066">:: ThreadPool </span><span style="color:#666600">:: </span><span style="color:#000000">
PostTask </span><span style="color:#666600">( </span><span style="color:#660066">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#000000">BindOnce </span><span style="color:#666600">( </span><span style="color:#660066">... </span><span style="color:#666600">));</span>
<span style="color:#880000">// 此任务具有最高优先级。线程池将在</span><span style="color:#880000">USER_VISIBLE 和 BEST_EFFORT 任务之前调度它。base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">, </span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#000000">
:: </span><span style="color:#000000">USER_BLOCKING </span><span style="color:#666600">} </span><span style="color:#666600">, </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(...));</span>
<span style="color:#880000">// 此任务具有最低优先级,并允许阻塞(例如,它</span><span style="color:#880000">//可以从磁盘读取文件)。base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#000000">
:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">MayBlock </span><span style="color:#666600">()},</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(</span><span style="color:#666600">...));</span>
<span style="color:#880000">// 此任务会阻止关闭。进程在其执行完成</span><span style="color:#880000">之前不会退出。base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTask </span><span style="color:#666600">( </span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">, </span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskShutdownBehavior </span><span style="color:#000000">
:: </span><span style="color:#000000">BLOCK_SHUTDOWN </span><span style="color:#666600">} </span><span style="color:#666600">, </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(...));</span>
</span></span>
保持浏览器响应
不要在主线程、IO 线程或任何预期以低延迟运行任务的序列上执行高开销工作。相反,请使用base::ThreadPool::PostTaskAndReply*()
或异步执行高开销工作base::SequencedTaskRunner::PostTaskAndReply()
。请注意,IO 线程上的异步/重叠 I/O 是可以的。
示例:在主线程上运行下面的代码将导致浏览器长时间无法响应用户输入。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// GetHistoryItemsFromDisk() 可能会阻塞很长时间。 // AddHistoryItemsToOmniboxDropDown( </span><span style="color:#660066">)</span><span style="color:#880000">更新 UI,因此必须</span><span style="color:#880000">// 在主线程上调用。AddHistoryItemsToOmniboxDropdown </span><span style="color:#666600">( </span><span style="color:#660066">GetHistoryItemsFromDisk </span><span style="color:#666600">( </span><span style="color:#008800">"keyword" </span><span style="color:#666600">));</span>
</span></span>
下面的代码解决了这个问题,它先在线程池中调用 ,GetHistoryItemsFromDisk()
然后在原始序列(本例中是主线程)中调用AddHistoryItemsToOmniboxDropdown()
。第一次调用的返回值会自动作为第二次调用的参数。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTaskAndReplyWithResult </span><span style="color:#666600">(</span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">MayBlock </span><span style="color:#666600">()},</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">GetHistoryItemsFromDisk </span><span style="color:#666600">,</span><span style="color:#008800">“关键字” </span><span style="color:#666600">),</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">AddHistoryItemsToOmniboxDropdown </span><span style="color:#666600">));</span>
</span></span>
延迟发布任务
延迟发布一次性任务
要发布延迟到期后必须运行一次的任务,请使用base::ThreadPool::PostDelayedTask*()
或base::TaskRunner::PostDelayedTask()
。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostDelayedTask </span><span style="color:#666600">(</span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">),</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">Hours </span><span style="color:#666600">(</span><span style="color:#006666">1 </span><span style="color:#666600">));</span> <span style="color:#000000">
scoped_refptr </span><span style="color:#666600">< </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">> </span><span style="color:#000000">task_runner </span><span style="color:#666600">= </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSequencedTaskRunner </span><span style="color:#666600">(</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">}); </span><span style="color:#000000">
task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostDelayedTask </span><span style="color:#666600">(</span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Task </span><span style="color:#666600">),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">Hours </span><span style="color:#666600">(</span><span style="color:#006666">1 </span><span style="color:#666600">));</span>
</span></span>
base::TaskPriority::BEST_EFFORT
以防止其在延迟到期时拖慢浏览器速度。
延迟发布重复任务
要发布必须定期运行的任务,请使用base::RepeatingTimer。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">class </span><span style="color:#000000">A </span><span style="color:#666600">{ </span><span style="color:#000088">public </span><span style="color:#666600">: </span><span style="color:#666600">~ </span><span style="color:#000000">A </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// 计时器在删除时自动停止。</span><span style="color:#666600">} </span><span style="color:#000088">void </span><span style="color:#660066">StartDoingStuff </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#000000">
timer_ </span><span style="color:#666600">. </span><span style="color:#660066">Start </span><span style="color:#666600">( </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#660066">Seconds </span><span style="color:#666600">( </span><span style="color:#006666">1 </span><span style="color:#666600">), </span><span style="color:#000088">this </span><span style="color:#666600">, </span><span style="color:#666600">& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#660066">DoStuff </span><span style="color:#666600">); </span><span style="color:#666600">} </span><span style="color:#000088">void </span><span style="color:#660066">StopDoingStuff </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#000000">
timer_ </span><span style="color:#666600">. </span><span style="color:#660066">Stop </span><span style="color:#666600">(); </span><span style="color:#666600">} </span><span style="color:#000088">private </span><span style="color:#666600">: </span><span style="color:#000088">void </span><span style="color:#660066">DoStuff </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// 在调用 StartDoingStuff() 的序列中每秒调用此方法</span><span style="color:#880000">。</span><span style="color:#666600">} </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">RepeatingTimer </span><span style="color:#000000">timer_ </span><span style="color:#666600">; </span><span style="color:#666600">};</span>
</span></span>
取消任务
使用 base::WeakPtr
base::WeakPtr可用于确保在对象被销毁时取消绑定到该对象的任何回调。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#660066">int</span><span style="color:#660066">计算</span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#666600">… </span><span style="color:#666600">}</span>
<span style="color:#000088">class </span><span style="color:#000000">A </span><span style="color:#666600">{ </span><span style="color:#000088">public </span><span style="color:#666600">: </span><span style="color:#000088">void </span><span style="color:#660066">ComputeAndStore </span><span style="color:#666600">() </span><span style="color:#666600">{ </span><span style="color:#880000">// 在线程池中安排对 Compute() 的调用,然后</span><span style="color:#880000">在当前序列中调用 A::Store()。对 // A::Store() 的调用</span><span style="color:#880000">在 |weak_ptr_factory_| 被销毁时被取消。</span><span style="color:#880000">// (保证 |this| 不会被释放后使用)。base </span><span style="color:#666600">:: </span><span style="color:#000000">
ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTaskAndReplyWithResult </span><span style="color:#660066">( </span><span style="color:#666600">FROM_HERE </span><span style="color:#000000">
, </span><span style="color:#666600">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#660066">Compute </span><span style="color:#666600">), </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#000000">
( </span><span style="color:#666600">& </span><span style="color:#000000">A </span><span style="color:#666600">:: </span><span style="color:#660066">Store </span><span style="color:#666600">, </span><span style="color:#000000">weak_ptr_factory_ </span><span style="color:#666600">. </span><span style="color:#660066">GetWeakPtr </span><span style="color:#666600">())); </span><span style="color:#666600">}</span>
<span style="color:#000088">私有</span><span style="color:#666600">:</span><span style="color:#000088">void</span><span style="color:#660066">存储</span><span style="color:#666600">(</span><span style="color:#660066">int</span><span style="color:#000000">值</span><span style="color:#666600">)</span><span style="color:#666600">{ </span><span style="color:#000000">value_ </span><span style="color:#666600">= </span><span style="color:#000000">value </span><span style="color:#666600">; </span><span style="color:#666600">}</span>
<span style="color:#660066">int </span><span style="color:#000000">value_ </span><span style="color:#666600">; </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">WeakPtrFactory </span><span style="color:#666600"><A> </span><span style="color:#666600">weak_ptr_factory_ </span><span style="color:#000000">{ </span><span style="color:#000000">this </span><span style="color:#000088">} </span><span style="color:#666600">; </span><span style="color:#666600">} </span><span style="color:#666600">;</span>
</span></span>
注意:WeakPtr
不是线程安全的:~WeakPtrFactory()
并且Store()
(绑定到WeakPtr
)必须全部在同一个序列上运行。
使用 base::CancelableTaskTracker
base::CancelableTaskTracker允许以与任务运行顺序不同的顺序取消任务。请记住,CancelableTaskTracker
无法取消已经开始运行的任务。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">auto </span><span style="color:#000000">task_runner </span><span style="color:#666600">= </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateTaskRunner </span><span style="color:#666600">({}); </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">CancelableTaskTracker </span><span style="color:#000000">cancellationable_task_tracker </span><span style="color:#666600">; </span><span style="color:#000000">
cancellationable_task_tracker.PostTask </span><span style="color:#666600">( </span><span style="color:#660066">task_runner.get </span><span style="color:#666600">(), </span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">, </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">DoNothing </span><span style="color:#666600">()); // 仅</span><span style="color:#660066">当Task( </span><span style="color:#000000">)</span><span style="color:#880000">尚未开始运行时才</span><span style="color:#000000">取消</span><span style="color:#000000">
它</span><span style="color:#666600">。cancelable_task_tracker.TryCancelAll </span><span style="color:#666600">( </span><span style="color:#666600">) </span><span style="color:#666600">;</span>
</span></span>
发布并行运行的作业
这base::PostJob是一个高级用户 API,能够调度单个 base::RepeatingCallback 工作任务,并请求 ThreadPool 工作线程并行调用该任务。这可以避免以下情况:
- 调用
PostTask()
每个工作项,造成大量开销。 - 固定数量的
PostTask()
调用会拆分工作,并且可能运行很长时间。当许多组件发布“核心数”任务,并且所有组件都希望使用所有核心时,这会带来问题。在这种情况下,调度程序缺乏上下文,无法公平地处理多个相同优先级的请求,并且/或者无法在高优先级工作到来时请求低优先级工作让步。
请参阅base/task/job_perftest.cc完整的示例。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// |worker_task| 的规范实现。void </span><span style="color:#000088">WorkerTask </span><span style="color:#666600">( </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">JobDelegate </span><span style="color:#666600">* </span><span style="color:#000000">job_delegate </span><span style="color:#666600">) </span><span style="color:#666600">{ </span><span style="color:#000088">while </span><span style="color:#666600">(! </span><span style="color:#000000">job_delegate </span><span style="color:#666600">-> </span><span style="color:#660066">ShouldYield </span><span style="color:#666600">()) </span><span style="color:#666600">{ </span><span style="color:#000088">auto </span><span style="color:#000000">work_item </span><span style="color:#666600">= </span><span style="color:#660066">TakeWorkItem </span><span style="color:#666600">(); </span><span style="color:#880000">// 最小工作单位。if </span><span style="color:#000088">( </span><span style="color:#666600">! </span><span style="color:#000000">work_item </span><span style="color:#666600">) </span><span style="color:#000088">return </span><span style="color:#660066">: </span><span style="color:#666600">ProcessWork </span><span style="color:#666600">( </span><span style="color:#000000">work_item </span><span style="color:#660066">) </span><span style="color:#666600">; </span><span style="color:#666600">} </span><span style="color:#666600">}</span>
<span style="color:#880000">// 返回最新的线程安全未完成工作项数量。</span><span style="color:#000088">void </span><span style="color:#660066">NumIncompleteWorkItems </span><span style="color:#666600">( </span><span style="color:#660066">size_t </span><span style="color:#000000">worker_count </span><span style="color:#666600">) </span><span style="color:#666600">{ </span><span style="color:#880000">// 如果 NumIncompleteWorkItems() 需要考虑</span><span style="color:#880000">本地工作列表,则可以使用 |worker_count|,这比自己进行核算更容易,同时记住</span><span style="color:#880000">,实际项目数量可能被高估,因此</span><span style="color:#880000">// 当没有可用的工作时,可能会调用 WorkerTask()。</span><span style="color:#000088">return </span><span style="color:#660066">GlobalQueueSize </span><span style="color:#666600">() </span><span style="color:#666600">+ </span><span style="color:#000000">worker_count </span><span style="color:#666600">; </span><span style="color:#666600">}</span>
<span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">PostJob </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{},</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindRepeating </span><span style="color:#666600">(& </span><span style="color:#660066">WorkerTask </span><span style="color:#666600">),</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindRepeating </span><span style="color:#666600">(& </span><span style="color:#660066">NumIncompleteWorkItems </span><span style="color:#666600">));</span>
</span></span>
通过在调用时循环执行尽可能多的工作,工作任务可以避免调度开销。同时,任务base::JobDelegate::ShouldYield()
会定期调用,以便有条件地退出,并让调度程序优先处理其他工作。例如,这种让步语义允许用户可见的作业使用所有核心,但在用户阻塞任务进入时让出。
向正在运行的作业添加额外工作
当添加新的工作项并且 API 用户希望额外的线程并行调用工作任务时,JobHandle/JobDelegate::NotifyConcurrencyIncrease()
必须在最大并发数增加后不久调用。
测试
有关更多详细信息,请参阅测试发布任务的组件。
要测试使用 或 中的函数的代码base::SingleThreadTaskRunner::CurrentDefaultHandle
,base::SequencedTaskRunner::CurrentDefaultHandle
请在测试范围内base/task/thread_pool.h实例化。如果需要 BrowserThreads,请使用而不是。base::test::TaskEnvironmentcontent::BrowserTaskEnvironment
base::test::TaskEnvironment
测试可以base::test::TaskEnvironment
使用 运行 的消息泵base::RunLoop
,可以使其运行直到Quit()
(明确或通过RunLoop::QuitClosure()
),或运行到RunUntilIdle()
准备运行的任务并立即返回。
如果在 TestTimeouts::action_timeout() 之后未明确退出,TaskEnvironment 会将 RunLoop::Run() 配置为 GTEST_FAIL()。这比测试代码未能触发 RunLoop 退出导致测试挂起要好得多。可以使用 base::test::ScopedRunLoopTimeout 覆盖超时时间。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">类</span><span style="color:#660066">MyTest </span><span style="color:#666600">:</span><span style="color:#000088">公共</span><span style="color:#000000">测试</span><span style="color:#666600">::</span><span style="color:#660066">测试</span><span style="color:#666600">{</span><span style="color:#000088">公共</span><span style="color:#666600">:</span><span style="color:#880000">//...</span><span style="color:#000088">受保护的</span><span style="color:#666600">:</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#000000">test </span><span style="color:#666600">:: </span><span style="color:#660066">TaskEnvironment </span><span style="color:#000000">task_environment_ </span><span style="color:#666600">; </span><span style="color:#666600">};</span>
<span style="color:#000000">
TEST_F </span><span style="color:#666600">(</span><span style="color:#660066">MyTest </span><span style="color:#666600">,</span><span style="color:#660066">FirstTest </span><span style="color:#666600">)</span><span style="color:#666600">{ </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">A </span><span style="color:#666600">));</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">B </span><span style="color:#666600">));</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostDelayedTask </span><span style="color:#666600">(</span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">C </span><span style="color:#666600">),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TimeDelta </span><span style="color:#666600">:: </span><span style="color:#660066">Max </span><span style="color:#666600">());</span>
<span style="color:#880000">// 这将运行 (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle 队列,直到它为空。</span><span style="color:#880000">// 延迟的任务只有在执行条件成熟时才会添加到队列中。</span><span style="color:#880000">// 尽可能优先使用明确的退出条件而不是 RunUntilIdle:</span><span style="color:#880000">// bit.ly/run-until-idle-with-care2.base </span><span style="color:#000000">
:: </span><span style="color:#660066">RunLoop </span><span style="color:#666600">(). </span><span style="color:#666600">RunUntilIdle </span><span style="color:#660066">( </span><span style="color:#666600">); </span><span style="color:#880000">// A 和 B 已执行。C 尚未成熟。</span>
<span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">RunLoop </span><span style="color:#000000">run_loop </span><span style="color:#666600">;</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">D </span><span style="color:#666600">));</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">SingleThreadTaskRunner </span><span style="color:#666600">:: </span><span style="color:#660066">GetCurrentDefault </span><span style="color:#666600">()-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">run_loop.QuitClosure </span><span style="color:#666600">());</span><span style="color:#660066">base </span><span style="color:#000000">
:: </span><span style="color:#666600">SingleThreadTaskRunner </span><span style="color:#660066">:: </span><span style="color:#666600">GetCurrentDefault </span><span style="color:#660066">(</span><span style="color:#666600">)-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#666600">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#000000">:: </span><span style="color:#666600">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">E </span><span style="color:#666600">))</span><span style="color:#660066">;</span>
<span style="color:#880000">// 这将运行 (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle 队列,直到</span><span style="color:#880000">调用</span><span style="color:#000000">
QuitClosure。run_loop.Run </span><span style="color:#666600">(); </span><span style="color:#666600">// D 和 run_loop.QuitClosure() 已执行</span><span style="color:#660066">。E</span><span style="color:#880000">仍在队列中。</span>
<span style="color:#880000">// 发布到线程池的任务在发布时异步运行。base </span><span style="color:#666600">:: </span><span style="color:#666600">ThreadPool </span><span style="color:#660066">:: </span><span style="color:#666600">PostTask </span><span style="color:#660066">(</span><span style="color:#666600">FROM_HERE </span><span style="color:#000000">,</span><span style="color:#666600">{ </span><span style="color:#666600">},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">F </span><span style="color:#666600">)); </span><span style="color:#000088">auto </span><span style="color:#000000">task_runner </span><span style="color:#000000">
= </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSequencedTaskRunner </span><span style="color:#666600">({}); </span><span style="color:#000000">
task_runner </span><span style="color:#666600">-> </span><span style="color:#660066">PostTask </span><span style="color:#666600">(</span><span style="color:#000000">FROM_HERE </span><span style="color:#666600">,</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(& </span><span style="color:#000000">G </span><span style="color:#666600">));</span>
<span style="color:#880000">// 阻塞直到发布到线程池的所有任务都运行完成:</span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPoolInstance </span><span style="color:#666600">:: </span><span style="color:#660066">Get </span><span style="color:#666600">()-> </span><span style="color:#660066">FlushForTesting </span><span style="color:#666600">(); </span><span style="color:#880000">// F 和 G 已经执行。</span>
<span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">PostTaskAndReplyWithResult </span><span style="color:#666600">(</span><span style="color:#000000">
FROM_HERE </span><span style="color:#666600">,</span><span style="color:#666600">{},</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(&</span><span style="color:#000000">H </span><span style="color:#666600">),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">BindOnce </span><span style="color:#666600">(&</span><span style="color:#000000">I </span><span style="color:#666600">));</span>
<span style="color:#880000">// 这将运行 (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle 队列,直到</span><span style="color:#880000">// (SingleThread|Sequenced)TaskRunner::CurrentDefaultHandle 队列和 ThreadPool 队列都为</span><span style="color:#880000">// 空。尽可能优先使用明确的退出条件而不是 RunUntilIdle:</span><span style="color:#880000">// bit.ly/run-until-idle-with-care2. </span><span style="color:#000000">
task_environment_ </span><span style="color:#666600">. </span><span style="color:#660066">RunUntilIdle </span><span style="color:#666600">(); </span><span style="color:#880000">// E、H、I 已执行。</span><span style="color:#666600">}</span>
</span></span>
在新进程中使用线程池
在使用ThreadPoolInstance 函数之前,需要在进程中初始化 ThreadPoolInstance。Chromebase/task/thread_pool.h浏览器进程及其子进程(渲染器、GPU、实用程序)中的 ThreadPoolInstance 初始化已完成。要在其他进程中使用 ThreadPoolInstance,请在主函数的早期阶段初始化 ThreadPoolInstance:
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#880000">// 这将使用默认参数初始化并启动 ThreadPoolInstance。base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPoolInstance </span><span style="color:#666600">:: </span><span style="color:#660066">CreateAndStartWithDefaultParams </span><span style="color:#666600">(</span><span style="color:#008800">“process_name” </span><span style="color:#666600">); // </span><span style="color:#000000">
base </span><span style="color:#880000">/task/thread_pool.h API 现在可以与 base::ThreadPool 特征一起使用。//</span><span style="color:#880000">任务将在发布时进行安排。</span>
<span style="color:#880000">// 这将初始化 ThreadPoolInstance。base </span><span style="color:#000000">
:: </span><span style="color:#660066">ThreadPoolInstance </span><span style="color:#666600">:: </span><span style="color:#660066">Create </span><span style="color:#666600">( </span><span style="color:#008800">"process_name" </span><span style="color:#666600">); </span><span style="color:#880000">// base/task/thread_pool.h API 现在可与 base::ThreadPool 特性一起使用。在调用 Start() 之前,不会</span><span style="color:#666600">创建</span><span style="color:#880000">任何线程,也不会安排任何任务</span><span style="color:#666600">。base :: </span><span style="color:#880000">ThreadPoolInstance </span><span style="color:#000000">
:: </span><span style="color:#660066">Get </span><span style="color:#666600">( </span><span style="color:#660066">) </span><span style="color:#666600">-> </span><span style="color:#660066">Start </span><span style="color:#666600">( </span><span style="color:#000000">params </span><span style="color:#666600">); </span><span style="color:#880000">// ThreadPool 现在可以创建线程并安排任务。</span>
</span></span>
并在主函数中关闭ThreadPoolInstance:
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPoolInstance </span><span style="color:#666600">:: </span><span style="color:#660066">Get </span><span style="color:#666600">()-> </span><span style="color:#660066">Shutdown </span><span style="color:#666600">(); </span><span style="color:#880000">// 使用 TaskShutdownBehavior::BLOCK_SHUTDOWN 发出的任务和</span><span style="color:#880000">// 使用 TaskShutdownBehavior::SKIP_ON_SHUTDOWN 发出的任务,</span><span style="color:#880000">在 Shutdown() 调用之前已开始运行,现已完成</span><span style="color:#880000">执行。使用 // TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN 发出的任务</span><span style="color:#880000">可能仍在</span><span style="color:#880000">运行。</span>
</span></span>
TaskRunner 所有权(鼓励不依赖注入)
TaskRunner 不应该被传递到多个组件。相反,应该由使用 TaskRunner 的组件来创建它。
请参阅此重构示例,其中 TaskRunner 被传递到多个组件,最终在叶子节点中使用。现在,叶子节点可以并且应该直接从 获取其 TaskRunner base/task/thread_pool.h。
如上所述,base::test::TaskEnvironment
允许单元测试控制从底层 TaskRunner 发出的任务。在极少数情况下,测试需要更精确地控制任务顺序:TaskRunner 的依赖注入可能会很有用。对于这种情况,首选方法如下:
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000088">类</span><span style="color:#660066">Foo </span><span style="color:#666600">{</span><span style="color:#000088">公共</span><span style="color:#666600">:</span>
<span style="color:#880000">// 在测试中覆盖 |background_task_runner_|。void </span><span style="color:#666600">SetBackgroundTaskRunnerForTesting </span><span style="color:#660066">( </span><span style="color:#666600">scoped_refptr </span><span style="color:#000088">< </span><span style="color:#666600">base </span><span style="color:#000000">:: </span><span style="color:#666600">SequencedTaskRunner </span><span style="color:#660066">> </span><span style="color:#000000">
background_task_runner </span><span style="color:#000000">) </span><span style="color:#666600">{ </span><span style="color:#666600">background_task_runner_ </span><span style="color:#000000">
= </span><span style="color:#666600">std </span><span style="color:#000000">:: </span><span style="color:#666600">move </span><span style="color:#000000">( </span><span style="color:#666600">background_task_runner </span><span style="color:#000000">) </span><span style="color:#666600">; </span><span style="color:#666600">}</span>
<span style="color:#000088">私有</span><span style="color:#666600">:</span><span style="color:#000000">
scoped_refptr </span><span style="color:#666600">< </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">SequencedTaskRunner </span><span style="color:#666600">> </span><span style="color:#000000">background_task_runner_ </span><span style="color:#666600">= </span><span style="color:#000000">
base </span><span style="color:#666600">:: </span><span style="color:#660066">ThreadPool </span><span style="color:#666600">:: </span><span style="color:#660066">CreateSequencedTaskRunner </span><span style="color:#666600">(</span><span style="color:#666600">{ </span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">MayBlock </span><span style="color:#666600">(),</span><span style="color:#000000">base </span><span style="color:#666600">:: </span><span style="color:#660066">TaskPriority </span><span style="color:#666600">:: </span><span style="color:#000000">BEST_EFFORT </span><span style="color:#666600">}); </span><span style="color:#666600">}</span>
</span></span>
请注意,这仍然允许删除 //chrome 和该组件之间的所有管道层,因为单元测试将直接使用叶层。
常问问题
有关更多示例,请参阅线程和任务常见问题解答。
内部
序列管理器
SequenceManager管理具有不同属性(例如优先级、通用任务类型)的任务队列 (TaskQueue),将所有已发布的任务多路复用到单个后台序列中。这通常是一个 MessagePump。根据所使用的消息泵类型,其他事件(例如 UI 消息)也可能会被处理。在 Windows 上,APC 调用(如果时间允许)以及发送到已注册的 HANDLE 集合的信号也可能会被处理。
消息泵
MessagePump负责处理原生消息,并定期将周期分配给其委托(SequenceManager)。MessagePump 会将委托回调与原生消息处理混合使用,确保两种类型的事件都不会导致对方的周期不足。
有不同的MessagePumpTypes,最常见的是:
默认:仅支持任务和计时器
UI:支持本机UI事件(例如Windows消息)
IO:支持异步IO(不是文件I/O!)
自定义:用户提供的 MessagePump 接口实现
运行循环
RunLoop 是一个辅助类,用于运行与当前线程(通常是 SequenceManager)关联的 RunLoop::Delegate。在堆栈上创建一个 RunLoop,然后调用 Run/Quit 来运行嵌套的 RunLoop,但请避免在生产代码中使用嵌套循环!
任务重入
SequenceManager 具有任务重入保护功能。这意味着,如果正在处理一个任务,则第二个任务在第一个任务完成之前无法启动。重入可以在处理任务时发生,并会创建一个内部消息泵。该内部消息泵随后处理可以隐式启动内部任务的原生消息。内部消息泵可以通过对话框 (DialogBox)、通用对话框 (GetOpenFileName)、OLE 函数 (DoDragDrop)、打印机函数 (StartDoc) 以及许多其他函数创建。
<span style="background-color:#fafafa"><span style="color:#000000"><span style="color:#000000">需要内部任务处理时的</span><span style="color:#660066">示例解决方法</span><span style="color:#666600">:</span><span style="color:#000000">
HRESULT hr </span><span style="color:#666600">; </span><span style="color:#666600">{ </span><span style="color:#660066">CurrentThread </span><span style="color:#666600">:: </span><span style="color:#660066">ScopedAllowApplicationTasksInNativeNestedLoop </span><span style="color:#000000">allow </span><span style="color:#666600">; </span><span style="color:#000000">
hr </span><span style="color:#666600">= </span><span style="color:#660066">DoDragDrop </span><span style="color:#666600">(...); </span><span style="color:#880000">//隐式运行模态消息循环。</span><span style="color:#666600">} </span><span style="color:#880000">//处理|hr|(DoDragDrop()返回的结果)。</span>
</span></span>
在使用 CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop 之前,请确保您的任务是可重入的(可嵌套的),并且所有全局变量都是稳定且可访问的。
通用 API
用户代码几乎不需要直接访问 SequenceManager API,因为它们是为处理调度的代码而设计的。您应该使用以下代码:
base::RunLoop:从绑定的线程驱动 SequenceManager。
base::Thread/SequencedTaskRunner::CurrentDefaultHandle:从正在运行的任务发回 SequenceManager TaskQueues。
SequenceLocalStorageSlot :将外部状态绑定到序列。
base::CurrentThread :与当前线程绑定的任务相关 API 子集的代理
嵌入器可以提供自己的静态访问器来在特定循环上发布任务(例如 content::BrowserThreads)。
SingleThreadTaskExecutor 和 TaskEnvironment
无需处理需要简单任务发布环境(一个默认任务队列)的 SequenceManager 和 TaskQueues 代码,而是可以使用SingleThreadTaskExecutor。
单元测试可以使用高度可配置的TaskEnvironment 。
MessageLoop 和 MessageLoopCurrent
您可能会在代码或文档中看到对 MessageLoop 或 MessageLoopCurrent 的引用。这些类已不再存在,我们正在处理或删除对它们的所有引用。base::MessageLoopCurrent
已被替换base::CurrentThread
,并且已删除base::MessageLoop
arebase::SingleThreadTaskExecutor
和 的替换项base::Test::TaskEnvironment
。