天天看點

《Linux多線程服務端程式設計:使用muduo C++網絡庫》上市半年重印兩次,總印數達到了9000冊

以下談一談這本書的寫作背景與内容取舍的原因。

參加工作以來,我編寫并維護了若幹C++/Java多線程網絡服務程式,這本書總結了我在開發維護這類服務程式方面的經驗。工作中,我沒有寫過單線程的網絡服務程式,沒有寫過C語言的網絡服務程式,也沒有寫過運作在Windows下的網絡服務程式,是以本書不涉及這些内容。

第9章“分布式系統工程實踐”詳細介紹了這本書的應用背景,即開發公司内部的分布式服務系統,書中的很多決策(design decision)和技術取舍(trade-off)是在這一應用場景下做出的。以下是各章直接的交叉引用關系圖(沒有計算引用次數),其中第0章是前言,字母章節是附錄。可見第9章是被引用最多的一章,某種意義上可以說第9章既是本書的先決條件,又是本書的終極目标。由于章節之間存在衆多的交叉引用,去掉任何一章都會破壞内容的完整性。

《Linux多線程服務端程式設計:使用muduo C++網絡庫》上市半年重印兩次,總印數達到了9000冊

這本書的書名原本打算叫“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)方面的不确定性,讓讀者集中精力攻克本質問題。這本書介紹的方法不一定對于每個應用場景都是最好的,但肯定是簡便易行的,是時間成本、功能、性能的一種合理折中。

    本文轉自 陳碩  部落格園部落格,原文連結:http://www.cnblogs.com/Solstice/p/3195397.html,如需轉載請自行聯系原作者