<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>中文 &#187; solstice_</title>
	<atom:link href="http://software.intel.com/zh-cn/blogs/author/solstice_/feed/" rel="self" type="application/rss+xml" />
	<link>http://software.intel.com/zh-cn/blogs</link>
	<description></description>
	<lastBuildDate>Sat, 26 May 2012 06:34:24 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.3</generator>
		<item>
		<title>击鼓传花：对比 muduo 与 libevent2 的事件处理效率</title>
		<link>http://software.intel.com/zh-cn/blogs/2011/01/12/muduo-libevent2/</link>
		<comments>http://software.intel.com/zh-cn/blogs/2011/01/12/muduo-libevent2/#comments</comments>
		<pubDate>Wed, 12 Jan 2011 05:12:47 +0000</pubDate>
		<dc:creator>solstice_</dc:creator>
				<category><![CDATA[博客征文专栏]]></category>
		<category><![CDATA[并行计算]]></category>

		<guid isPermaLink="false">http://software.intel.com/zh-cn/blogs/2011/01/12/muduo-libevent2/</guid>
		<description><![CDATA[前面我们比较了 muduo 和 libevent2 的吞吐量，得到的结论是 muduo 比 libevent2 快 18%。有人会说，libevent2 并不是为高吞吐的应用场景而设计的，这样的比较不公平，胜之不武。为了公平起见，这回我们用 libevent2 自带的性能测试程序（击鼓传花）来对比 muduo 和 libevent2 在高并发情况下的 IO 事件处理效率。 测试对象 libevent 2.0.6-rc， 源代码包 http://monkey.org/~provos/libevent-2.0.6-rc.tar.gz muduo 0.1.2-alpha，源码 http://muduo.googlecode.com/files/muduo-0.1.2-alpha.tar.gz SHA1 Checksum: 9e7da4b46ad87602dd206eaedf54e67c17dfe4e1 。须编译为 release 版。 测试环境 测试用的软硬件环境与《muduo 与 boost asio 吞吐量对比》和《muduo 与 libevent2 吞吐量对比》相同，另外我还在自己的笔记本上运行了测试，结果也附在后面。 测试内容 测试的场景是：有 1000 个人围成一圈，玩击鼓传花的游戏，一开始第 1 个人手里有花，他把花传给右手边的人，那个人再继续把花传给右手边的人，当花转手 100 次之后游戏停止，记录从开始到结束的时间。 用程序表达是，有 1000 个网络连接 (socketpairs 或 [...]]]></description>
			<content:encoded><![CDATA[<p>前面我们比较了 muduo 和 libevent2 的吞吐量，得到的结论是 muduo 比 libevent2 快 18%。有人会说，libevent2 并不是为高吞吐的应用场景而设计的，这样的比较不公平，胜之不武。为了公平起见，这回我们用 libevent2 自带的性能测试程序（击鼓传花）来对比 muduo 和 libevent2 在高并发情况下的 IO 事件处理效率。</p>
<p>测试对象<br />
libevent 2.0.6-rc， 源代码包 http://monkey.org/~provos/libevent-2.0.6-rc.tar.gz<br />
muduo 0.1.2-alpha，源码 http://muduo.googlecode.com/files/muduo-0.1.2-alpha.tar.gz SHA1 Checksum: 9e7da4b46ad87602dd206eaedf54e67c17dfe4e1 。须编译为 release 版。<br />
测试环境<br />
测试用的软硬件环境与《muduo 与 boost asio 吞吐量对比》和《muduo 与 libevent2 吞吐量对比》相同，另外我还在自己的笔记本上运行了测试，结果也附在后面。</p>
<p>测试内容<br />
测试的场景是：有 1000 个人围成一圈，玩击鼓传花的游戏，一开始第 1 个人手里有花，他把花传给右手边的人，那个人再继续把花传给右手边的人，当花转手 100 次之后游戏停止，记录从开始到结束的时间。</p>
<p>用程序表达是，有 1000 个网络连接 (socketpairs 或 pipes)，数据在这些连接中顺次传递，一开始往第 1 个连接里写 1 个字节，然后从这个连接的另一头读出这 1 个字节，再写入第 2 个连接，然后读出来继续写到第 3 个连接，直到一共写了 100 次之后程序停止，记录所用的时间。</p>
<p>以上是只有一个活动连接的场景，我们实际测试的是 100 个或 1000 个活动连接（即 100 朵花或 1000 朵花，均匀分散在人群手中），而连接总数（即并发数）从 100 到 100,000 (十万)。注意每个连接是两个文件描述符，为了运行测试，需要调高每个进程能打开的文件数，比如设为 256000。</p>
<p>libevent2 的测试代码位于 test/bench.c，我修复了 2.0.6-rc 版里的一个小 bug，修正后的代码见 http://github.com/chenshuo/recipes/blob/master/pingpong/libevent/bench.c</p>
<p>muduo 的测试代码位于 examples/pingpong/bench.cc，见 http://gist.github.com/564985#file_pingpong_bench.cc</p>
<p>测试结果与讨论<br />
第一轮，分别用 100 个活动连接和 1000 个活动连接，无超时，读写 100 次，测试一次游戏的总时间（包含初始化）和事件处理的时间（不包含注册 event watcher）随连接数（并发数）变化的情况。具体解释见 libev 的性能测试文档 http://libev.schmorp.de/bench.html ，不同之处在于我们不比较 timer event 的性能，只比较 IO event 的性能。对每个并发数，程序循环 25 次，刨去第一次的热身数据，后 24 次算平均值。测试用的脚本在 http://github.com/chenshuo/recipes/blob/master/pingpong/libevent/run_bench.sh 。这个脚本是 libev 的作者 Marc Lehmann 写的，我略作改用，用于测试 muduo 和 libevent2。</p>
<p>第一轮的结果，请先只看红线和绿线。红线是 libevent2 用的时间，绿线是 muduo 用的时间。数字越小越好。注意这个图的横坐标是对数的，每一个数量级的取值点为 1, 2, 3, 4, 5, 6, 7.5, 10。<br />
<a href="http://software.intel.com/zh-cn/blogs/wordpress/wp-content/uploads/2011/01/1.png"><img class="alignnone size-full wp-image-400006689" src="http://software.intel.com/zh-cn/blogs/wordpress/wp-content/uploads/2011/01/1.png" alt="" width="680" height="564" /></a><br />
从红绿线对比可以看出：</p>
<p>1. libevent2 在初始化 event watcher 上面比 muduo 快 20% （左边的两个图）</p>
<p>2. 在事件处理方面（右边的两个图）：a) 在 100 个活动连接的情况下，libevent2 和 muduo 分段领先。当总连接数（并发数）小于 1000 时，二者性能差不多；当总连接数大于 30000 时，muduo 略占优；当总连接数大于 1000 小于 30000 时，libevent2 明显领先。b) 在 1000 个活动连接的情况下，当并发数小于 10000 时，libevent2 和 muduo 得分接近；当并发数大于 10000 时，muduo 明显占优。</p>
<p>这里我们有两个问题：1. 为什么 muduo 花在初始化上的时间比较多？ 2. 为什么在一些情况下它比 libevent2 慢很多。</p>
<p>我仔细分析了其中的原因，并参考了 libev 的作者 Marc Lehmann 的观点 ( http://lists.schmorp.de/pipermail/libev/2010q2/001041.html )，结论是：在第一轮初始化时，libevent2 和 muduo 都是用 epoll_ctl(fd, EPOLL_CTL_ADD, …) 来添加 fd event watcher。不同之处在于，在后面 24 轮中，muduo 使用了 epoll_ctl(fd, EPOLL_CTL_MOD, …) 来更新已有的 event watcher；然而 libevent2 继续调用 epoll_ctl(fd, EPOLL_CTL_ADD, …) 来重复添加 fd，并忽略返回的错误码 EEXIST (File exists)。在这种重复添加的情况下，EPOLL_CTL_ADD 将会快速地返回错误，而 EPOLL_CTL_MOD 会做更多的工作，花的时间也更长。于是 libevent2 捡了个便宜。</p>
<p>为了验证这个结论，我改动了 muduo，让它每次都用 EPOLL_CTL_ADD 方式初始化和更新 event watcher，并忽略返回的错误。</p>
<p>第二轮测试结果见上图的蓝线，可见改动之后的 muduo 的初始化性能比 libevent2 更好，事件处理的耗时也有所降低（我推测是 kernel 内部的原因）。</p>
<p>这个改动只是为了验证想法，我并没有把它放到 muduo 最终的代码中去，这或许可以留作日后优化的余地。（具体的改动是 muduo/net/poller/EPollPoller.cc 第 115 行和 144 行，读者可自行验证。）</p>
<p>同样的测试在双核笔记本电脑上运行了一次，结果如下：（我的笔记本的 CPU 主频是 2.4GHz，高于台式机的 1.86GHz，所以用时较少。）<br />
<a href="http://software.intel.com/zh-cn/blogs/wordpress/wp-content/uploads/2011/01/2.png"><img class="alignnone size-full wp-image-400006690" src="http://software.intel.com/zh-cn/blogs/wordpress/wp-content/uploads/2011/01/2.png" alt="" width="558" height="461" /></a><br />
结论：在事件处理效率方面，muduo 与 libevent2 总体比较接近，各擅胜场。在并发量特别大的情况下（大于 10k），muduo 略微占优。</p>
<p>关于 muduo 的更多介绍请见《发布一个基于 Reactor 模式的 C++ 网络库》。muduo 的项目网站是 http://code.google.com/p/muduo ，上面有个 class diagram 可供参考。</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/zh-cn/blogs/2011/01/12/muduo-libevent2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>多线程服务器的适用场合</title>
		<link>http://software.intel.com/zh-cn/blogs/2010/11/11/400006332/</link>
		<comments>http://software.intel.com/zh-cn/blogs/2010/11/11/400006332/#comments</comments>
		<pubDate>Thu, 11 Nov 2010 12:11:26 +0000</pubDate>
		<dc:creator>solstice_</dc:creator>
				<category><![CDATA[博客征文专栏]]></category>
		<category><![CDATA[并行计算]]></category>

		<guid isPermaLink="false">http://software.intel.com/zh-cn/blogs/2010/11/11/400006332/</guid>
		<description><![CDATA[陈硕 (giantchen_AT_gmail) Blog.csdn.net/Solstice 2010 Feb 28 这篇文章原本是前一篇博客《多线程服务器的常用编程模型》（以下简称《常用模型》）计划中的一节，今天终于写完了。 “服务器开发”包罗万象，本文所指的“服务器开发”的含义请见《常用模型》一文，一句话形容是：跑在多核机器上的 Linux 用户态的没有用户界面的长期运行的网络应用程序。“长期运行”的意思不是指程序 7x24 不重启，而是程序不会因为无事可做而退出，它会等着下一个请求的到来。例如 wget 不是长期运行的，httpd 是长期运行的。 正名 与前文相同，本文的“进程”指的是 fork() 系统调用的产物。“线程”指的是 pthread_create() 的产物，而且我指的 pthreads 是 NPTL 的，每个线程由 clone() 产生，对应一个内核的 task_struct。本文所用的开发语言是 C++，运行环境为 Linux。 首先，一个由多台机器组成的分布式系统必然是多进程的（字面意义上），因为进程不能跨 OS 边界。在这个前提下，我们把目光集中到一台机器，一台拥有至少 4 个核的普通服务器。如果要在一台多核机器上提供一种服务或执行一个任务，可用的模式有： 运行一个单线程的进程 运行一个多线程的进程 运行多个单线程的进程 运行多个多线程的进程 这些模式之间的比较已经是老生常谈，简单地总结： 模式 1 是不可伸缩的 (scalable)，不能发挥多核机器的计算能力； 模式 3 是目前公认的主流模式。它有两种子模式： 3a 简单地把模式 1 中的进程运行多份，如果能用多个 tcp port 对外提供服务的话； 3b [...]]]></description>
			<content:encoded><![CDATA[<p>陈硕 (giantchen_AT_gmail)</p>
<p>Blog.csdn.net/Solstice</p>
<p>2010 Feb 28</p>
<p>这篇文章原本是前一篇博客《多线程服务器的常用编程模型》（以下简称《常用模型》）计划中的一节，今天终于写完了。</p>
<p>“服务器开发”包罗万象，本文所指的“服务器开发”的含义请见《常用模型》一文，一句话形容是：跑在多核机器上的 Linux 用户态的没有用户界面的长期运行的网络应用程序。“长期运行”的意思不是指程序 7x24 不重启，而是程序不会因为无事可做而退出，它会等着下一个请求的到来。例如 wget 不是长期运行的，httpd 是长期运行的。</p>
<p>正名<br />
与前文相同，本文的“进程”指的是 fork() 系统调用的产物。“线程”指的是 pthread_create() 的产物，而且我指的 pthreads 是 NPTL 的，每个线程由 clone() 产生，对应一个内核的 task_struct。本文所用的开发语言是 C++，运行环境为 Linux。</p>
<p>首先，一个由多台机器组成的分布式系统必然是多进程的（字面意义上），因为进程不能跨 OS 边界。在这个前提下，我们把目光集中到一台机器，一台拥有至少 4 个核的普通服务器。如果要在一台多核机器上提供一种服务或执行一个任务，可用的模式有：</p>
<p>运行一个单线程的进程<br />
运行一个多线程的进程<br />
运行多个单线程的进程<br />
运行多个多线程的进程<br />
这些模式之间的比较已经是老生常谈，简单地总结：</p>
<p>模式 1 是不可伸缩的 (scalable)，不能发挥多核机器的计算能力；<br />
模式 3 是目前公认的主流模式。它有两种子模式：<br />
3a 简单地把模式 1 中的进程运行多份，如果能用多个 tcp port 对外提供服务的话；<br />
3b 主进程+woker进程，如果必须绑定到一个 tcp port，比如 httpd+fastcgi。<br />
模式 2 是很多人鄙视的，认为多线程程序难写，而且不比模式 3 有什么优势；<br />
模式 4 更是千夫所指，它不但没有结合 2 和 3 的优点，反而汇聚了二者的缺点。<br />
本文主要想讨论的是模式 2 和模式 3b 的优劣，即：什么时候一个服务器程序应该是多线程的。</p>
<p>从功能上讲，没有什么是多线程能做到而单线程做不到的，反之亦然，都是状态机嘛（我很高兴看到反例）。从性能上讲，无论是 IO bound 还是 CPU bound 的服务，多线程都没有什么优势。那么究竟为什么要用多线程？</p>
<p>在回答这个问题之前，我先谈谈必须用必须用单线程的场合。</p>
<p>必须用单线程的场合<br />
据我所知，有两种场合必须使用单线程：</p>
<p>程序可能会 fork()<br />
限制程序的 CPU 占用率<br />
先说 fork()，我在《Linux 新增系统调用的启示》中提到：</p>
<p>fork() 一般不能在多线程程序中调用，因为 Linux 的 fork() 只克隆当前线程的 thread of control，不克隆其他线程。也就是说不能一下子 fork() 出一个和父进程一样的多线程子进程，Linux 也没有 forkall() 这样的系统调用。forkall() 其实也是很难办的（从语意上），因为其他线程可能等在 condition variable 上，可能阻塞在系统调用上，可能等着 mutex 以跨入临界区，还可能在密集的计算中，这些都不好全盘搬到子进程里。</p>
<p>更为糟糕的是，如果在 fork() 的一瞬间某个别的线程 a 已经获取了 mutex，由于 fork() 出的新进程里没有这个“线程a”，那么这个 mutex 永远也不会释放，新的进程就不能再获取那个 mutex，否则会死锁。（这一点仅为推测，还没有做实验，不排除 fork() 会释放所有 mutex 的可能。）</p>
<p>综上，一个设计为可能调用 fork() 的程序必须是单线程的，比如我在《启示》一文中提到的“看门狗进程”。多线程程序不是不能调用 fork()，而是这么做会遇到很多麻烦，我想不出做的理由。</p>
<p>一个程序 fork() 之后一般有两种行为：</p>
<p>立刻执行 exec()，变身为另一个程序。例如 shell 和 inetd；又比如 lighttpd fork() 出子进程，然后运行 fastcgi 程序。或者集群中运行在计算节点上的负责启动 job 的守护进程（即我所谓的“看门狗进程”）。<br />
不调用 exec()，继续运行当前程序。要么通过共享的文件描述符与父进程通信，协同完成任务；要么接过父进程传来的文件描述符，独立完成工作，例如 80 年代的 web 服务器 NCSA httpd。<br />
这些行为中，我认为只有“看门狗进程”必须坚持单线程，其他的均可替换为多线程程序（从功能上讲）。</p>
<p>单线程程序能限制程序的 CPU 占用率。</p>
<p>这个很容易理解，比如在一个 8-core 的主机上，一个单线程程序即便发生 busy-wait（无论是因为 bug 还是因为 overload），其 CPU 使用率也只有 12.5%，即占满 1 个 core。在这种最坏的情况下，系统还是有 87.5% 的计算资源可供其他服务进程使用。</p>
<p>因此对于一些辅助性的程序，如果它必须和主要功能进程运行在同一台机器的话（比如它要监控其他服务进程的状态），那么做成单线程的能避免过分抢夺系统的计算资源。</p>
<p>基于进程的分布式系统设计<br />
《常用模型》一文提到，分布式系统的软件设计和功能划分一般应该以“进程”为单位。我提倡用多线程，并不是说把整个系统放到一个进程里实现，而是指功能划分之后，在实现每一类服务进程时，在必要时可以借助多线程来提高性能。对于整个分布式系统，要做到能 scale out，即享受增加机器带来的好处。</p>
<p>对于上层的应用而言，每个进程的代码量控制在 10 万行 C++ 以下，这不包括现成的 library 的代码量。这样每个进程都能被一个脑子完全理解，不会出现混乱。（其实我更想说 5 万行。）</p>
<p>这里推荐一篇 Google 的好文《Introduction to Distributed System Design》。其中点睛之笔是：分布式系统设计，是 design for failure。</p>
<p>本文继续讨论一个服务进程什么时候应该用多线程，先说说单线程的优势。</p>
<p>单线程程序的优势<br />
从编程的角度，单线程程序的优势无需赘言：简单。程序的结构一般如《常用模型》所言，是一个基于 IO multiplexing 的 event loop。或者如云风所言，直接用阻塞 IO。</p>
<p>event loop 的典型代码框架是：</p>
<p>while (!done) {<br />
int retval = ::poll(fds, nfds, timeout_ms);<br />
if (retval 0) {<br />
处理 IO 事件<br />
}<br />
}<br />
}</p>
<p>event loop 有一个明显的缺点，它是非抢占的(non-preemptive)。假设事件 a 的优先级高于事件 b，处理事件 a 需要 1ms，处理事件 b 需要 10ms。如果事件 b 稍早于 a 发生，那么当事件 a 到来时，程序已经离开了 poll() 调用开始处理事件 b。事件 a 要等上 10ms 才有机会被处理，总的响应时间为 11ms。这等于发生了优先级反转。</p>
<p>这可缺点可以用多线程来克服，这也是多线程的主要优势。</p>
<p>多线程程序有性能优势吗？<br />
前面我说，无论是 IO bound 还是 CPU bound 的服务，多线程都没有什么绝对意义上的性能优势。这里详细阐述一下这句话的意思。</p>
<p>这句话是说，如果用很少的 CPU 负载就能让的 IO 跑满，或者用很少的 IO 流量就能让 CPU 跑满，那么多线程没啥用处。举例来说：</p>
<p>对于静态 web 服务器，或者 ftp 服务器，CPU 的负载较轻，主要瓶颈在磁盘 IO 和网络 IO。这时候往往一个单线程的程序（模式 1）就能撑满 IO。用多线程并不能提高吞吐量，因为 IO 硬件容量已经饱和了。同理，这时增加 CPU 数目也不能提高吞吐量。<br />
CPU 跑满的情况比较少见，这里我只好虚构一个例子。假设有一个服务，它的输入是 n 个整数，问能否从中选出 m 个整数，使其和为 0 （这里 n 0）。这是著名的 subset sum 问题，是 NP-Complete 的。对于这样一个“服务”，哪怕很小的 n 值也会让 CPU 算死，比如 n = 30，一次的输入不过 120 字节（32-bit 整数），CPU 的运算时间可能长达几分钟。对于这种应用，模式 3a 是最适合的，能发挥多核的优势，程序也简单。<br />
也就是说，无论任何一方早早地先到达瓶颈，多线程程序都没啥优势。</p>
<p>说到这里，可能已经有读者不耐烦了：你讲了这么多，都在说单线程的好处，那么多线程究竟有什么用？</p>
<p>适用多线程程序的场景<br />
我认为多线程的适用场景是：提高响应速度，让 IO 和“计算”相互重叠，降低 latency。</p>
<p>虽然多线程不能提高绝对性能，但能提高平均响应性能。</p>
<p>一个程序要做成多线程的，大致要满足：</p>
<p>有多个 CPU 可用。单核机器上多线程的优势不明显。<br />
线程间有共享数据。如果没有共享数据，用模型 3b 就行。虽然我们应该把线程间的共享数据降到最低，但不代表没有；<br />
共享的数据是可以修改的，而不是静态的常量表。如果数据不能修改，那么可以在进程间用 shared memory，模式 3 就能胜任；<br />
提供非均质的服务。即，事件的响应有优先级差异，我们可以用专门的线程来处理优先级高的事件。防止优先级反转；<br />
latency 和 throughput 同样重要，不是逻辑简单的 IO bound 或 CPU bound 程序；<br />
利用异步操作。比如 logging。无论往磁盘写 log file，还是往 log server 发送消息都不应该阻塞 critical path；<br />
能 scale up。一个好的多线程程序应该能享受增加 CPU 数目带来的好处，目前主流是 8 核，很快就会用到 16 核的机器了。<br />
具有可预测的性能。随着负载增加，性能缓慢下降，超过某个临界点之后急速下降。线程数目一般不随负载变化。<br />
多线程能有效地划分责任与功能，让每个线程的逻辑比较简单，任务单一，便于编码。而不是把所有逻辑都塞到一个 event loop 里，就像 Win32 SDK 程序那样。<br />
这些条件比较抽象，这里举一个具体的（虽然是虚构的）例子。</p>
<p>假设要管理一个 Linux 服务器机群，这个机群里有 8 个计算节点，1 个控制节点。机器的配置都是一样的，双路四核 CPU，千兆网互联。现在需要编写一个简单的机群管理软件（参考 LLNL 的 SLURM），这个软件由三个程序组成：</p>
<p>运行在控制节点上的 master，这个程序监视并控制整个机群的状态。<br />
运在每个计算节点上的 slave，负责启动和终止 job，并监控本机的资源。<br />
给最终用户的 client 命令行工具，用于提交 job。<br />
根据前面的分析，slave 是个“看门狗进程”，它会启动别的 job 进程，因此必须是个单线程程序。另外它不应该占用太多的 CPU 资源，这也适合单线程模型。</p>
<p>master 应该是个模式 2 的多线程程序：</p>
<p>它独占一台 8 核的机器，如果用模型 1，等于浪费了 87.5% 的 CPU 资源。<br />
整个机群的状态应该能完全放在内存中，这些状态是共享且可变的。如果用模式 3，那么进程之间的状态同步会成大问题。而如果大量使用共享内存，等于是掩耳盗铃，披着多进程外衣的多线程程序。<br />
master 的主要性能指标不是 throughput，而是 latency，即尽快地响应各种事件。它几乎不会出现把 IO 或 CPU 跑满的情况。<br />
master 监控的事件有优先级区别，一个程序正常运行结束和异常崩溃的处理优先级不同，计算节点的磁盘满了和机箱温度过高这两种报警条件的优先级也不同。如果用单线程，可能会出现优先级反转。<br />
假设 master 和每个 slave 之间用一个 TCP 连接，那么 master 采用 2 个或 4 个 IO 线程来处理 8 个 TCP connections 能有效地降低延迟。<br />
master 要异步的往本地硬盘写 log，这要求 logging library 有自己的 IO 线程。<br />
master 有可能要读写数据库，那么数据库连接这个第三方 library 可能有自己的线程，并回调 master 的代码。<br />
master 要服务于多个 clients，用多线程也能降低客户响应时间。也就是说它可以再用 2 个 IO 线程专门处理和 clients 的通信。<br />
master 还可以提供一个 monitor 接口，用来广播 (pushing) 机群的状态，这样用户不用主动轮询 (polling)。这个功能如果用单独的线程来做，会比较容易实现，不会搞乱其他主要功能。<br />
master 一共开了 10 个线程：<br />
4 个用于和 slaves 通信的 IO 线程<br />
1 个 logging 线程<br />
1 个数据库 IO 线程<br />
2 个和 clients 通信的 IO 线程<br />
1 个主线程，用于做些背景工作，比如 job 调度<br />
1 个 pushing 线程，用于主动广播机群的状态<br />
虽然线程数目略多于 core 数目，但是这些线程很多时候都是空闲的，可以依赖 OS 的进程调度来保证可控的延迟。<br />
综上所述，master 用多线程方式编写是自然且高效的。</p>
<p>线程的分类<br />
据我的经验，一个多线程服务程序中的线程大致可分为 3 类：</p>
<p>IO 线程，这类线程的的主循环是 io multiplexing，等在 select/poll/epoll 系统调用上。这类线程也处理定时事件。当然它的功能不止 IO，有些计算也可以放入其中。<br />
计算线程，这类线程的主循环是 blocking queue，等在 condition variable 上。这类线程一般位于 thread pool 中。<br />
第三方库所用的线程，比如 logging，又比如 database connection。<br />
服务器程序一般不会频繁地启动和终止线程。甚至，在我写过的程序里，create thread 只在程序启动的时候调用，在服务运行期间是不调用的。</p>
<p>在多核时代，多线程编程是不可避免的，“鸵鸟算法”不是办法。</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/zh-cn/blogs/2010/11/11/400006332/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>发布一个基于 Reactor 模式的 C++ 网络库</title>
		<link>http://software.intel.com/zh-cn/blogs/2010/09/09/reactor-c/</link>
		<comments>http://software.intel.com/zh-cn/blogs/2010/09/09/reactor-c/#comments</comments>
		<pubDate>Thu, 09 Sep 2010 10:05:32 +0000</pubDate>
		<dc:creator>solstice_</dc:creator>
				<category><![CDATA[博客征文专栏]]></category>
		<category><![CDATA[软件开发工具]]></category>

		<guid isPermaLink="false">http://software.intel.com/zh-cn/blogs/2010/09/09/reactor-c/</guid>
		<description><![CDATA[本文主要介绍 muduo 网络库的使用。其设计与实现将有另文讲解。 目录 由来 1 下载与编译 2 例子 2 基本结构 3 公开接口 4 内部实现 4 线程模型 5 结语 5 由来 半年前我写了一篇《学之者生，用之者死——ACE历史与简评》，其中提到“我心目中理想的网络库”的样子： 线程安全，支持多核多线程 不考虑可移植性，不跨平台，只支持 Linux，不支持 Windows。 在不增加复杂度的前提下可以支持 FreeBSD/Darwin，方便将来用 Mac 作为开发用机，但不为它做性能优化。也就是说 IO multiplexing 使用 poll 和 epoll。 主要支持 x86-64，兼顾 IA32 不支持 UDP，只支持 TCP 不支持 IPv6，只支持 IPv4 不考虑广域网应用，只考虑局域网 只支持一种使用模式：non-blocking IO + one event loop per thread，不考虑阻塞 IO [...]]]></description>
			<content:encoded><![CDATA[<p>本文主要介绍 muduo 网络库的使用。其设计与实现将有另文讲解。</p>
<p>目录</p>
<p><a href="http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx#_Toc17667">由来 1</a></p>
<p><a href="http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx#_Toc20754">下载与编译 2</a></p>
<p><a href="http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx#_Toc2416">例子 2</a></p>
<p><a href="http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx#_Toc32039">基本结构 3</a></p>
<p><a href="http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx#_Toc29754">公开接口 4</a></p>
<p><a href="http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx#_Toc24136">内部实现 4</a></p>
<p><a href="http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx#_Toc8317">线程模型 5</a></p>
<p><a href="http://blog.csdn.net/Solstice/archive/2010/08/29/5848547.aspx#_Toc20019">结语 5</a><br />
由来<br />
半年前我写了一篇《学之者生，用之者死——ACE历史与简评》，其中提到“我心目中理想的网络库”的样子：</p>
<p>线程安全，支持多核多线程<br />
不考虑可移植性，不跨平台，只支持 Linux，不支持 Windows。<br />
在不增加复杂度的前提下可以支持 FreeBSD/Darwin，方便将来用 Mac 作为开发用机，但不为它做性能优化。也就是说 IO multiplexing 使用 poll 和 epoll。<br />
主要支持 x86-64，兼顾 IA32<br />
不支持 UDP，只支持 TCP<br />
不支持 IPv6，只支持 IPv4<br />
不考虑广域网应用，只考虑局域网<br />
只支持一种使用模式：non-blocking IO + one event loop per thread，不考虑阻塞 IO<br />
API 简单易用，只暴露具体类和标准库里的类，不使用 non-trivial templates，也不使用虚函数<br />
只满足常用需求的 90%，不面面俱到，必要的时候以 app 来适应 lib<br />
只做 library，不做成 framework<br />
争取全部代码在 5000 行以内（不含测试）<br />
以上条件都满足时，可以考虑搭配 Google Protocol Buffers RPC<br />
在想清楚这些目标之后，我开始第三次尝试编写自己的 C++ 网络库。与前两次不同，这次我一开始就想好了库的名字，叫 muduo （木铎），并在 Google code 上创建了项目： http://code.google.com/p/muduo/ 。muduo 的主体内容在 5 月底已经基本完成，现在我把它开源。</p>
<p>本文主要介绍 muduo 网络库的使用，其设计与实现将有另文讲解。</p>
<p>下载与编译<br />
下载地址： http://muduo.googlecode.com/files/muduo-0.1.0-alpha.tar.gz</p>
<p>SHA1 Checksum: 5d3642e311177ded89ed0d15c10921738f8c984c</p>
<p>Muduo 使用了 Linux 较新的系统调用，要求 Linux 的内核版本大于 2.6.28 （我自己用的是 2.6.32 ）。在 Debian Squeeze / Ubuntu 10.04 LTS 上编译测试通过，32 位和 64 位系统都能使用。</p>
<p>Muduo 采用 CMake 为 build system，安装方法：</p>
<p>$ sudo apt-get install cmake</p>
<p>Muduo 依赖 Boost，很容易安装：</p>
<p>$ sudo apt-get install libboost1.40-dev # 或 libboost1.42-dev</p>
<p>编译方法很简单：</p>
<p>$ tar zxf muduo-0.1.0-alpha.tar.gz</p>
<p>$ cd muduo/</p>
<p>$ ./build.sh</p>
<p># 编译生成的可执行文件和静态库文件分别位于 ../build/debug/{bin,lib}</p>
<p>如果要编译 release 版，可执行</p>
<p>$ BUILD_TYPE=release ./build.sh</p>
<p># 编译生成的可执行文件和静态库文件分别位于 ../build/release/{bin,lib}</p>
<p>编译完成之后请试运行其中的例子。比如 bin/inspector_test ，然后通过浏览器访问 http://10.0.0.10:12345/ 或 http://10.0.0.10:12345/proc/status，其中 10.0.0.10 替换为你的 Linux box 的 IP。</p>
<p>例子<br />
Muduo 附带了几十个小例子，位于 examples 目录。其中包括从 Boost.Asio、JBoss Netty、Python Twisted 等处移植过来的例子。</p>
<p>examples</p>
<p>|-- simple # 简单网络协议的实现</p>
<p>| |-- allinone # 在一个程序里同时实现下面 5 个协议</p>
<p>| |-- chargen # RFC 864，可测试带宽</p>
<p>| |-- daytime # RFC 867</p>
<p>| |-- discard # RFC 863</p>
<p>| |-- echo # RFC 862</p>
<p>| |-- time # RFC 868</p>
<p>| `-- timeclient # time 协议的客户端</p>
<p>|-- hub # 一个简单的 pub/sub/hub 服务，演示应用级的广播</p>
<p>|-- roundtrip # 测试两台机器的网络延时与时间差</p>
<p>|-- asio # 从 Boost.Asio 移植的例子</p>
<p>| |-- chat # 聊天服务</p>
<p>| `-- tutorial # 一系列 timers</p>
<p>|-- netty # 从 JBoss Netty 移植的例子</p>
<p>| |-- discard # 可用于测试带宽，服务器可多线程运行</p>
<p>| |-- echo # 可用于测试带宽，服务器可多线程运行</p>
<p>| `-- uptime # TCP 长连接</p>
<p>`-- twisted # 从 Python Twisted 移植的例子</p>
<p>`-- finger # finger01 ~ 07</p>
<p>基本结构<br />
Muduo 的目录结构如下。</p>
<p>muduo</p>
<p>|-- base # 与网络无关的基础代码，已提前发布</p>
<p>`-- net # 网络库</p>
<p>|-- http # 一个简单的可嵌入的 web 服务器</p>
<p>|-- inspect # 基于以上 web 服务器的“窥探器”，用于报告进程的状态</p>
<p>`-- poller # poll(2) 和 epoll(4) 两种 IO multiplexing 后端</p>
<p>Muduo 是基于 Reactor 模式的网络库，其核心是个事件循环 EventLoop，用于响应计时器和 IO 事件。Muduo 采用基于对象（object based）而非面向对象（object oriente d）的设计风格，其接口多以 boost::function + boost::bind 表达。</p>
<p>Muduo 的头文件明确分为客户可见和客户不可见两类。客户可见的为白底，客户不可见的为灰底。<br />
<img src="http://hi.csdn.net/attachment/201008/29/0_128309628817lF.gif" alt="" /><br />
这里简单介绍各个头文件及 class 的作用，详细的介绍留给以后的博客。</p>
<p>公开接口<br />
Buffer 仿 Netty ChannelBuffer 的 buffer class，数据的读写透过 buffer 进行<br />
InetAddress 封装 IPv4 地址 (end point)，注意，muduo 目前不能解析域名，只认 IP<br />
EventLoop 反应器 Reactor，用户可以注册计时器回调<br />
EventLoopThread 启动一个线程，在其中运行 EventLoop::loop()<br />
TcpConnection 整个网络库的核心，封装一次 TCP 连接<br />
TcpClient 用于编写网络客户端，能发起连接，并且有重试功能<br />
TcpServer 用于编写网络服务器，接受客户的连接<br />
在这些类中，TcpConnection 的生命期依靠 shared_ptr 控制（即用户和库共同控制）。Buffer 的生命期由 TcpConnection 控制。其余类的生命期由用户控制。<br />
HttpServer 和 Inspector，暴露出一个 http 界面，用于监控进程的状态，类似于 Java JMX。这么做的原因是，《程序员修炼之道》第 6 章第 34 条提到“对于更大、更复杂的服务器代码，提供其操作的内部试图的一种漂亮技术是使用内建的 Web 服务器”，Jeff Dean 也说“（每个 Google 的服务器进程）Export HTML-based status pages for easy diagnosis”。<br />
内部实现<br />
Channel 是 selectable IO channel，负责注册与响应 IO 事件，它不拥有 file descriptor。它是 Acceptor、Connector、EventLoop、TimerQueue、TcpConnection 的成员，生命期由后者控制。<br />
Socket 封装一个 file descriptor，并在析构时关闭 fd。它是 Acceptor、TcpConnection 的成员，生命期由后者控制。EventLoop、TimerQueue 也拥有 fd，但是不封装为 Socket。<br />
SocketsOps 封装各种 sockets 系统调用。<br />
EventLoop 封装事件循环，也是事件分派的中心。它用 eventfd(2) 来异步唤醒，这有别于传统的用一对 pipe(2) 的办法。它用 TimerQueue 作为计时器管理，用 Poller 作为 IO Multiplexing。<br />
Poller 是 PollPoller 和 EPollPoller 的基类，采用“电平触发”的语意。它是 EventLoop 的成员，生命期由后者控制。<br />
PollPoller 和 EPollPoller 封装 poll(2) 和 epoll(4) 两种 IO Multiplexing 后端。Poll 的存在价值是便于调试，因为 poll(2) 调用是上下文无关的，用 strace 很容易知道库的行为是否正确。<br />
Connector 用于发起 TCP 连接，它是 TcpClient 的成员，生命期由后者控制。<br />
Acceptor 用于接受 TCP 连接，它是 TcpServer 的成员，生命期由后者控制。<br />
TimerQueue 用 timerfd 实现定时，这有别于传统的设置 poll/epoll_wait 的等待时长的办法。为了简单起见，目前用链表来管理 Timer，如果有必要可改为优先队列，这样复杂度可从 O(n) 降为O(ln n) （某些操作甚至是 O(1)）。它是 EventLoop 的成员，生命期由后者控制。<br />
EventLoopThreadPool 用于创建 IO 线程池，也就是说把 TcpConnection 分派到一组运行 EventLoop 的线程上。它是 TcpServer 的成员，生命期由后者控制。<br />
线程模型<br />
Muduo 的线程模型符合我主张的 one loop per thread + thread pool 模型。每个线程最多有一个 EventLoop。每个 TcpConnection 必须归某个 EventLoop 管理，所有的 IO 会转移到这个线程，换句话说一个 file descriptor 只能由一个线程读写。TcpConnection 所在的线程由其所属的 EventLoop 决定，这样我们可以很方便地把不同的 TCP 连接放到不同的线程去，也可以把一些 TCP 连接放到一个线程里。TcpConnection 和 EventLoop 是线程安全的，可以跨线程调用。TcpServer 直接支持多线程，它有两种模式：</p>
<p>1. 单线程，accept 与 TcpConnection 用同一个线程做 IO。</p>
<p>2. 多线程，accept 与 EventLoop 在同一个线程，另外创建一个 EventLoopThreadPool，新到的连接会按 round-robin 方式分配到线程池中。</p>
<p>结语<br />
Muduo 是我对常见网络编程任务的总结，用它我能很容易地编写多线程的 TCP 服务器和客户端。Muduo 是我业余时间的作品，代码估计还有很多 bug，功能也不完善（例如不支持 signal 处理），待日后慢慢改进吧。</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/zh-cn/blogs/2010/09/09/reactor-c/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

