《Linux多线程服务端编程:使用muduo C++网络库》上市半年重印两次,总印数达到了9000册

《Linux多线程服务端编程:使用muduo C++网络库》这本书自今年一月上市以来,半年以内已经重印两次(加上首印,一共是三次印刷),总印数达到了9000册,这在技术书里已经算是至关不错的成绩。本书购买方式见配套网站 http://chenshuo.com/book程序员

如下谈一谈这本书的写做背景与内容取舍的缘由。编程

参加工做以来,我编写并维护了若干C++/Java多线程网络服务程序,这本书总结了我在开发维护这类服务程序方面的经验。工做中,我没有写过单线程的网络服务程序,没有写过C语言的网络服务程序,也没有写过运行在Windows下的网络服务程序,所以本书不涉及这些内容。性能优化

在“Linux服务端开发”这一背景下,这本书主要讲三个方面的内容[1]:现代C++、多线程、网络编程,分别对应书的第三、一、2部分。这不是一本入门书,本书的读者应该在以上三方面已经具有至关的基础[2]:网络编程方面,能轻松读懂6.1节的两个Python程序;C++方面,对12.8节的代码不感到陌生;多线程方面,能明白第1章要解决什么问题。服务器

第9章“分布式系统工程实践”详细介绍了这本书的应用背景,即开发公司内部的分布式服务系统,书中的不少决策(design decision)和技术取舍(trade-off)是在这一应用场景下作出的。如下是各章直接的交叉引用关系图(没有计算引用次数),其中第0章是前言,字母章节是附录。可见第9章是被引用最多的一章,某种意义上能够说第9章既是本书的先决条件,又是本书的终极目标。因为章节之间存在众多的交叉引用,去掉任何一章都会破坏内容的完整性。网络

ref

这本书的书名本来打算叫“Linux C++ 多线程系统编程”。写完以后发现,与其余Unix/Linux系统编程方面的书不一样,这本书有明确的应用场景,所以能够砍掉不少内容,突出重点。甚至能够说我主要讲别的书没有讲到的内容。这不是一本面面俱到的书,所以最终的书名也就不叫“系统编程”了。数据结构

同时,我认为不少教科书上介绍的一些作法是过期的(signal),一些是不推荐使用的(从外部终止线程、TCP OOB数据),一些是大多数状况下不必使用的(内存池、lock-free 编程)。做为全面的教材和手册,把这些内容放进去能够理解。可是这本书定位是经验总结,我略去了教科书上那些基本用不到的知识点,以避免喧宾夺主,也建议读者不要把精力花在那些次要问题上。多线程

  • 这本书没有花很大的篇幅去讲signal,而是在第4.10节说明多线程程序不要使用signal做为IPC。而且,在muduo-protorpc的示例中给出了Linux专有的signalfd(2)的用法,能够避免传统signal handler的常见陷阱,也更符合UNIX的“everything is a file”哲学。第4.4节说明不要从外部终止线程,所以也就没必要去细究Pthreads cancellation point了。多线程程序最好不要fork()(第4.9节)。
  • 这本书没介绍daemon进程,我认为daemon是过期的作法。由于daemon进程的父进程是init(1),配置文件在本机,不便于多机统一监控与管理(第9.8节)。(注:若是是第三方标准的服务程序,又不须要常常升级或改配置重启,而且一旦崩溃,重启就能继续服务,那么作成 daemon 让init(1)接管是能够的,好比ntpd、sshd等。本书谈的是本身开发维护的服务程序。)另外,Java/Python/Go写的服务程序彷佛也没有作成daemon的习惯,C++程序没有理由要特殊对待。补充一点,Linux的进程管理机制很落后(从UNIX继承而来),子进程退出的事件只能被父进程以SIGCHLD信号的方式收到(并且这个signal可能丢失),kill(pid) 也存在不少race condition(你怎么保证pid在kill以前的一瞬间还表明你想kill的那个进程,而不是一个新启动的进程?close(fd)就不会有这种 race condition。)。这些困难在用户态没法克服,只能修改内核,引入新的系统调用才能治本。例如 FreeBSD 9.0 引入了 pdfork()/pdkill() 等,将子进程变成文件描述符,这样就能用IO事件框架统一处理了,也符合UNIX的“everything is a file”哲学。希望Linux内核也能尽快引入相似的系统调用,减轻程序员的负担。
  • 这本书没有讲内存池,而是说明不是每一个程序都要本身写内存池(§12.2.8)。这本书也没有把“避免内存碎片”挂在嘴边,而是论证为何通常的程序没必要在乎它(§A.1.8);
  • 这本书只关注Linux,不考虑移植性。它推荐使用Linux专有的gettid()系统调用做为线程标识(第4.3节),而不是用pthread_self()。
  • 这本书不讲POSIX中五花八门的定时函数,而专讲用Linux特有的timerfd来实现高精度定时(§7.8.2),由于它能方便地融入IO事件处理框架。muduo直接使用C++标准库来管理定时器,而不是本身实现小顶堆(heap),这样能够简化实现(§8.2.1)。
  • 这本书只讲mutex和condition variable做为最基础的线程同步手段(第2章),而且我认为一个C++多线程程序代码里不该该直接出现pthread_mutex_lock之类的基本Pthreads调用。本书进一步建议只使用非递归的mutex(§2.1.1),这与某些网上文章的推荐正好相反。这本书第2.3节甚至建议不要使用读写锁和信号量(semaphore),由于一是容易用错,二是不见得能提升性能。mutex和condition variable是完备的,能实现多种更易用的同步设施,例如CountDownLatch和BlockingQueue。§12.8.3的代码展现了用BlockingQueue和ThreadPool控制并发度的手法,作到了“No locks. No condition variables. No callbacks.”
  • 这本书不讲lock-free编程,由于编写可靠的lock-free代码并分析验证其正确性的难度远大于编写普通的使用mutex和condition variable的多线程代码,后者已经有了至关成熟的理论和工具。我认为lock-free不是每一个多线程程序员应该掌握的技术,它投入高而用处少,能够适当了解,但不值得每一个人都去深究。只须要少数人用它实现封装好的数据结构,像我这样的普通人就能够受益。
  • 这本书只讲BSD Sockets做为进程间通讯的手段,而且只用TCP长链接(§3.4)。这样就砍掉了pipe、FIFO、POSIX message queue、shared memory、STREAMS、UNIX domain socket等等内容,由于它们都只限本机进程间通讯,没法扩展到多机。
  • 网络编程方面(第六、7章),这本书不讲Sockets API的基本用法,并且代码中也不会直接使用它们。我认为在程序中直接使用 Sockets API是初学者的作法,当写一个新网络服务程序,若是一开始考虑的是怎么组织accept、read、epoll_wait等调用,这种作法无异于用铅笔刀锯大树,事倍功半,也不利于未来的功能扩展和维护。稍微像样点的公司都会用成熟的网络库(不必定开源),把网络编程的复杂性封装进去,暴露出良好易用的接口,让开发人员使用更高层的building blocks(消息传递或RPC)从功能的角度去设计程序,避免一次次反复掉到TCP网络编程的坑里。多个服务程序共享相同的基础库和事件处理框架的益处是显而易见的,一方面把网络编程的复杂性集中到一块儿,避免每一个团队都去踏一遍坑;另外一方面,基础库的bug修复与性能优化能惠及用到它的所有服务程序;最后,程序结构上的类似性让编程经验更加通用,多个服务程序在功能、性能、正确性等方面具备共性,能触类旁通举一反三,下降未来开发维护的成本。应该避免每一个程序都另起炉灶,单独设计其IO事件处理结构。
  • 这本书只讲非阻塞IO结合IO复用(IO-Multiplexing)这一种并发风格(概括为三个半事件),并介绍在多线程下的扩展(one loop per thread)。IO复用方面,本书只讲level-trigger,不讲edge-trigger。一方面目前没有up to date的测试代表ET更快,相反,我认为LT在读取数据时能够节约一次read()调用(§8.7.2);另外一方面,LT模式更容易与其余第三方库结合(§7.15)。多线程程序管理并发socket fd有不少风格可供选择,例如epoll fd是多个线程共享一个(多对一)仍是每一个线程有本身的epoll fd(一对一),每一个socket fd是只属于一个epoll fd(多对一)仍是能够同时属于多个 epoll fd(多对多),每一个socket fd是只能被固定的一个线程读写仍是能够被多个线程读写(若是是后者,那么读写的时候是加锁仍是使用ONESHOT)。以上不是每种均可行,本书也没有一一加以分析,而是建议使用one loop per thread这种适用性较强的风格,首先是正确性容易验证,其次是性能也能知足要求。
  • 本书不讲IPv6,由于目前世界上最大的公司的服务机群也用不完一个私有A类地址(10.0.0.0/8)。本书不讲UDP,由于《Unix网络编程》已经讲得很好了。
  • 这本书举的网络编程的例子再也不是简单的echo服务,而是有格式(所以引入codec)、多链接之间会交换数据的网络程序,更接近业务场景,也借机讲解如何避免TCP网络编程的常见陷阱。而且在示例代码中给出了分布式单词计数、多机求中位数等稍微复杂一点的程序。
  • 在C++方面,这本书没有介绍动态连接库热更新这种“高级”技术,而是说明,在分布式系统中,为了部署方便,应该从源码编译所有的库,与主程序连接为一个standalone的可执行文件,以减少对运行环境的依赖(第10章)。第11章还讨论了程序库与应用程序之间的接口设计。

“信息”按照香农的定义,是“减小不肯定性”,这本书包含的信息正是减小选用编程设施(facilities)方面的不肯定性,让读者集中精力攻克本质问题。这本书介绍的方法不必定对于每一个应用场景都是最好的,但确定是简便易行的,是时间成本、功能、性能的一种合理折中。并发


[1] 这本书前言的第一句话“本书主要讲述采用现代 C++ 在 x86-64 Linux 上编写多线程 TCP 网络服务程序的主流常规技术”,封面印着“示范在多核时代采用现代 C++ 编写多线程 TCP 网络服务器的正规作法”。框架

[2] 前言写到:读者应该已经大体读过《现代操做系统》、《UNIX 环境高级编程》、《UNIX 网络编程》、《C++ Primer》或与以内容相近的书籍,熟悉基本概念,并掌握 Pthreads 和 Sockets API 的常规用法。dom

相关文章
相关标签/搜索