Chromium 消息循环和线程池详解
Chromium 中的多线程机制由 base 库提供,要理解 Chromium 中的多线程机制,首先要理解的概念就是 base::MessageLoop 和 base::TaskScheduler ,它们两个是 Chromium 多线程的基础
1. MessageLoop详解
base::MessageLoop 代表消息循环,它不会主动创建新的线程,默认情况下它使用当前线程(你也可以手动把它 Bind 到指定的线程上),它只负责消息(任务)循环,它提供了 task_runner() 方法用于获取 TaskRunner 对象,你需要使用 TaskRunner::PostTask*() 方法来向该消息循环分发消息,默认情况下这些消息(任务)会在当前线程运行。因此,你可以在当前线程中创建 MessageLoop 并且在当前线程中向它 Post 消息,并且这些消息(任务)会在当前线程执行。
每一个通过base::Thread创建出来的线程都拥有一个 MessageLoop,但是主线程(main 函数所在的线程)不是通过base::Thread来创建的,它又需要处理消息循环,因此需要手动给主线程创建MessageLoop,这个过程一般在程序的入口处进行。
你可以使用 base::MessageLoopCurrent::Get() 静态方法获取当前线程的MessageLoop对象,从而使用 base::MessageLoopCurrent::Get()->task_runner()→PostTask*() 方法来创建任务。
一旦你创建了一个 MessageLoop 对象,它会自动 Bind 到当前线程(通过线程依赖的 ThreadLocal 机制来实现)。

比较常规的使用方式可以参考下面:
1 #include "base/logging.h"
2 #include "base/message_loop/message_loop.h"
3 #include "base/message_loop/message_loop_current.h"
4 #include "base/task/post_task.h"
5 #include "base/task/single_thread_task_executor.h"
6 #include "base/task/thread_pool/thread_pool_impl.h"
7 #include "base/task/thread_pool/thread_pool_instance.h"
8 #include "base/threading/thread_task_runner_handle.h"
9 #include "base/timer/timer.h"
10
11 void Hello() {
12 LOG(INFO) << "hello,demo!";
13 }
14
15 int main(int argc, char** argv) {
16 // 创建消息循环
17 base::MessageLoop message_loop;
18 // 也可以使用下面的方法。它们的区别仅在于 MessageLoop 对外暴露了更多的内部接口。
19 // 在当前线程创建一个可执行 task 的环境,同样需要使用 RunLoop 启动
20 // base::SingleThreadTaskExecutor main_task_executer;
21
22 base::RunLoop run_loop;
23
24 // 使用 message_loop 对象直接创建任务
25 message_loop.task_runner()->PostTask(FROM_HERE, base::BindOnce(&Hello));
26 // 获取当前线程的 task runner
27 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
28 base::BindOnce(&Hello));
29
30 // 启动消息循环,即使没有任务也会阻塞程序运行。当前进程中只有一个线程。
31 run_loop.Run();
32
33 return 0;
34 }
2. MessageLoop 的运行流程

3. MessageLoop 的类图

类图中已经介绍了主要类的功能,这里不再赘述,简单总接一下就是:MessageLoop 创建消息/任务循环,并且绑定到当前线程,RunLoop 启动消息循环,调用者通过 TaskRunner 来创建任务。
4. TaskScheduler详解
base::TaskScheduler 直译为任务调度器,也可以叫做线程池,你需要使用它的 static 类型的 Create*() 相关方法来构造它,使用 Start() 方法来启动它,或者通过 CreateAndStartWithDefaultParams() 方法来同时创建并启动线程池。默认情况下他会创建 3 个线程,1 个 Service 线程,2 个 Worker 线程。Service 线程只用来调度延时任务,Worker 线程用来执行任务。Service 线程继承自 base::Thread ,因此它内部也包含了 MessageLoop(每一个 base::Thread 类创建出来的线程都有一个 MessageLoop)。Worker 线程是 TaskScheduler 直接使用 PlatformThread::Create*() 方法创建出来的,因此它不包含 MessageLoop。你可以使用 base::PostTask*() 全局方法来向线程池 Post 任务。
TaskScheduler一般用法如下:
1 #include <base/logging.h>
2 #include <base/message_loop/message_loop.h>
3 #include <base/task/post_task.h>
4 #include <base/task/task_scheduler/task_scheduler.h>
5
6 void Hello()
7 {
8 LOG(INFO)<<"hello,demo!";
9 }
10
11 int main(int argc,char** argv)
12 {
13 // 初始化线程池,会创建新的线程,在新的线程中会创建消息循环 MessageLoop
14 base::TaskScheduler::CreateAndStartWithDefaultParams("Demo");
15
16 // 通过以下方法创建任务
17 base::PostTask(FROM_HERE, base::BindOnce(&Hello));
18 // 或者通过创建新的TaskRunner来创建任务,TaskRunner可以控制任务执行的顺序以及是否在同一个线程中运行
19 scoped_refptr<base::TaskRunner> task_runner_ =
20 base::CreateTaskRunnerWithTraits({base::TaskPriority::USER_VISIBLE});
21 task_runner_->PostTask(FROM_HERE,base::BindOnce(&Hello));
22
23 // 不能使用以下方法创建任务,会导致程序崩溃,因为当前线程没有创建消息循环
24 //base::MessageLoopCurrent::Get()->task_runner()->PostTask(FROM_HERE, base::BindOnce(&Hello));
25
26 // 由于线程池默认不会阻塞程序运行,因此这里为了看到结果使用getchar()阻塞主线程。当前进程中共有4个线程,1个主线程,1个线程池Service线程,2个Worker线程。
27 getchar();
28
29 return 0;
30 }
5. TaskScheduler 的运行流程

6. TaskScheduler 的类图

7. 总结
- 每一个
base::Thread线程都拥有一个MessageLoop用来进行任务调度; - 主线程如果需要消息循环,需要自行创建
MessageLoop; - 由于
MessageLoop中维护有TaskRunner,因此你可以通过获取该线程的TaskRunner来给该线程 Post 任务; - 线程池使用更底层的
PlatformThread::Create*()来直接创建线程,从而避免每个线程都有 MessageLoop; - 线程池中的任务是通过
base::PostTask*()创建的; - 线程池使用名为
TaskSchedulerSe的线程来调度需要延时的任务,不需要延迟的任务会直接放入线程池的任务任务队列; - 通过
TaskRunner的PostTask*()方法会将任务Post到TaskRunner所在的MessageLoop; - 通过
base::PostTask*()全局方法默认会将任务Post到线程池中;
8. 参考文献

Chromium 消息循环和线程池详解的更多相关文章
- Java线程池详解(二)
一.前言 在总结了线程池的一些原理及实现细节之后,产出了一篇文章:Java线程池详解(一),后面的(一)是在本文出现之后加上的,而本文就成了(二).因为在写完第一篇关于java线程池的文章之后,越发觉 ...
- nginx源码分析线程池详解
nginx源码分析线程池详解 一.前言 nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...
- 三、VIP课程:并发编程专题->01-并发编程之Executor线程池详解
01-并发编程之Executor线程池详解 线程:什么是线程&多线程 线程:线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系 ...
- Tomcat 连接数与线程池详解
前言 在使用tomcat时,经常会遇到连接数.线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector). 在前面的文章 详解Tomcat配置文件server.xm ...
- Java 并发编程 | 线程池详解
原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...
- java - jdk线程池详解
线程池参数详解 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUni ...
- java/android线程池详解
一,简述线程池: 线程池是如何工作的:一系列任务出现后,根据自己的线程池安排任务进行. 如图: 线程池的好处: 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销. 能有效控制线程池的最大并 ...
- Java线程池详解
一.线程池初探 所谓线程池,就是将多个线程放在一个池子里面(所谓池化技术),然后需要线程的时候不是创建一个线程,而是从线程池里面获取一个可用的线程,然后执行我们的任务.线程池的关键在于它为我们管理了多 ...
- Java多线程之线程池详解
前言 在认识线程池之前,我们需要使用线程就去创建一个线程,但是我们会发现有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因 ...
- 【java线程系列】java线程系列之java线程池详解
一线程池的概念及为何需要线程池: 我们知道当我们自己创建一个线程时如果该线程执行完任务后就进入死亡状态,这样如果我们需要在次使用一个线程时得重新创建一个线程,但是线程的创建是要付出一定的代价的,如果在 ...
随机推荐
- 企业级logstash简单使用(ELK)
企业级logstash简单使用(ELK) 要使用logstash收集到Elasticsearch的方式,需确保logstash版本与es版本一致. 由于我也是刚刚研究使用,所以本文暂不会出现原理性的东 ...
- go select 使用总结
转载请注明出处: 在Go语言中,select语句用于处理多个通道的并发操作.它类似于switch语句,但是select语句用于通信操作,而不是条件判断.select语句会同时监听多个通道的操作,并选择 ...
- python笔记:第四章使用字典
1.1 概述 说白了就是键值对的映射关系 不会丢失数据本身关联的结构,但不关注数据的顺序 是一种可变类型 格式:dic = {键:值, 键:值} 键的类型:字典的键可以是任何不可变的类型,如浮点数,字 ...
- SDK 接入|游戏语音之“范围语音”接入实践
语音是线上游戏用户的主要交流方式,大多数用户会通过游戏中的内置语音功能与其他玩家沟通,而一些用户在游戏没有内置语音功能的情况下,通过其他语音软件与玩家沟通. 并且,游戏语音在玩家开黑时承担着至关重要的 ...
- redis 中的 字符串
String是redis 中的最基本的类型, 为二进制安全 ,意味着String可以表示各种类型 一个字符串value 最大为 521M set k1 v100 set k2 v200 get 命 ...
- 青少年CTF平台 Web签到
题目说明 Web一星简单题,Web签到. 直接启动环境,等待30秒左右访问题目链接. 做题过程 进入后,题目好像没有告诉我们什么有用的信息, F12,看遍了题目源码,也没有发现flag,正当我怀疑这个 ...
- Unity 编辑器选择器工具类Selection 常用函数和用法
Unity 编辑器选择器工具类Selection 常用函数和用法 点击封面跳转下载页面 简介 在Unity中,Selection类是一个非常有用的工具类,它提供了许多函数和属性,用于操作和管理编辑器中 ...
- 使用synapse搭建matrix去中心化加密通信服务
前言 首先必须介绍下Matrix.Matrix是一个开源.可交互.去中心化的实时通信服务框架.使用Matrix可以搭建安全的通信服务器,配合支持 Matrix 的客户端可以实现个人.团队间的实时聊天交 ...
- wineqq中接收文件的查看与移动
在Ubuntu等linux系统中安装QQ都需要安装wine支持,而在使用时,会遇到qq接收到的文件无法直接进行操作等问题. 这时,我们发现直接对文件进行复制后,无法在Ubuntu目录中进行粘贴. 其实 ...
- Hybird 技术讨论:热更新原理解析
原生应用 VS 混合应用 大家对于原生应用和混合应用已经非常熟悉了,这里就不再进行详细的介绍,用通俗易懂的话解释下他们的一些特点. 1.原生应用 在 Android.iOS 等移动平台上利用提供的 ...