• 多线程开发中,多用消息传递,少用锁


    线程和任务

    Chrome具有多进程架构, 并且每个进程都具有大量多线程。每个进程共享的基本线程系统。主要目标是使主线程(浏览器进程中又称为“ UI”线程)和IO线程(用于处理IPC的每个进程的线程 )保持响应。这意味着将任何阻塞的I / O或其他昂贵的操作分配到其他线程。

    实现方法是使用消息传递作为线程之间进行通信的方式。不建议使用锁定和线程安全的对象。相反,对象仅存活在一个(通常是虚拟的)线程上,并且在这些线程之间传递消息进行通信。

    核心概念

    • 任务(Task):要处理的工作单元。有效地具有可选状态的函数指针。在Chrome中,这是 base::Callback 通过 base::Bind (文档)创建的 。

    • 任务队列(Task queue):要处理的任务队列。

    • 物理线程(Physical thread):操作系统提供的线程(例如POSIX上的pthread或Windows上的CreateThread())。Chrome跨平台抽象为 base::PlatformThread 。应该几乎永远不要直接使用它。

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

    • 线程池(Thread pool):具有共享任务队列的物理线程池。在Chrome中,这是 base::ThreadPoolInstance 。每个Chrome流程只有一个实例,它可以处理通过其发布的任务  base/task/post_task.h ,因此您几乎不需要 base::ThreadPoolInstance 直接使用API(有关稍后发布任务的更多信息)。

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

    • 任务运行器(Task runner):可以通过其发布任务的接口。在Chrome中,它是 base::TaskRunner 。

    • 顺序任务运行程序(Sequenced task runner):一个任务运行程序,它保证发布到其上的任务将按发布顺序顺序运行。保证每个这样的任务都会看到其前面任务的副作用。发布到已排序任务运行器的任务通常由单个线程(虚拟或物理)处理。在Chrome中,这是 base::SequencedTaskRunner -a  base::TaskRunner 。

    • 单线程任务运行程序(Single-thread task runnet):顺序任务运行程序,可确保所有任务将由同一物理线程处理。在Chrome中,这是  base::SingleThreadTaskRunner -a  base::SequencedTaskRunner 。只要有可能,尽可能使用序列而不是线程。

    线程词典

    注意:以下术语旨在过渡通用线程命名法与在Chrome中使用它们的方式之间的差距。如果这很难解析,请考虑跳到下面的更详细的部分,并在必要时参考此内容。

    • 线程不安全(Thread-unsafe):Chrome中的绝大多数类型都是线程不安全的(根据设计)。对此类类型或方法的访问必须在外部同步。通常,线程不安全类型要求将访问其状态的所有任务都张贴到同一任务, base::SequencedTaskRunner 并在 SEQUENCE_CHECKER 成员的调试版本中对此进行验证。锁也是同步访问的一种方法,但是在Chrome浏览器中,我们强烈推荐序列而不是 锁。

    • 线程仿射(Thread-affine):此类类型或方法始终需要从相同的物理线程(即,从 base::SingleThreadTaskRunner )访问,并且通常具有一个 THREAD_CHECKER 成员以验证它们是否正确。缺少使用第三方API或具有叶子依赖关系(线程仿射)的原因:Chrome中几乎没有理由让类型成为线程仿射。注意 base::SingleThreadTaskRunner 是一个, base::SequencedTaskRunner 所以线程仿射是线程不安全的子集。线程仿射有时也称为 线程恶意 。

    • 线程安全的(Thread-safe):可以安全地同时访问此类类型或方法。

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

    • 不可变(Immutable):线程兼容类型的子集,在构造后无法修改。

    • 顺序友好的(Sequence-friendly):此类类型或方法是线程不安全的类型,支持从中调用 base::SequencedTaskRunner 。理想情况下,所有线程不安全类型都是这种情况,但是旧版代码有时会进行过分的检查,仅在线程不安全的情况下就强制执行线程亲和力。有关更多详细信息,请参见下面的“优先选择线程”。

    线程数

    每个Chrome进程都有

    • 主线程

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

      • 在渲染器进程(Blink主线程)中:运行大多数Blink

    • IO线程

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

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

    • 一些专用线程

    • 和通用线程池

    大多数线程都有一个循环,该循环从队列中获取任务并运行它们(该队列可以在多个线程之间共享)。

    任务

    任务被 base::OnceClosure 添加到队列中以异步执行。

    一个 base::OnceClosure 存储函数指针和参数。它具有 Run() 使用绑定参数调用函数指针的方法。它是使用创建的 base::BindOnce 。(请参阅Callback <>和Bind()文档)。

    1. void TaskA() {}
    2. void TaskB(int v) {}
    3. auto task_a = base::BindOnce(&TaskA);
    4. auto task_b = base::BindOnce(&TaskB, 42);

    可以通过以下方式之一执行一组任务:

    • 并行(Parallel):无任务执行顺序,可能在任何线程上一次全部执行

    • 已排序(Sequenced):以发布顺序执行的任务,在任何线程上一次执行。

    • 单线程(Single Threaded):以发布顺序执行的任务,一次在一个线程上执行。

    • COM Single Threaded:COM初始化的单线程的变体。

    优先选择序列而不是物理线程

    顺序执行(在虚拟线程上)比单线程执行(在物理线程上)更受青睐。除了绑定到主线程(UI)或IO线程的类型或方法外, base::SequencedTaskRunner 通过管理自己的物理线程比通过管理自己的物理线程更好地实现了线程安全 (请参见下面的发布序列化任务)。

    对于“当前物理线程”公开的所有API都具有“当前序列”(映射)的等效项。

    如果您发现自己写的是序列友好类型,并且 THREAD_CHECKER 在叶子依赖项中未通过线程亲和力检查(例如),请考虑使该依赖项也对序列友好。Chrome中的大多数核心API都是顺序友好的,但是某些传统类型可能仍然过分地使用ThreadChecker / ThreadTaskRunnerHandle / SingleThreadTaskRunner来代替它们依赖“当前序列”,而不再是仿射。

    发布并行任务

    直接发布到线程池

    可以在任何线程上运行并且与其他任务没有排序或互斥要求的任务应使用中 base::PostTask*() 定义的功能 之一发布  base/task/post_task.h 。

    base::PostTask(FROM_HERE, base::BindOnce(&Task));
    

    这将发布具有默认特征的任务。

    该 base::PostTask*() 函数允许调用者通过TaskTraits提供有关任务的其他详细信息(请参阅使用TaskTraits注释任务)。

    1. base::PostTask(
    2. FROM_HERE, {base::TaskPriority::BEST_EFFORT, MayBlock()},
    3. base::BindOnce(&Task));

    通过TaskRunner发布

    并行 base::TaskRunner 是 base::PostTask*() 直接调用的替代方法。当事先不知道任务是并行,按顺序还是单线程发布时,这尤其有用(请参阅发布序列化任务,将多个任务发布到同一线程)。

    因为 base::TaskRunner 是 base::SequencedTaskRunner and 的基类 base::SingleThreadTaskRunner ,所以 scoped_refptr 成员可以容纳a  base::TaskRunner ,a base::SequencedTaskRunner 或a  base::SingleThreadTaskRunner 。

    1. class A {
    2. public:
    3. A() = default;
    4. void DoSomething() {
    5. task_runner_->PostTask(FROM_HERE, base::BindOnce(&A));
    6. }
    7. private:
    8. scoped_refptr<base::TaskRunner> task_runner_ =
    9. base::CreateTaskRunner({base::TaskPriority::USER_VISIBLE});
    10. };

    除非测试需要精确控制任务的执行方式,否则最好 base::PostTask*() 直接调用(请参阅测试,以控制测试中的侵入性较小的方法)。

    发布顺序任务

    序列是一组任务,这些任务以发布顺序一次运行(不一定在同一线程上)。要将任务发布为序列的一部分,请使用 base::SequencedTaskRunner 。

    发布到新序列(Posting to a New Sequence)

    一个 base::SequencedTaskRunner 可以通过以下方式创建  base::CreateSequencedTaskRunner() 。

    1. scoped_refptr<SequencedTaskRunner> sequenced_task_runner =
    2. base::CreateSequencedTaskRunner(...);
    3. // TaskB runs after TaskA completes.
    4. sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskA));
    5. sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskB));

    发布到当前(虚拟)线程

    向当前线程发布的首选方式是通过 base::CurrentThread trait。

    1. // The task will run on the current (virtual) thread's default task queue.
    2. base::PostTask(FROM_HERE, {base::CurrentThread()}, base::BindOnce(&Task));

    您可以选择指定其他特征。这很重要,因为某些线程(例如浏览器UI线程,浏览器IO线程和Blink主线程)将多个任务队列集中到同一线程中,并且默认优先级可能不适合您的任务。

    例如,您可以显式设置优先级:

    1. // The task will run on the current (virtual) thread's best effort queue.
    2. // NOTE only the Browser UI and Browser IO threads support task priority (for
    3. // now), other (virtual) threads will silently ignore traits used in combination
    4. // with `base::CurrentThread`.
    5. base::PostTask(FROM_HERE,
    6. {base::CurrentThread(), base::TaskPriority::BEST_EFFORT},
    7. base::BindOnce(&Task));

    该 base::SequencedTaskRunner 可通过以下方式获得其当前任务被派驻到  base::GetContinuationTaskRunner() 。

    在某些线程上,只有一个任务运行程序,因此当前序列与当前线程相同。在浏览器UI,浏览器IO或Blink主线程中不是这种情况。此外,并行 base::GetContinuationTaskRunner() 线程池任务或没有任务运行时,当前序列的概念不存在, 在这种情况下将进行DCHECK处理。

    注意:虽然 base::GetContinuationTaskRunner() 从并行任务调用无效,但对有序任务或单线程任务有效。即来自 base::SequencedTaskRunner 或 base::SingleThreadTaskRunner 。

    1. // The task will run after any task that has already been posted
    2. // to the SequencedTaskRunner to which the current task was posted
    3. // (in particular, it will run after the current task completes).
    4. // It is also guaranteed that it won’t run concurrently with any
    5. // task posted to that SequencedTaskRunner.
    6. base::GetContinuationTaskRunner()->
    7. PostTask(FROM_HERE, ba
  • 相关阅读:
    前端、后端开发者常用到的免费API整理
    Web APIs——正则表达式使用
    [资源推荐] 飞书画板模板
    研发效能定义及核心价值
    【算法训练-回溯算法 零】回溯算法解题框架
    js实现数组扁平化的多种方式
    Linux(三)- Vi 和 Vim 编辑器
    SCADA系统是什么意思?
    java split字符串作业
    Vue.js之Vuex的使用
  • 原文地址:https://blog.csdn.net/m0_73257876/article/details/126685721