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

以下谈一谈这本书的写作背景与内容取舍的原因。

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

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

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

这本书的书名原本打算叫“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等等内容,因为它们都只限本机进程间通信,无法扩展到多机。
  • 网络编程方面(第6、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 的常规用法。

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

  1. Linux多线程服务端编程:使用muduo C++网络库

    内容推荐本 书主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread.这 ...

  2. 《Linux多线程服务端编程——使用muduo C++网络库》读书笔记

    第一章 线程安全的对象生命期管理 第二章 线程同步精要 第三章 多线程服务器的适用场合与常用编程模型 第四章 C++多线程系统编程精要 1.(P84)11个常用的最基本Pthreads函数: 2个:线 ...

  3. Linux多线程服务端编程 使用muduo C++网络库 学习笔记 日志log

    代码来自陈硕开源代码库 muduo中 地址是https://github.com/chenshuo/muduo #pragma once #include <string> #define ...

  4. 《Linux 多线程服务端编程:使用 muduo C++ 网络库》电子版上市

    <Linux 多线程服务端编程:使用 muduo C++ 网络库> 电子版已在京东和亚马逊上市销售. 京东购买地址:http://e.jd.com/30149978.html 亚马逊Kin ...

  5. Linux多线程服务端编程一些总结

    能接触这本书是因为上一个项目是用c++开发基于Linux的消息服务器,公司没有使用第三方的网络库,卷起袖子就开撸了.个人因为从业经验较短,主 要负责的是业务方面的编码.本着兴趣自己找了这本书.拿到书就 ...

  6. 《Linux多线程服务端编程》笔记——多线程服务器的适用场合

    如果要在一台多核机器上提供一种服务或执行一个任务,可用的模式有 运行一个单线程的进程 运行一个多线程的进程 运行多个单线程的进程 运行多个多线程的进程 这些模式之间的比较已经是老生常谈,简单地总结 模 ...

  7. 《Linux多线程服务端编程》笔记——线程同步精要

    并发编程基本模型 message passing和shared memory. 线程同步的四项原则 尽量最低限度地共享对象,减少需要同步的场合.如果确实需要,优先考虑共享 immutable 对象. ...

  8. 一、智能指针及线程同步总结------linux多线程服务端编程

    更新2.0 二.多线程及服务器编程总结------linux多线程服务端编程 https://www.cnblogs.com/l2017/p/11335609.html 三.分布式编程总结------ ...

  9. 陈硕 - Linux 多线程服务端编程 - muduo 网络库作者

    http://chenshuo.com/book/ Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)http://blog.csdn.net/nk_test/ ...

随机推荐

  1. iOS内存管理

    iOS内存管理的方式是引用计数机制.分为MRC(人式引用计数)和ARC(自动引用计数). 为什么要学习内存管理? 内存管理方式是引用计数机制,通过控制对象的引用计数来实现操作对象的功能.一个对象的生命 ...

  2. ubuntu和win7 64双系统 安装

    我是thinkpad机器 主要参考到是http://www.weste.net/2012/4-29/82173.html这个地址到教程,表示感谢 这边说下安装好以后到环境设置 首先要 sudo apt ...

  3. Windows性能优化关键点-Windows Performance tuning important settings

    最近重装了windows8系统,发现性能差得很,远不如官方说的比win7好很多的说法.经过几个关键配置的调整,终于找回电脑原来的风采. 下面总结一下,希望对大家有帮助: 1. 检查windows服务, ...

  4. 免费而优秀的图表JS插件

    1.百度的Echart ECharts,缩写来自Enterprise Charts,是百度推出的一款开源的,商业级数据图表,它最初是为了满足百度公司商业体系里各种业务系统(如凤巢.广告管家等等)的报表 ...

  5. 44、NLP的其他分词功能测试

    1. 命名实体识别功能测试 @Test public void testNer(){ if (NER.create("ltp_data/ner.model")<0) { Sy ...

  6. 使用UltraISO制作U盘启动盘——转载

    现在流行用U盘来安装系统,但要用U盘来安装系统的前提条件下是如何将镜像文件写入到U盘里,UltraISO能很好的满足你的需求. 步骤/方法  鼠标右键“以管理员身份运行”UltraISO图标    打 ...

  7. VC++中StretchBlt图像失真问题的解决办法

    在 VC 中使用 StretchBlt 会碰到一些与点阵图大小缩放相关的一些问题.在扩展一个点阵图时,StretchBlt必须复制图素行或列.如果放大倍数不是原图的整数倍,那么此操作会造成产生的图像有 ...

  8. 如何在cluster上跑R脚本

    R 是一个比较不错但是有时候操蛋的语言,不错是因为用着爽的时候真的很爽,操蛋是因为这种爽不是什么时候都可以的,比如说在cluster上批处理跑R脚本. 当然说这话有些在上面跑过的各种不服气,你丫傻逼吧 ...

  9. gslX680驱动的移植实践

    将gslX680触摸屏驱动移植到自己的开发板上(对应的源码文件gslX680.c),并且实现可以使用make menuconfig进行动态的加载和卸载 因为触摸屏设备属于一种典型的输入设备,所以他的驱 ...

  10. 调用SAP函数创建寄售退货订单的时候报错:业务对象 BUS2032 是销售订单,销售凭证类别 H 是退货。

    RE.KR订单类型用BAPI_CUSTOMERRETURN_CREATE 其他用BAPI_SALESORDER_CREATEFROMDAT2