- 浏览: 2035748 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (651)
- ACE (35)
- BAT (9)
- C/C++ (116)
- fast-cgi (14)
- COM (27)
- python (59)
- CGI (4)
- C# (2)
- VC (84)
- DataBase (29)
- Linux (96)
- P2P (6)
- PHP (15)
- Web (6)
- Memcached (7)
- IME输入法 (11)
- 设计模式 (2)
- 搜索引擎 (1)
- 个人情感 (4)
- 笔试/面试 (3)
- 一亩三分地 (33)
- 历史 (2)
- 地理 (1)
- 人物 (3)
- 经济 (0)
- 不仅仅是笑哦 (43)
- 小故事大道理 (2)
- http://www.bjdsmyysjk120.com/ (0)
- http://www.bjdsmyy120.com/ (0)
- 它山之石可以攻玉 (15)
- 大学生你关注些什么 (28)
- 数据恢复 (1)
最新评论
-
luokaichuang:
这个规范里还是没有让我明白当浏览器上传文件时,STDIN的消息 ...
FastCGI规范 -
effort_fan:
好文章!学习了,谢谢分享!
com技术简介 -
vcell:
有错误os.walk(strPath)返回的已经是全部的文件和 ...
通过python获取目录的大小 -
feifeigd:
feifeigd 写道注意:文章中的CPP示例第二行 #inc ...
ATL入门:利用ATL编写简单的COM组件 -
feifeigd:
注意:文章中的CPP示例第二行 #include " ...
ATL入门:利用ATL编写简单的COM组件
Chrome的多线程模型(一)
0. Chrome的并发模型
如果你仔细看了前面的图,对Chrome的线程和进程框架应该有了个基本的了解。Chrome有一个主进程,称为Browser进程,它是老大,管理Chrome大部分的日常事务;其次,会有很多Renderer进程,它们圈地而治,各管理一组站点的显示和通信(Chrome在宣传中一直宣称一个tab对应一个进程,其实是很不确切的...),它们彼此互不搭理,只和老大说话,由老大负责权衡各方利益。它们和老大说话的渠道,称做IPC(Inter-Process Communication),这是Google搭的一套进程间通信的机制,基本的实现后面自会分解。。。
Chrome的进程模型
|
不论是Browser进程还是Renderer进程,都不只是光杆司令,它们都有一系列的线程为自己打理各种业务。对于Renderer进程,它们通常有两个线程,一个是Main thread,它负责与老大进行联系,有一些幕后黑手的意思;另一个是Render thread,它们负责页面的渲染和交互,一看就知道是这个帮派的门脸级人物。相比之下,Browser进程既然是老大,小弟自然要多一些,除了大脑般的Main thread,和负责与各Renderer帮派通信的IO thread,其实还包括负责管文件的file thread,负责管数据库的db thread等等(一个更详细的列表,参见这里),它们各尽其责,齐心协力为老大打拼。它们和各Renderer进程的之间的关系不一样,同一个进程内的线程,往往需要很多的协同工作,这一坨线程间的并发管理,是Chrome最出彩的地方之一了。。。
闲话并发
在第二种场合下,我们会自然而然的关注数据的分离,从而很好的利用上多CPU的能力;而在第一种场合,我们习惯了单CPU的模式,往往不注重数据与行为的对应关系,导致在多CPU的场景下,性能不升反降。。。 |
1. Chrome的线程模型
仔细回忆一下我们大部分时候是怎么来用线程的,在我足够贫瘠的多线程经历中,往往都是这样用的:起一个线程,传入一个特定的入口函数,看一下这个函数是否是有副作用的(Side Effect),如果有,并且还会涉及到多线程的数据访问,仔细排查,在可疑地点上锁伺候。。。
Chrome的线程模型走的是另一个路子,即,极力规避锁的存在。换更精确的描述方式来说,Chrome的线程模型,将锁限制了极小的范围内(仅仅在将Task放入消息队列的时候才存在...),并且使得上层完全不需要关心锁的问题(当然,前提是遵循它的编程模型,将函数用Task封装并发送到合适的线程去执行...),大大简化了开发的逻辑。。。
不过,从实现来说,Chrome的线程模型并没有什么神秘的地方(美女嘛,都是穿衣服比不穿衣服更有盼头...),它用到了消息循环的手段。每一个Chrome的线程,入口函数都差不多,都是启动一个消息循环(参见MessagePump类),等待并执行任务。而其中,唯一的差别在于,根据线程处理事务类别的不同,所起的消息循环有所不同。比如处理进程间通信的线程(注意,在Chrome中,这类线程都叫做IO线程,估计是当初设计的时候谁的脑门子拍错了...)启用的是MessagePumpForIO类,处理UI的线程用的是MessagePumpForUI类,一般的线程用到的是MessagePumpDefault类(只讨论windows, windows, windows...)。不同的消息循环类,主要差异有两个,一是消息循环中需要处理什么样的消息和任务,第二个是循环流程(比如是死循环还是阻塞在某信号量上...)。下图是一个完整版的Chrome消息循环图,包含处理Windows的消息,处理各种Task(Task是什么,稍后揭晓,敬请期待...),处理各个信号量观察者(Watcher),然后阻塞在某个信号量上等待唤醒。。。
图2 Chrome的消息循环 |
MessagePumpDefault | MessagePumpForIO | MessagePumpForUI | |
是否需要处理系统消息 | 否 | 是 | 是 |
是否需要处理Task | 是 | 是 | 是 |
是否需要处理Watcher | 否 | 是 | 否 |
是否阻塞在信号量上 | 否 | 是 | 是 |
2. Chrome中的Task
从上面的表不难看出,不论是哪一种消息循环,必须处理的,就是Task(暂且遗忘掉系统消息的处理和Watcher,以后,我们会缅怀它们的...)。刨去其它东西的干扰,只留下Task的话,我们可以这样认为:Chrome中的线程从实现层面来看没有任何区别,它的区别只存在于职责层面,不同职责的线程,会处理不同的Task。最后,在铺天盖地西红柿来临之前,我说一下啥是Task。。。
简单的看,Task就是一个类,一个包含了void Run()抽象方法的类(参见Task类...)。一个真实的任务,可以派生Task类,并实现其Run方法。每个MessagePump类中,会有一个MessagePump::Delegate的类的对象(MessagePump::Delegate的一个实现,请参见MessageLoop类...),在这个对象中,会维护若干个Task的队列。当你期望,你的一个逻辑在某个线程内执行的时候,你可以派生一个Task,把你的逻辑封装在Run方法中,然后实例一个对象,调用期望线程中的PostTask方法,将该Task对象放入到其Task队列中去,等待执行。我知道很多人已经抄起了板砖,因为这种手法实在是太常见了,就不是一个简单的依赖倒置,在线程池,Undo\Redo等模块的实现中,用的太多了。。。
但,我想说的是,虽说谁家过年都是吃顿饺子,这饺子好不好吃还是得看手艺,不能一概而论。在Chrome中,线程模型是统一且唯一的,这就相当于有了一套标准,它需要满足在各个线程上执行的几十上百种任务的需求,因此,必须在灵活行和易用性上有良好的表现,这就是设计标准的难度。为了满足这些需求,Chrome在底层库上做了足够的功夫:
- 它提供了一大套的模板封装(参见task.h),可以将Task摆脱继承结构、函数名、函数参数等限制(就是基于模板的伪function实现,想要更深入了解,建议直接看鼻祖《Modern C++》和它的Loki库...);
- 同时派生出CancelableTask、ReleaseTask、DeleteTask等子类,提供更为良好的默认实现;
- 在消息循环中,按逻辑的不同,将Task又分成即时处理的Task、延时处理的Task、Idle时处理的Task,满足不同场景的需求;
- Task派生自tracked_objects::Tracked,Tracked是为了实现多线程环境下的日志记录、统计等功能,使得Task天生就有良好的可调试性和可统计性;
这一套七荤八素的都搭建完,这才算是一个完整的Task模型,由此可知,这饺子,做的还是很费功夫的。。。
3. Chrome的多线程模型
工欲善其事,必先利其器。Chrome之所以费了老鼻子劲去磨底层框架这把刀,就是为了面对多线程这坨怪兽的时候杀的更顺畅一些。在Chrome的多线程模型下,加锁这个事情只发生在将Task放入某线程的任务队列中,其他对任何数据的操作都不需要加锁。当然,天下没有免费的午餐,为了合理传递Task,你需要了解每一个数据对象所管辖的线程,不过这个事情,与纷繁的加锁相比,真是小儿科了不知道多少倍。。。
|
图3 Task的执行模型 |
如果你熟悉设计模式,你会发现这是一个Command模式,将创建于执行的环境相分离,在一个线程中创建行为,在另一个线程中执行行为。Command模式的优点在于,将实现操作与构造操作解耦,这就避免了锁的问题,使得多线程与单线程编程模型统一起来,其次,Command还有一个优点,就是有利于命令的组合和扩展,在Chrome中,它有效统一了同步和异步处理的逻辑。。。
Command模式 |
在一般的多线程模型中,我们需要分清楚啥是同步啥是异步,在同步模式下,一切看上去和单线程没啥区别,但同时也丧失了多线程的优势(沦落成为多线程串行...)。而如果采用异步的模式,那写起来就麻烦多了,你需要注册回调,小心管理对象的生命周期,程序写出来是嗷嗷恶心。在Chrome的多线程模型下,同步和异步的编程模型区别就不复存在了,如果是这样一个场景:A线程需要B线程做一些事情,然后回到A线程继续做一些事情;在Chrome下你可以这样来做:生成一个Task,放到B线程的队列中,在该Task的Run方法最后,会生成另一个Task,这个Task会放回到A的线程队列,由A来执行。如此一来,同步异步,天下一统,都是Task传来传去,想不会,都难了。。。
|
图4 Chrome的一种异步执行的解决方案 |
4. Chrome多线程模型的优缺点
一直在说Chrome在规避锁的问题,那到底锁是哪里不好,犯了何等滔天罪责,落得如此人见人嫌恨不得先杀而后快的境地。《代码之美》的第二十四章“美丽的并发”中,Haskell设计人之一的Simon Peyton Jones总结了一下用锁的困难之处,我罚抄一遍,如下:
- 锁少加了,导致两个线程同时修改一个变量;
- 锁多加了,轻则妨碍并发,重则导致死锁;
- 锁加错了,由于锁和需要锁的数据之间的联系,只存在于程序员的大脑中,这种事情太容易发生了;
- 加锁的顺序错了,维护锁的顺序是一件困难而又容易出错的问题;
- 错误恢复;
- 忘记唤醒和错误的重试;
-
而最根本的缺陷,是锁和条件变量不支持模块化的编程。比如一个转账业务中,A账户扣了100元钱,B账户增加了100元,即使这两个动作单独用锁保护维持其正确性,你也不能将两个操作简单的串在一起完成一个转账操作,你必须让它们的锁都暴露出来,重新设计一番。好好的两个函数,愣是不能组在一起用,这就是锁的最大悲哀;
通过这些缺点的描述,也就可以明白Chrome多线程模型的优点。它解决了锁的最根本缺陷,即,支持模块化的编程,你只需要维护对象和线程之间的职能关系即可,这个摊子,比之锁的那个烂摊子,要简化了太多。对于程序员来说,负担一瞬间从泰山降成了鸿毛。。。
而Chrome多线程模型的一个主要难点,在于线程与数据关系的设计上,你需要良好的划分各个线程的职责,如果有一个线程所管辖的数据,几乎占据了大半部分的Task,那么它就会从多线程沦为单线程,Task队列的锁也将成为一个大大的瓶颈。。。
设计者的职责 |
从根本上来说,Chrome的线程模型解决的是并发中的用户体验问题而不是联合工作的问题(参见我前面喷的“闲话并发”),它不是和Map/Reduce那样将关注点放在数据和执行步骤的拆分上,而是放在线程和数据的对应关系上,这是和浏览器的工作环境相匹配的。设计总是和所处的环境相互依赖的,毕竟,在客户端,不会和服务器一样,存在超规模的并发处理任务,而只是需要尽可能的改善用户体验,从这个角度来说,Chrome的多线程模型,至少看上去很美。。。
发表评论
-
内存卡读不出来怎么办
2015-05-21 17:04 1389内存卡在生活中使用广泛,应用于手机作为扩展内存很普遍 ... -
对UTF8编码的初步认识
2011-06-07 15:10 1718在网络中有很多地方都有采用UTF8编码,由于要编写与邮件服务端 ... -
怎样煮小米粥?
2011-03-22 08:14 1814小米粥是健康食品,可单独煮熬,亦可添加大枣、红豆、红薯 ... -
如何清除svn保存的username用户名和paasword密码(windows和linux)
2010-12-23 15:33 2444windows下 方法1:对于TortoiseSVN软件 ... -
svn命令
2010-12-23 13:48 4752The Subversion Command-Line Cl ... -
Google Chrome 的内核引擎 WebKit 介绍
2010-06-28 10:22 2791Google Chrome 的内核引擎 WebKit ... -
I love you
2010-06-25 22:23 920让电脑替你说"I IOVE YOU":新建一个记事本,在里面输 ... -
for test zip file
2010-04-28 11:09 953for test zip file load -
FastCGI中文参考手册
2010-04-09 11:14 1154FastCGI中文参考手册 主题 FastCGI中文参考 ... -
详细设计说明书
2010-03-30 10:13 1265详细设计说明书 http://www.chinauni ... -
概要设计文档编写规范
2010-03-22 11:16 3362概要设计文档编写规 ... -
概要设计说明书
2010-03-22 11:13 2564概要设计说明书 一. 引言 1. ... -
Chrome的进程间通信(五)
2010-03-15 14:43 2976Chrome的进程间通信(五) 1. NPAPI ... -
Chrome的进程间通信(四)
2010-03-15 14:41 2059Chrome的进程间通信(四) 1. Chrome的窗口 ... -
Chrome的进程间通信(三)
2010-03-15 14:39 2215Chrome的进程间通信(三) 1. 基本的进程结构 ... -
Chrome的进程间通信(二)
2010-03-15 14:36 1998Chrome的进程间通信(二) 1. Chrome进程通 ... -
Chrome源码剖析 序
2010-03-15 14:27 1888Chrome源码剖析 序 开源是口好东西,它让这个充斥 ... -
编码人员的误区
2009-09-10 16:22 967编码人员的误区 误区一:因为任务紧迫,所以没有时 ... -
软件军军规
2009-09-09 11:37 1036编码人员的误区 误区一:因为任务紧迫,所以没有时间想 有些人认 ... -
简单UDP服务器端和客户端(源代码)
2009-09-02 14:28 6793//客户端 #include <iostream ...
相关推荐
3. **多线程**:虽然项目强调多进程,但在某些情况下,多线程也会被用于优化内部操作,比如异步加载内容或执行JavaScript。 4. **安全机制**:实现沙箱模型,确保每个进程在安全的环境中运行,防止恶意代码影响整个...
这可能涉及处理线程模型、注册表设置以及兼容性问题。同时,必须确保你的插件符合Chrome的安全规范,例如,避免使用不安全的API或数据传输方式。 在实际应用中,OCX插件可能会用于各种场景,比如读取或控制本地硬件...
Chrome是一款由Google开发的开源网页浏览器,其核心部分的源码是许多开发者和技术爱好者研究的对象。这个压缩包文件包含了Chrome的核心组件,让...同时,这也是对软件工程、多线程编程、网络通信等领域知识的综合实践。
5. **Compositor线程**:Chrome采用了多线程模型,其中Compositor线程负责处理UI的渲染和合成。这使得UI更新与JavaScript执行和网络请求等其他任务并行,提高了整体性能。 6. **UI组件重用与模块化**:Chrome界面...
4. 应用多线程,采用生产者-消费者模型,提高数据爬取速度,其中生产者负责爬取数据,消费者负责处理和保存数据。 5. 将爬取到的表情包数据存储到本地文件夹,便于后续使用。 **实验步骤**详细说明: 1. 打开"斗图...
标题中的“Node.js-一个能在单独的线程中执行Node.js函数的零依赖库”指出,这个库专注于在Node.js环境中实现多线程处理,且不依赖任何外部库。这通常意味着它提供了一种轻量级的方式来并行执行任务,避免了Node.js...
在标题和描述中提到的这个框架,它具备了多线程、异步、IP动态代理、分布式以及JavaScript渲染等关键特性,这些都是构建高效爬虫所必需的组件。 1. **多线程**:在爬虫领域,多线程技术可以显著提升数据抓取的速度...
Chrome的设计采用了多进程模型,其中每个标签页都有一个独立的渲染进程(Renderer),而主进程(Browser Process)负责管理这些渲染进程。这种设计模式使得浏览器能够更好地隔离不同网页之间的资源访问权限,同时也...
- 处理线程同步:由于CEFSharp运行在单独的线程中,你需要熟悉多线程编程,以确保安全地在UI线程和CEFSharp线程之间进行数据交换。 通过以上步骤和理解,你将能够成功构建一个基于C# WinForm和CEFSharp的简易Chrome...
Node.js作为一个基于Chrome V8引擎的JavaScript运行环境,主要特点是基于事件循环的非阻塞I/O模型,虽然这样的设计让它在I/O密集型任务中表现优异,但其单线程模型却限制了在CPU密集型任务上的性能表现。为了解决这...
为达到这些目标,Chrome 采用了多进程架构,确保了每一个网络应用都能在独立的环境中运行,避免了一个应用崩溃影响整个浏览器的正常工作。 #### 核心思想——多进程架构 多进程架构是 Chrome 的核心设计之一,其...
在MFC应用的主函数或初始化阶段,调用CEF的初始化函数,如`cefInitialize()`,并设置CEF的相关参数,如多线程模式、命令行参数等。同时,需要为CEF创建一个上下文(`CefApp`对象),并注册自定义的处理程序,如`...
然而,对于CPU密集型任务,单线程模型可能成为性能瓶颈。为了解决这个问题,Node.js引入了多线程特性,通过`worker_threads`模块实现。 首先,理解Node.js的工作原理至关重要。Node.js的核心机制是事件循环和工作池...
6. **性能优化**:CEF4Delphi通常会进行一些优化,以适应Delphi的性能特点,比如内存管理、线程模型和资源调度,以确保在嵌入Web视图时保持良好的应用性能。 7. **示例和文档**:CEF4Delphi项目通常会包含示例代码...
2. **多线程和事件驱动**:库设计为可选的多线程和事件驱动,这使得它能适应不同的系统环境和负载需求。开发者可以根据应用的需求选择合适的模型。 3. **轻量级**:libwebsockets库设计得非常轻便,没有依赖过多的...
传统服务器多采用多线程模型,每个请求对应一个线程,而I/O操作往往较慢,导致线程大量阻塞和内存浪费。Node.js采用单线程模型,通过事件驱动和非阻塞I/O的方式,大大提高了处理高并发的能力。 Node.js凭借其单线程...