《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

相關文章
相關標籤/搜索