Chromium 中的并发
2024-11-25
Chromium 是一个多进程(Multi-Process)的程序。
进程之间信息传递,通过 IPC (Inter-Process Communication,进程间通信),因为不同进程的内存空间不同。
进程内部,为充分利用多核并提高性能,通常采用多线程(Multi-Thread)的方式。
尽管对于多个线程而言,数据共享是容易的,但安全的数据共享不是,因为有数据竞争(Data Race),不同线程对同一块内存的访问顺序需要协调。
通常采用原子操作,加锁等。
C++11 后,STL 中有诸如 std::atomic<T>
和 std::mutex
这些基础设施。
不过 Chromium 的开发,早在 C++11 前,而且超前的使用了一种新颖的,面向通信的模型,让多线程开发像单线程一样简单、清晰、正确。
让开发者更加关注任务执行的顺序,而非线程调度。
1. 经典并发模型
计算机行业发展的原始动力,来自于人们总想更快、更好地完成任务。
提高任务的并发度,是一种行之有效的方式。
多核硬件出现,并发编程逐渐流行。
人们发现,它可以有效提高程序的运行效率,尽管会带来额外的复杂度。
并发模型可以理解为一种让并发编程高效且正确工作的机制。
经典的有三种:
- 共享内存(Share memory)
- 通信(Communicate)
- 混合(Hybrid)
第一种最常见,也是主流用法。
通过共享内存通信。
多个线程共享同一块内存,内存的改动对于所有线程可见。不过,读写操作之间需要控制顺序,否则便会出现脏读或脏写的问题。
这就是锁、条件变量的用武之处。
第二种,
通过通信共享内存。
这是 Go 语言推崇的 并发模型🔗。
线程之间不会同时访问同一块内存。它们之间通过传递讯息(message-passing)完成交流。
好处是,不同的讯息之间,有严格顺序:上一条处理完,下一条才会继续。
不需要锁这种来自外部的协调。
第三种模型同时支持上述两种。
2. Chromium 中的模型
Chromium 支持混合模型,但更青睐通信的方式。
所以在 Chromium 仓库中,很少见到锁。
在 Chromium 的作者看来,对于开发者,线程(Thread)是一个粒度很粗,而且比较重的工具,抽象程度低,使用难度大。
其实开发者更在意的,是完成任务(Task),线程只是处理任务的一种手段。
而根据任务特点,可归纳为三种可能情况:
- 独立任务
- 有顺序依赖的任务
- 顺序依赖,且必须由同一个线程执行的任务
对于第一种,任务之间独立,任何线程都可以执行,经典的线程池(Thread Pool)可以很好地解决。
第二种需要技巧。比较容易想到,让所有的待执行任务,按照顺序,排列在 FIFO 队列(Queue)中。任务不一定由同一个线程执行,但需要保证,下一个任务必须等待,直到上一个任务完成。
第三种,是第二种情况的特化,所有任务只能由一条线程执行。
Chromium 实现了这三种任务的处理类:
ThreadPool
,线程池SequencedTaskRunner
,顺序执行队列SingleThreadTaskRunner
,单线程执行队列
3. 启示
锁,条件变量,本质要去控制的,其实是顺序。当以一种隐晦而曲折的方式使用时,易用性就变差了。
队列则将隐含的顺序显像化,直观、清晰,容易验证正确性。
利用经典数据结构,巧妙地解决多线程协调(Coordinate)的问题。
4. 总结
一直认为,计算机科学很务实,就是回答如何更好地解决问题。
数据结构,算法,代码都是载体,是工具。
真正有价值的,是我们如何利用它们,解决生活中一个又一个的问题。
(完)
参考
- 本文作者:Plantree
- 本文链接:https://plantree.me/blog/2024/chromium-concurrency/
- 版权声明:所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
最后更新于: 2024-11-25T08:05:23+08:00