Linux 多线程服务端编程
使用 muduo C++ 网络库
陈硕 (giantchen@gmail.com)
最后更新 2012-09-30
示范在多核时代采用现代 C++ 编写
多线程 TCP 网络服务器的正规做法
封面文案
内容简介
本书主要讲述采用现代 C++ 在 x86-64 Linux 上编写多线程 TCP 网络服务程序的
主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即 one loop
per thread。这是在 Linux 下以 native 语言编写用户态高性能网络程序最成熟的模
式,掌握之后可顺利地开发各类常见的服务端网络应用程序。本书以 muduo 网络库
为例,讲解这种编程模型的使用方法及注意事项。
本书的宗旨是贵精不贵多。掌握两种基本的同步原语就可以满足各种多线程同步
的功能需求,还能写出更易用的同步设施。掌握一种进程间通信方式和一种多线程网
络编程模型就足以应对日常开发任务,编写运行于公司内网环境的分布式服务系统。
作者简介
陈硕,北京师范大学硕士,擅长 C++ 多线程网络编程和实时分布式系统架构。
曾在摩根士丹利 IT 部门工作 5 年,从事实时外汇交易系统开发。现在在美国加州硅
谷某互联网大公司工作,从事大规模分布式系统的可靠性工程。编写了开源 C++ 网
络库 muduo,参与翻译了《代码大全(第 2 版)》和《C++ 编程规范(繁体版)》,整
理了《C++ Primer (第 4 版)(评注版)》,并曾多次在各地技术大会演讲。
电子工业出版社
封底文案
看完了 W. Richard Stevens 的传世经典《UNIX 网络编程》,能照着例子用 Sockets API 编
写 echo 服务,却仍然对稍微复杂一点的网络编程任务感到无从下手?学习网络编程有哪些好
的练手项目?书中示例代码把业务逻辑和 Sockets 调用混在一起,似乎不利于将来扩展?网络
编程中遇到一些具体问题该怎么办?例如:
• 程序在本机测试正常,放到网络上运行就
经常出现数据收不全的情况?
• TCP 协议真的有所谓的“粘包问题”吗?
该如何设计消息帧的协议?又该如何编码
实现分包才不会掉到陷阱里?
• 带外数据(OOB)、信号驱动 IO 这些高级
• 要处理成千上万的并发连接,似乎《UNIX
网 络 编 程》 介 绍 的 传 统 fork() 模 型 应
付不过来, 该用哪种并发模型呢?试试
select(2)/poll(2)/epoll(4) 这 种 IO 复
用模型吧,又感觉非阻塞 IO 陷阱重重,怎
么程序的 CPU 使用率一直是 100%?
特性到底有没有用?
• 网络消息格式该怎么设计?发送 C struct
会有对齐方面的问题吗?对方不用 C/C++
怎么通信?将来服务端软件升级,需要在
消息中增加一个字段,现有的客户端就必
须强制升级?
• 要不改用现成的 libevent 网络库吧,怎么
查询一下数据库就把其他连接上的请求给
耽误了?
• 再用个线程池吧。万一发回响应的时候对
方已经断开连接了怎么办?会不会串话?
读过《UNIX 环境高级编程》,想用多线程来发挥多核 CPU 的性能潜力,但对程序该用哪
种多线程模型感到一头雾水?有没有值得推荐的适用面广的多线程 IO 模型?互斥器、条件变
量、读写锁、信号量这些底层同步原语哪些该用哪些不该用?有没有更高级的同步设施能简化
开发?《UNIX 网络编程(第 2 卷)》介绍的那些琳琅满目的进程间通信(IPC)机制到底用哪
个才能兼顾开发效率与可伸缩性?
网络编程和多线程编程的基础打得差不多,开始实际做项目了,更多问题扑面而来:
• 网上听人说服务端开发要做到 7 24 运
行,为了防止内存碎片连动态内存分配都
不能用,那岂不是连 C++ STL 也一并禁用
了?硬件的可靠性高到值得去这么做吗?
• 传闻服务端开发主要通过日志来查错,那
么日志里该写些什么?日志是写给谁看的?
怎样写日志才不会影响性能?
• 分布式系统跟单机多进程到底有什么本质
区别?心跳协议为什么是必需的,该如何
实现?
• C++ 的大型工程该如何管理?库的接口如
何设计才能保证升级的时候不破坏二进制
兼容性?有没有更适合大规模分布式系统
的部署方案?
这本《Linux 多线程服务端编程:使用 muduo C++ 网络库》中,作者凭借多年的工程实
践经验试图解答以上疑问。当然,内容还远不止这些……
前言
本书主要讲述采用现代 C++ 在 x86-64 Linux 上编写多线程 TCP 网络服务程序的
主流常规技术,这也是我对过去 5 年编写生产环境下的多线程服务端程序的经验总
结。本书重点讲解多线程网络服务器的一种 IO 模型,即 one loop per thread。这是一
种适应性较强的模型,也是 Linux 下以 native 语言编写用户态高性能网络程序最成
熟的模式,掌握之后可顺利地开发各类常见的服务端网络应用程序。本书以 muduo
网络库为例,讲解这种编程模型的使用方法及注意事项。
muduo 是一个基于非阻塞 IO 和事件驱动的现代 C++ 网络库,原生支持 one
loop per thread 这种 IO 模型。muduo 适合开发 Linux 下的面向业务的多线程服务
端网络应用程序,其中“面向业务的网络编程”的定义见附录 A。“现代 C++”指的
不是 C++11 新标准,而是 2005 年 TR1 发布之后的 C++ 语言和库。与传统 C++ 相比,
现代 C++ 的变化主要有两方面:资源管理(见第 1 章)与事件回调(见第 449 页)。
本书不是多线程编程教程,也不是网络编程教程,更不是 C++ 教程。读者应该
已经大致读过《UNIX 环境高级编程》、《UNIX 网络编程》、《C++ Primer》或与之内
容相近的书籍。本书不谈 C++11,因为目前(2012 年)主流的 Linux 服务端发行版的
g++ 版本都还停留在 4.4,C++11 进入实用尚需一段时日。
本书适用的硬件环境是主流 x86-64 服务器,多路多核 CPU、几十 GB 内存、千
兆以太网互联。除了第 5 章讲诊断日志之外,本书不涉及文件 IO。
本书分为四大部分,第 1 部分“C++ 多线程系统编程”考察多线程下的对象生
命期管理、线程同步方法、多线程与 C++ 的结合、高效的多线程日志等。第 2 部
分“muduo 网络库”介绍使用现成的非阻塞网络库编写网络应用程序的方法,以及
muduo 的设计与实现。第 3 部分“工程实践经验谈”介绍分布式系统的工程化开发
方法和 C++ 在工程实践中的功能特性取舍。第 4 部分“附录”分享网络编程和 C++
语言的学习经验。
本书的宗旨是贵精不贵多。掌握两种基本的同步原语就可以满足各种多线程同步
的功能需求,还能写出更易用的同步设施。掌握一种进程间通信方式和一种多线程网
络编程模型就足以应对日常开发任务,编写运行于公司内网环境的分布式服务系统。
(本书不涉及分布式存储系统,也不涉及 UDP。)
iii
iv
术语与排版范例
前言
本书大量使用英文术语,甚至有少量英文引文。设计模式的名字一律用英文,
例如 Observer、Reactor、Singleton。在中文术语不够突出时,也会使用英文,例
如 class、heap、event loop、STL algorithm 等。 注意几个中文 C++ 术语: 对象
实体(instance)、函数重载决议(resolution)、模板具现化(instantiation)、覆写
(override)虚函数、提领(dereference)指针。本书中的英语可数名词一般不用复数
形式,例如两个 class,6 个 syscall;但有时会用 (s) 强调中文名词是复数。fd 是文件
描述符(file descriptor)的缩写。“CPU 数目”一般指的是核(core)的数目。容量
单位 kB、MB、GB 表示的字节数分别为 103、106、109,在特别强调准确数值时,会
分别用 KiB、MiB、GiB 表示 210、220、230 字节。用诸如 §11.5 表示本书第 11.5 节,
L42 表示上下文中出现的第 42 行代码。[JCP]、[CC2e] 等是参考文献,见书末清单。
一般术语用普通罗马字体,如 mutex、socket;C++ 关键字用无衬线字体,如
class、this、mutable;函数名和 class 名用等宽字体,如 fork(2)、muduo::EventLoop,
其中 fork(2) 表示系统函数 fork() 的文档位于 manpage 第 2 节,可以通过 man 2 fork
命令查看。如果函数名或类名过长,可能会折行,行末有连字号“-”,如 EventLoop-
ThreadPool。文件路径和 URL 采用窄字体,例如 muduo/base/Date.h、http://chenshuo.com。
用中文楷体表示引述别人的话。
代码
本书的示例代码以开源项目的形式发布在 GitHub 上,地址是 http://github.com/
chenshuo/recipes/ 和 http://github.com/chenshuo/muduo/。本书配套页面提供全部源代码打包
下载,正文中出现的类似 recipes/thread 的路径是压缩包内的相对路径,读者不难找到
其对应的 GitHub URL。本书引用代码的形式如下,左侧数字是文件的行号,右侧的
“muduo/base/Types.h”是文件路径 1。例如下面这几行代码是 muduo::string 的 typedef。
muduo/base/Types.h
15 namespace muduo
16 {
17
18 #ifdef MUDUO_STD_STRING
19 using std::string;
20 #else // !MUDUO_STD_STRING
21 typedef __gnu_cxx::__sso_string string;
22 #endif
muduo/base/Types.h
1 在第 6、7 两章的 muduo 示例代码中,路径 muduo/examples/XXX 会简写为 examples/XXX。此外,第 8 章会
把 recipes/reactor/XXX 简写为 reactor/XXX。
Linux 多线程服务端编程:使用 muduo C++ 网络库 (excerpt) http://www.chenshuo.com/book/
前言
v
本书假定读者熟悉 diff -u 命令的输出格式,用于表示代码的改动。
本书正文中出现的代码有时为了照顾排版而略有改写,例如改变缩进规则,去掉
单行条件语句前后的花括号等。就编程风格而论,应以电子版代码为准。
联系方式
邮箱:giantchen@gmail.com
主页:http://chenshuo.com/book (正文和脚注中出现的 URL 可从这里找到。)
微博:http://weibo.com/giantchen
博客:http://blog.csdn.net/Solstice
代码:http://github.com/chenshuo
陈硕
中国·香港
Linux 多线程服务端编程:使用 muduo C++ 网络库 (excerpt) http://www.chenshuo.com/book/
内容一览
第 1 部分 C++ 多线程系统编程
.
.
.
.
.
.
.
第 1 章 线程安全的对象生命期管理 . .
第 2 章 线程同步精要 .
.
第 3 章 多线程服务器的适用场合与常用编程模型 .
第 4 章 C++ 多线程系统编程精要 .
第 5 章 高效的多线程日志 . .
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
第 2 部分 muduo 网络库
第 6 章 muduo 网络库简介 . .
第 7 章 muduo 编程示例 .
.
第 8 章 muduo 网络库设计与实现 .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
第 3 部分 工程实践经验谈
.
.
.
第 9 章 分布式系统工程实践 . .
第 10 章 C++ 编译链接模型精要 .
.
第 11 章 反思 C++ 面向对象与虚函数 . .
第 12 章 C++ 经验谈 . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
第 4 部分 附录
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
附录 A 谈一谈网络编程学习经验 .
.
附录 B 从《C++ Primer(第 4 版)》入手学习 C++ .
附录 C 关于 Boost 的看法 . .
.
附录 D 关于 TCP 并发连接的几个思考题与试验 . .
参考文献 .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
vi
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
3
31
59
83
.
.
.
.
.
.
. .
.
.
.
.
.
.
. 107
123
. 125
.
. . 177
.
. 277
337
. 339
. 391
. 429
. 501
559
. 561
. 579
. 591
. 593
. 599
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
第 1 部分 C++ 多线程系统编程
第 1 章 线程安全的对象生命期管理
1.1 当析构函数遇到多线程 . .
.
1.1.1 线程安全的定义 . .
.
MutexLock 与 MutexLockGuard . .
1.1.2
1.1.3 一个线程安全的 Counter 示例 . .
.
.
.
1.2 对象的创建很简单 . .
1.3 销毁太难 .
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1.3.1 mutex 不是办法 . .
.
1.3.2 作为数据成员的 mutex 不能保护析构 . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1.11.1 enable_shared_from_this . .
1.11.2 弱回调 . .
.
1.12 替代方案 .
.
.
.
1.13 心得与小结 .
.
.
1.14 Observer 之谬 . .
.
1.4 线程安全的 Observer 有多难 .
.
1.5 原始指针有何不妥 . .
.
.
1.6 神器 shared_ptr/weak_ptr . .
.
1.7 插曲:系统地避免各种指针错误 .
1.8 应用到 Observer 上 .
.
.
1.9 再论 shared_ptr 的线程安全 .
.
1.10 shared_ptr 技术与陷阱 . .
.
.
1.11 对象池 . .
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
. .
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
目录
1
3
3
4
4
4
5
7
7
8
8
11
13
14
16
17
19
21
. 23
.
24
. 26
. 26
. 28
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
第 2 章 线程同步精要
2.1 互斥器(mutex) . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
31
. 32
vii
viii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2.1.1 只使用非递归的 mutex .
2.1.2 死锁 .
.
.
.
.
.
2.2 条件变量(condition variable) .
.
2.3 不要用读写锁和信号量 . .
.
.
2.4 封装 MutexLock、MutexLockGuard、Condition .
2.5 线程安全的 Singleton 实现 . .
.
.
.
.
.
2.6
2.7 归纳与总结 .
.
.
.
2.8 借 shared_ptr 实现 copy-on-write . .
.
sleep(3) 不是同步原语 . .
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3.3.1
3.3.2 线程池 . .
.
3.3.3 推荐模式 . .
第 3 章 多线程服务器的适用场合与常用编程模型
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
. .
.
.
.
.
3.6 “多线程服务器的适用场合”例释与答疑 .
.
3.1 进程与线程 .
.
3.2 单线程服务器的常用编程模型 .
3.3 多线程服务器的常用编程模型 .
one loop per thread . .
.
.
.
.
.
.
.
.
.
3.5.1 必须用单线程的场合 .
.
3.5.2 单线程程序的优缺点 .
.
3.5.3 适用多线程程序的场景 .
.
.
.
.
3.4 进程间通信只用 TCP .
.
3.5 多线程服务器的适用场合 . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
第 4 章 C++ 多线程系统编程精要
.
.
.
.
.
.
.
.
.
.
4.1 基本线程原语的选用 .
.
4.2 C/C++ 系统库的线程安全性 .
4.3 Linux 上的线程标识 .
.
.
4.4 线程的创建与销毁的守则 . .
.
.
.
.
.
.
.
.
.
pthread_cancel 与 C++ .
.
exit(3) 在 C++ 中不是线程安全的 . .
.
.
4.5 善用 __thread 关键字 .
4.6 多线程与 IO .
.
.
.
. .
.
.
.
.
.
. .
.
.
.
.
4.4.1
4.4.2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
目录
.
33
. 35
. 40
. 43
. 44
. 48
. 50
.
51
. 52
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
59
. 59
61
.
.
62
. 62
. 63
. 64
65
67
69
70
71
74
.
.
. .
. .
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
83
. . 84
.
.
85
.
89
.
.
91
.
.
.
94
94
.
.
96
. .
.
. 98
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Linux 多线程服务端编程:使用 muduo C++ 网络库 (excerpt) http://www.chenshuo.com/book/