《Threading and Tasks in Chrome》笔记

学习资料:链接
理解上还有一些混乱,限制可以实战看代码了


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();
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
System.Threading.Tasks是一个命名空间,提供了用于创建和管理任务的类和方法。它的作用是通过任务的概念来解决线程处理的复杂问题。任务(Task)包含一个操作,以及依赖哪个任务的完成才能开始。在使用System.Threading.Tasks时,可以使用Task类来创建和执行任务,使用Task.ContinueWith方法来定义任务之间的依赖关系,使用Task.WaitAll方法来阻塞调用任务,直到等待的所有任务完成为止,使用Task.WhenAll方法返回一个任务,允许使用async关键字等待结果,而不会阻塞等待任务。此外,还可以使用Task.Delay方法来指定从这个方法返回的任务完成前要等待的毫秒数。\[2\]在使用System.Threading.Tasks时,可以使用async和await关键字来简化异步编程的代码,使代码更加清晰和易于理解。\[3\] #### 引用[.reference_title] - *1* [C#任务Task应用详解](https://blog.csdn.net/weixin_36609037/article/details/126541489)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [.NET 4.0 System.Threading.Tasks学习笔记](https://blog.csdn.net/weixin_34223655/article/details/85449464)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [System.Threading.Tasks](https://blog.csdn.net/weixin_30699831/article/details/96191950)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值