• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            陳碩的Blog

            置頂隨筆

            [置頂]《Linux 多線程服務(wù)端編程:使用 muduo C++ 網(wǎng)絡(luò)庫》網(wǎng)上書店預(yù)訂

            內(nèi)容簡介

            本書主要講述采用現(xiàn)代 C++ 在 x86-64 Linux 上編寫多線程 TCP 網(wǎng)絡(luò)服務(wù)程序的主流常規(guī)技術(shù),重點講解一種適應(yīng)性較強的多線程服務(wù)器的編程模型,即 one loop per thread。這是在 Linux 下以 native 語言編寫用戶態(tài)高性能網(wǎng)絡(luò)程序最成熟的模式,掌握之后可順利地開發(fā)各類常見的服務(wù)端網(wǎng)絡(luò)應(yīng)用程序。本書以 muduo 網(wǎng)絡(luò)庫為例,講解這種編程模型的使用方法及注意事項。

            本書的宗旨是貴精不貴多。掌握兩種基本的同步原語就可以滿足各種多線程同步的功能需求,還能寫出更易用的同步設(shè)施。掌握一種進程間通信方式和一種多線程網(wǎng)絡(luò)編程模型就足以應(yīng)對日常開發(fā)任務(wù),編寫運行于公司內(nèi)網(wǎng)環(huán)境的分布式服務(wù)統(tǒng)。

            基本信息

            cover

            出版社:電子工業(yè)出版社

            頁數(shù):xvi+600

            定價:人民幣89元

            ISBN:9787121192821

            豆瓣及網(wǎng)上書店預(yù)訂

            豆瓣:http://book.douban.com/subject/20471211/
            互動:http://product.china-pub.com/3021861
            亞馬遜:http://www.amazon.cn/dp/B00AYS2KL0
            當當:http://product.dangdang.com/product.aspx?product_id=23162953
            京東:http://book.360buy.com/11163782.html

            試讀樣章

            前言與目錄:https://chenshuo-public.s3.amazonaws.com/pdf/preamble.pdf
            第1章:線程安全的對象生命期管理:https://chenshuo-public.s3.amazonaws.com/pdf/chap1.pdf
            第6章:muduo網(wǎng)絡(luò)庫簡介:https://chenshuo-public.s3.amazonaws.com/pdf/chap6.pdf
            附錄:https://chenshuo-public.s3.amazonaws.com/pdf/appendix.pdf
            樣章合集下載:http://vdisk.weibo.com/s/mtupb 共150頁,包括第 11.5 節(jié)。

            前言(節(jié)選)

            本書主要講述采用現(xiàn)代 C++ 在 x86-64 Linux 上編寫多線程 TCP 網(wǎng)絡(luò)服務(wù)程序的主流常規(guī)技術(shù),這也是我對過去 5 年編寫生產(chǎn)環(huán)境下的多線程服務(wù)端程序的經(jīng)驗總結(jié)。本書重點講解多線程網(wǎng)絡(luò)服務(wù)器的一種 IO 模型,即 one loop per thread。這是一種適應(yīng)性較強的模型,也是 Linux 下以 native 語言編寫用戶態(tài)高性能網(wǎng)絡(luò)程序最成熟的模式, 掌握之后可順利地開發(fā)各類常見的服務(wù)端網(wǎng)絡(luò)應(yīng)用程序。本書以 muduo 網(wǎng)絡(luò)庫為例,講解這種編程模型的使用方法及注意事項。

            muduo 是一個基于非阻塞 IO 和事件驅(qū)動的現(xiàn)代 C++ 網(wǎng)絡(luò)庫,原生支持 one loop per thread 這種 IO 模型。muduo 適合開發(fā) Linux 下的面向業(yè)務(wù)的多線程服務(wù)端網(wǎng)絡(luò)應(yīng)用程序,其中“面向業(yè)務(wù)的網(wǎng)絡(luò)編程”的定義見附錄 A。 “現(xiàn)代 C++”指的不是 C++11 新標準,而是 2005 年 TR1 發(fā)布之后的 C++ 語言和庫。 與傳統(tǒng) C++ 相比,現(xiàn)代 C++ 的變化主要有兩方面:資源管理(見第 1 章)與事件回調(diào)(見第 449 頁)。

            本書不是多線程編程教程,也不是網(wǎng)絡(luò)編程教程,更不是 C++ 教程。讀者應(yīng)該已經(jīng)大致讀過《UNIX 環(huán)境高級編程》、《UNIX 網(wǎng)絡(luò)編程》、《C++ Primer》或與之內(nèi)容相近的書籍。本書不談 C++11,因為目前(2012 年)主流的 Linux 服務(wù)端發(fā)行版的 g++ 版本都還停留在 4.4,C++11 進入實用尚需一段時日。

            本書適用的硬件環(huán)境是主流 x86-64 服務(wù)器,多路多核 CPU、幾十 GB 內(nèi)存、千兆以太網(wǎng)互聯(lián)。除了第 5 章講診斷日志之外,本書不涉及文件 IO。

            本書分為四大部分,第 1 部分“C++ 多線程系統(tǒng)編程”考察多線程下的對象生命期管理、線程同步方法、多線程與 C++ 的結(jié)合、高效的多線程日志等。第 2 部分“muduo 網(wǎng)絡(luò)庫”介紹使用現(xiàn)成的非阻塞網(wǎng)絡(luò)庫編寫網(wǎng)絡(luò)應(yīng)用程序的方法,以及 muduo 的設(shè)計與實現(xiàn)。第 3 部分“工程實踐經(jīng)驗談”介紹分布式系統(tǒng)的工程化開發(fā)方法和 C++ 在工程實踐中的功能特性取舍。第 4 部分“附錄”分享網(wǎng)絡(luò)編程和 C++ 語言的學(xué)習(xí)經(jīng)驗。

            本書的宗旨是貴精不貴多。掌握兩種基本的同步原語就可以滿足各種多線程同步的功能需求,還能寫出更易用的同步設(shè)施。掌握一種進程間通信方式和一種多線程網(wǎng)絡(luò)編程模型就足以應(yīng)對日常開發(fā)任務(wù),編寫運行于公司內(nèi)網(wǎng)環(huán)境的分布式服務(wù)系統(tǒng)。(本書不涉及分布式存儲系統(tǒng),也不涉及 UDP。)

            術(shù)語與排版范例


            本書大量使用英文術(shù)語,甚至有少量英文引文。設(shè)計模式的名字一律用英文,例如 Observer、Reactor、Singleton。在中文術(shù)語不夠突出時,也會使用英文,例如 class、heap、event loop、STL algorithm 等。注意幾個中文 C++ 術(shù)語:對象實體(instance) 、函數(shù)重載決議(resolution) 、模板具現(xiàn)化(instantiation) 、覆寫(override)虛函數(shù)、提領(lǐng)(dereference)指針。本書中的英語可數(shù)名詞一般不用復(fù)數(shù)形式,例如兩個 class,6 個 syscall;但有時會用 (s) 強調(diào)中文名詞是復(fù)數(shù)。fd 是文件描述符(file descriptor)的縮寫。“CPU 數(shù)目”一般指的是核(core)的數(shù)目。用諸如§11.5 表示本書第 11.5 節(jié),L42 表示上下文中出現(xiàn)的第 42 行代碼。[JCP]、[CC2e] 等是參考文獻,見書末清單。

            代碼

            本書的示例代碼以開源項目的形式發(fā)布在 GitHub 上,
            地址是 http://github.com/chenshuo/recipes/http://github.com/chenshuo/muduo/ 。本書配套頁面提供全部源代碼打包下載,正文中出現(xiàn)的類似 recipes/thread 的路徑是壓縮包內(nèi)的相對路徑,讀者不難找到其對應(yīng)的 GitHub URL。

            本書假定讀者熟悉 diff -u 命令的輸出格式,用于表示代碼的改動。

            本書正文中出現(xiàn)的代碼有時為了照顧排版而略有改寫,例如改變縮進規(guī)則,去掉單行條件語句前后的花括號等。就編程風(fēng)格而論,應(yīng)以電子版代碼為準。

            聯(lián)系方式


            郵箱:giantchen_at_gmail.com

            主頁:http://chenshuo.com/book (正文和腳注中出現(xiàn)的 URL 可從這里找到。 )

            微博:http://weibo.com/giantchen

            博客:http://blog.csdn.net/Solstice

            代碼:http://github.com/chenshuo

            陳碩

            中國•香港

            posted @ 2013-01-11 12:43 陳碩 閱讀(4864) | 評論 (6)編輯 收藏

            2014年12月3日

            在 Boolan 網(wǎng)開講《網(wǎng)絡(luò)編程實戰(zhàn)》課程

            《網(wǎng)絡(luò)編程實戰(zhàn)》是一門以講解實例為主的課程,每一節(jié)都講一兩個網(wǎng)絡(luò)編程的例子程序,課程偏重 Linux 服務(wù)端 TCP 網(wǎng)絡(luò)編程。

            本課程要求聽課人員已經(jīng)讀過《Unix 網(wǎng)絡(luò)編程》,能寫簡單的 TCP echo 服務(wù)。

             

            課程地址:http://boolan.com/course/4

            配套頁面:http://chenshuo.com/pnp

            posted @ 2014-12-03 01:50 陳碩 閱讀(4163) | 評論 (1)編輯 收藏

            2013年11月1日

            《Linux 多線程服務(wù)端編程:使用 muduo C++ 網(wǎng)絡(luò)庫》電子版上市

            《Linux 多線程服務(wù)端編程:使用 muduo C++ 網(wǎng)絡(luò)庫》 電子版已在京東上市銷售。

            購買地址:http://e.jd.com/30149978.html

            閱讀效果:

            PC

            ebookpc

            iPad

            ebookipad

            目前京東的閱讀器沒有切白邊功能,值得改進。

            posted @ 2013-11-01 10:35 陳碩 閱讀(8052) | 評論 (1)編輯 收藏

            2013年10月11日

            C++面試中string類的一種正確簡明的寫法

            本文首發(fā)于酷殼網(wǎng) http://coolshell.cn/articles/10478.html

            先說說程序員(應(yīng)屆生)面試的一般過程,一輪面試(面對一到兩個面試官)一般是四、五十分鐘,面試官會問兩三個編程問題(通常是兩大一小),因此留給每個編程題的時間只有 20 分鐘。這 20 分鐘不光是寫代碼,還要跟面試官討論你的答案并解答提問,比如面試官拿過你的答案紙,問某一行代碼如果修改會有什么后果。因此真正留給在紙上或白板上寫代碼的時間也就 10 分鐘上下。本文給出了一個能用 10 分鐘時間在紙上寫出來且不會有錯的 String class,強調(diào)正確性及易實現(xiàn)(白板上寫也不會錯),不強調(diào)效率與功能完備。

            本文的配套代碼位于 https://github.com/chenshuo/recipes/blob/master/string/StringTrivial.h

            全文:https://chenshuo.googlecode.com/files/CppEngineering.pdf

            posted @ 2013-10-11 10:59 陳碩 閱讀(4798) | 評論 (0)編輯 收藏

            2013年10月10日

            讀者來信與解答 1

            讀者來信用黑色,我的回答用藍色。經(jīng)過整理,接近對話體。


            > 陳碩,你好,
            >
            > 閱讀了你的書,很有收獲。
            > 但是沒有在moduo的源代碼里面找到實現(xiàn)線程模型11的例子。即one thread per loop + thread pool。
            > 謝謝。

            書第 173 頁圖 6-14 下面的第一段話,具體改動方法參考前一頁的 diff。


            > 謝謝。
            >
            > 另外TcpConnection和Channel的生命周期管理有點問題。
            > TcpConnection如果已經(jīng)被回收了,其包含的Channel也已經(jīng)被回收了。而這個時候在Channel::handleEvent()里面檢查tied_和tie_是危險的。因為其內(nèi)存已經(jīng)被回收了。
            >
            > 如果用戶保證TcpConnection被回收之后,不會再用Channel的裸指針,則沒有必要在TcpConnection::connectEstablished()中call tie().

            TcpConnection 回收之前,會調(diào)用 connectDestroyed,其中調(diào)用 channel_->remove();,這樣就不可能再會有 Channel::handleEvent() 被調(diào)用了。

            tie() 的作用是防止 Channel::handleEvent() 運行期間其 owner 對象析構(gòu),導(dǎo)致 Channel 本身被銷毀。


            > > TcpConnection 回收之前,會調(diào)用 connectDestroyed,其中調(diào)用 channel_->remove();,這樣就不可能再會有 Channel::handleEvent() 被調(diào)用了
            > 這個時候會不會有race condition?假設(shè)現(xiàn)在有兩個active channels,處理頭一個的時候回收TcpConnection,而第二個channel剛好對應(yīng)這個connection。

            這時你沒有辦法強制銷毀 TcpConnection,只能降低其引用計數(shù),所以不會有問題。你可以寫段代碼試試。

            > 另外底層的poller OS api是否保證unregister channel之后一定不會再有這個channel的事件,會清空內(nèi)核的已經(jīng)就緒的事件隊列?

            跟內(nèi)核沒關(guān)系,Poller class 在 unregister channel 之后就不可能調(diào)用其 handleEvent() 成員函數(shù)。

            > 那EPollPoller::fillActiveChannels()的改一改,“assert(it != channels_.end());”不再適用了,而且每次都個event都要查一次map。效率會有問題。

            assert() 只有在 debug build 才執(zhí)行,不會影響效率。
            再說每個 event 都要涉及 read/write 等系統(tǒng)調(diào)用,開銷比“查一次 map”大得多,優(yōu)化這里是無用功。

            > 但這個assert()不是invalid了嗎?你可能之前在unregister channel的時候已經(jīng)從map里面remove掉了它。

            這個 assert 是有效的,你再想想。


            > > tie() 的作用是防止 Channel::handleEvent() 運行期間其 owner 對象析構(gòu),導(dǎo)致 Channel 本身被銷毀。

            > 這個也不太make sense。仍然有race conditon。在Channel::handleEvent()擁有g(shù)uard鎖定ownner之前,Channel::handleEvent()需要檢查其tied_。

            你再想想,tie 的作用是防止調(diào)用 handleEvent() 期間對象銷毀(比如調(diào)用 closeCallback 期間),不是也不可能防止調(diào)用 handleEvent() 之前對象銷毀。

            > 恩,是的。整個TcpConnection, Channel, EventLoop都是一個thread里面run的。

            posted @ 2013-10-10 12:17 陳碩 閱讀(1473) | 評論 (0)編輯 收藏

            2013年10月6日

            [轉(zhuǎn)載]賴勇浩:推薦《Linux 多線程服務(wù)器端編程》

            推薦《Linux 多線程服務(wù)器端編程》 

            賴勇浩(http://laiyonghao.com)

            最近,有一位朋友因為工作需要,需要從網(wǎng)游的客戶端編程轉(zhuǎn)向服務(wù)器端編程,找我推薦一本書。我推薦了《Linux 多線程服務(wù)器端編程——使用 muduo C++ 網(wǎng)絡(luò)庫》給他,他在網(wǎng)上書店看了以后問我為什么推薦這么厚一本書給他,正好這本書我已經(jīng)早就看完了,一直也想寫篇“書評”,就在這里多扯幾句。其實實在算不上書評,原因有二:一是讀書的時候囫圇吞棗,理解不夠深刻,不深刻自然不能評;二是這幾年雖然在 Linux 下寫服務(wù)器端的網(wǎng)絡(luò)程序,但很少用多線程,也很少用 C++,書里談的東西,算是不熟悉的領(lǐng)域,自然也不能亂評。所以今天這篇,應(yīng)當是推薦,是為陳碩老師背書。

            繼續(xù)閱讀:

            http://blog.csdn.net/gzlaiyonghao/article/details/10366863

            http://book.douban.com/review/6249351/

            posted @ 2013-10-06 02:15 陳碩 閱讀(1594) | 評論 (0)編輯 收藏

            2013年9月9日

            用條件變量實現(xiàn)事件等待器的正確與錯誤做法

            TL;DR 如果你能一眼看出 https://gist.github.com/chenshuo/6430925 中的那 8 個 Waiter classes 哪些是對的哪些是錯的,本文就不必看了。

            前幾天,我發(fā)了一條微博 http://weibo.com/1701018393/A7FrW7ZVd ,質(zhì)疑某本書對 Pthreads 條件變量的封裝是錯的,因為它沒有把 mutex 的 lock()/unlock() 函數(shù)暴露出來,導(dǎo)致無法實用。后來大家討論的分歧是這個 cond class 是不是通用的條件變量封裝,還是只是一個特殊的“事件等待器”。作為事件等待器,其實現(xiàn)也是錯的,因為存在丟失事件的可能,可以算是初學(xué)者使用條件變量的典型錯誤。

            本文的代碼位于 recipes/thread/test/Waiter_test.cc,這里提到的某書的版本相當于 Waiter1 class。

            我在拙作《Linux 多線程服務(wù)端編程:使用 muduo C++ 網(wǎng)絡(luò)庫》第 2.2 節(jié)總結(jié)了條件變量的使用要點:

            條件變量只有一種正確使用的方式,幾乎不可能用錯。對于 wait 端:
            1. 必須與 mutex 一起使用,該布爾表達式的讀寫需受此 mutex 保護。
            2. 在 mutex 已上鎖的時候才能調(diào)用 wait()。
            3. 把判斷布爾條件和 wait() 放到 while 循環(huán)中。

            對于 signal/broadcast 端:
            1. 不一定要在 mutex 已上鎖的情況下調(diào)用 signal (理論上)。
            2. 在 signal 之前一般要修改布爾表達式。
            3. 修改布爾表達式通常要用 mutex 保護(至少用作 full memory barrier)。
            4. 注意區(qū)分 signal 與 broadcast:“broadcast 通常用于表明狀態(tài)變化,signal 通常用于表示資源可用。(broadcast should generally be used to indicate state change rather than resource availability。)”

            如果用條件變量來實現(xiàn)一個“事件等待器/Waiter”,正確的做法是怎樣的?我的最終答案見 WaiterInMuduo class。“事件等待器”的一種用途是程序啟動時等待初始化完成,也可以直接用 muduo::CountDownLatch 到達相同的目的,將初值設(shè)為 1 即可。

            以下根據(jù)微博上的討論過程給出幾個正確或錯誤的版本,博大家一笑。只要記住 Pthread 的條件變量是邊沿觸發(fā)(edge trigger),即 signal()/broadcast() 只會喚醒已經(jīng)等在 wait() 上的線程(s),我們在編碼時必須要考慮 signal() 早于 wait() 的可能,那么就很容易判斷以下各個版本的正誤了。代碼見 recipes/thread/test/Waiter_test.cc

            版本一:錯誤。某書上的原始版,有丟失事件的可能。

            1

            版本二:錯誤。lock() 之后再 signal(),同樣有丟失事件的可能。

            2

            版本三:錯誤。引入了 bool signaled_; 條件,但沒有正確處理 spurious wakeup。

            版本四五六:正確。僅限 single waiter 使用。

            版本七:最佳。可供 multiple waiters 使用。

            版本八:錯誤。存在 data race,且有丟失事件的可能。理由見 http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex

            總結(jié):使用條件變量,調(diào)用 signal() 的時候無法知道是否已經(jīng)有線程等待在 wait() 上。因此一般總是要先修改“條件”,使其為 true,再調(diào)用 signal();這樣 wait 線程先檢查“條件”,只有當條件不成立時才去 wait(),避免了丟事件的可能。換言之,通過使用“條件”,將邊沿觸發(fā)(edge trigger)改為電平觸發(fā)(level trigger)。這里“修改條件”和“檢查條件”都必須在 mutex 保護下進行,而且這個 mutex 必須用于配合 wait()。

            思考題:如果用兩個 mutex,一個用于保護“條件”,另一個專門用于和 cond 配合 wait(),會出現(xiàn)什么情況?

            最后注明一點,http://stackoverflow.com/questions/6419117/signal-and-unlock-order 這篇帖子里對 spurious wakeup 的解釋是錯的,spurious wakeup 指的是一次 signal() 調(diào)用喚醒兩個或以上 wait()ing 的線程,或者沒有調(diào)用 signal() 卻有線程從 wait() 返回。manpage 里對 Pthreads 系列函數(shù)的介紹非常到位,值得細讀。

            posted @ 2013-09-09 03:01 陳碩 閱讀(14148) | 評論 (21)編輯 收藏

            2013年8月18日

            用muduo實現(xiàn)memcached協(xié)議的例子

            最近花了兩天時間用 muduo 部分實現(xiàn)了 memcached 服務(wù)器協(xié)議,代碼位于 examples/memcached/server,能通過 memcached 的大部分測試用例(incr/decr 還沒有實現(xiàn))。

            這不是 memcached 的替代品(它沒有實現(xiàn)LRU和超時功能,也沒有實現(xiàn)二進制協(xié)議,更沒有自己管理內(nèi)存),而是一個網(wǎng)絡(luò)編程的示例(代碼只有 1000 行,比 memcached 小很多),展示 muduo 風(fēng)格的事件驅(qū)動編程,以及將來性能優(yōu)化的試驗品(換句話說,現(xiàn)在這個版本完全沒有在性能上做出任何努力)。讀過 memcached 代碼的人可以對比這兩種編程風(fēng)格的區(qū)別,memcached 的 read/write 操作穿插于正常邏輯處理,而 muduo 的網(wǎng)絡(luò)數(shù)據(jù)讀寫是由庫完成,應(yīng)用程序只關(guān)心消息收發(fā),目前二者的基本 get/set 操作的性能相當。

            現(xiàn)在 muduo 的 inspector 內(nèi)置了 gperftools 的遠程 profiling 功能,memcached-debug 展示了其用法。

            為什么不必優(yōu)化 set 操作(含 set/add/update/append/prepend/cas 等)的性能?

            1. 比例。既然是 memcache,那么 get:set 的比例很高,10:1 甚至更高,因此優(yōu)化的重心應(yīng)該是 get 而非 set。

            假設(shè) memcached 能處理 100k QPS,再假設(shè)這些操作都是 set(其實應(yīng)該不到 10% 是 set),再假設(shè)所有的 set 都是串行執(zhí)行的(沒有并發(fā)),那么每次 set 的 CPU 時間不應(yīng)該超過 10 us(含服務(wù)器本地的網(wǎng)絡(luò)代碼運行時間,但不含網(wǎng)絡(luò)延遲)。而實際上一次 set 的 CPU 時間最多是 2~3 us (用 memcached-footprint 程序測得),根本不值得優(yōu)化。

            2. 網(wǎng)絡(luò)帶寬。假設(shè)一次 set 操作的 key + value 的長度是 1k bytes,TCP 的有效載荷帶寬按110MB/s估算,那么1kB數(shù)據(jù)在千兆網(wǎng)上的慣性延遲是 9us(傳輸延遲是幾十上百微秒,與此無關(guān)),也就是說服務(wù)器的網(wǎng)卡收到這 1kB 數(shù)據(jù)需要花 9us 時間(從第一個字節(jié)到達到服務(wù)器到收完最后一個字節(jié)),那么在 set 耗時 2~3 us 的情況下再去優(yōu)化它是做無用功。

            3. 產(chǎn)生“需要更新的數(shù)據(jù)”的成本遠大于 memcached set 的開銷。memcached 需要更新,往往是將已寫入數(shù)據(jù)庫的新數(shù)據(jù)放到 memcached 中,那么寫數(shù)據(jù)庫的開銷遠遠大于 memcached set 的開銷,優(yōu)化 set 對提升系統(tǒng)整體性能沒意義。

            posted @ 2013-08-18 12:59 陳碩 閱讀(3460) | 評論 (0)編輯 收藏

            2013年8月12日

            近期微博吐槽言論存檔,涉及“性能優(yōu)化”、C++陋習(xí)等

            寫C++程序的幾個陋習(xí):class 名以大寫 C 開頭,例如 CDate;成員變量以 m_ 開頭;變量采用匈牙利命名法;不知道何時禁用 copy-ctor/assign operator。前三個可能是從MFC那里傳下來的,當時C++、class、OO是新玩意兒,要與 C struct 區(qū)分,現(xiàn)在還這么做就土了。C++的成員變量可用特殊命名格式,加下劃線后綴即可(加下劃線前綴是錯的)。但在 Java 里不必模仿 C++ 的這種成員變量命名方式,IDE 可以讓成員變量以不同的顏色顯示,與局部變量區(qū)分,根本無需特殊命名。寫程序就怕把以前的編程經(jīng)驗不加區(qū)分地應(yīng)用到新語言中,寫成四不像,不地道。

            知道禁用 copy-ctor/assign operator 是 C++ 程序員的試金石。在看到一個開源項目時,我一般會先查看其 RAII handle class 是否禁用了 copy-ctor/assign operator(例如 Thread、Mutex、CondVar、Connection),如果沒有,對其第一印象就很差了。

            關(guān)于 class 命名風(fēng)格,Google、LLVM、Mozilla、muduo 都采用 Pascal 風(fēng)格(LikeThis),例如 EventLoop、SudokuSolver 等等。正巧它們也都是用 2 格縮進的,可以用 clang-format 自動格式化代碼。

            順便說說我不認同的兩個 C++ 教條:1. 用nullptr替換NULL,2. 用cstdio頭文件替換stdio.h。

            因為例如 gettimeofday(&tv, NULL) 這種系統(tǒng)函數(shù)傳個 nullptr 進去實在是違和,現(xiàn)在用 NULL 也能達到 nullptr 的好處,大不了在某個頭文件里define一下就行。這條將來或許會變。

            另外 ctime 頭文件沒定義 std::gmtime_r,而 time.h 定義了 ::gmtime_r。我可不想去背哪些函數(shù)是 C 語言的哪些是 Posix 的,哪些頭文件是 C 語言的哪些是 Posix 的(在Linux下,二者基本不分家)。為了用幾個系統(tǒng)函數(shù)(例如 fcntl() ),我該 include cfcntl 還是 fcntl.h?用線程是 cpthread 還是 pthread.h?我總是記不住 memset() 的參數(shù)順序,因此一般用 bzero() 代替,但是 manpage 說 bzero() 聲明于 strings.h,那我要不要考慮試試 cstrings 呢?何必給自己找麻煩,C++ 標準庫之外的內(nèi)容干脆統(tǒng)一用 .h 頭文件好了。

            性能優(yōu)化?

            有些人常常把“性能”掛在嘴邊,而且其以“提高性能”為理由的“優(yōu)化措施”往往不到點子上,只增加了復(fù)雜性和維護難度,降低了代碼質(zhì)量。這屬于決策點找偏了。我發(fā)現(xiàn)初學(xué)者往往過分關(guān)注微觀(語句級)性能,比方說關(guān)心 while(true) 和 for(;;) 哪個更快,++i 與 i=i+1 哪個更快,i/=16 和 i >>= 4 哪個快等等,而忽視了現(xiàn)代編譯器的優(yōu)化能力。

            有的人談性能優(yōu)化,一是拿不出具體的合理的性能目標,只想越快越好,二是不能實際準確測量驗證性能數(shù)據(jù),憑感覺和過時經(jīng)驗行事。在編碼的時候,遇到兩種做法都可行,決策辦法是憑感覺猜選“性能會更好”的一種,而忽視了其他更重要的因素。可讀性和性能的典型關(guān)系如下圖,有多少場合是值得為了性能而犧牲代碼的可讀性和可維護性呢?我希望自己的代碼位于第 3 區(qū),而一些人以為自己的代碼是在第 4 區(qū),其實是在第 1 區(qū)。

            read-perf

            能在第 4 區(qū)寫代碼的人屬于鳳毛麟角,有時候你費勁優(yōu)化了半天,結(jié)果新CPU加了幾條指令,直接在硬件層面把問題解決了。現(xiàn)在一些人動不動就要挽起袖子自己寫內(nèi)存池,號稱能提高性能,真當 Ulrich Drepper 是水貨?(書第 12.2.8 節(jié)“有必要自行定制內(nèi)存分配器嗎”)你打算如何測試內(nèi)存分配器(malloc)的性能?有哪些指標?有哪些影響因素需要控制或模擬(比如線程數(shù))?你的測試結(jié)果是否反映實際場景?

            雜項

            有人問為什么我說“poco不是服務(wù)端C++網(wǎng)絡(luò)庫”( http://www.oschina.net/question/12_120943 ),雖然它也提供了reactor?因為它的reactor用的是 Socket::select(),雖然后者包裝了epoll,但看其實現(xiàn)就知道,它每次調(diào)用都會創(chuàng)建并銷毀 epoll fd,然后重建整個watch list,沒有哪個服務(wù)端網(wǎng)絡(luò)庫會這么做。

            嗯,世界上有兩種網(wǎng)絡(luò)編程:網(wǎng)絡(luò)編程和Windows網(wǎng)絡(luò)編程。

            posted @ 2013-08-12 13:54 陳碩 閱讀(3736) | 評論 (6)編輯 收藏

            2013年7月17日

            《Linux多線程服務(wù)端編程:使用muduo C++網(wǎng)絡(luò)庫》上市半年重印兩次,總印數(shù)達到了9000冊

            《Linux多線程服務(wù)端編程:使用muduo C++網(wǎng)絡(luò)庫》這本書自今年一月上市以來,半年之內(nèi)已經(jīng)重印兩次(加上首印,一共是三次印刷),總印數(shù)達到了9000冊,這在技術(shù)書里已經(jīng)算是相當不錯的成績。本書購買方式見配套網(wǎng)站 http://chenshuo.com/book

            以下談一談這本書的寫作背景與內(nèi)容取舍的原因。

            參加工作以來,我編寫并維護了若干C++/Java多線程網(wǎng)絡(luò)服務(wù)程序,這本書總結(jié)了我在開發(fā)維護這類服務(wù)程序方面的經(jīng)驗。工作中,我沒有寫過單線程的網(wǎng)絡(luò)服務(wù)程序,沒有寫過C語言的網(wǎng)絡(luò)服務(wù)程序,也沒有寫過運行在Windows下的網(wǎng)絡(luò)服務(wù)程序,因此本書不涉及這些內(nèi)容。

            在“Linux服務(wù)端開發(fā)”這一背景下,這本書主要講三個方面的內(nèi)容[1]:現(xiàn)代C++、多線程、網(wǎng)絡(luò)編程,分別對應(yīng)書的第3、1、2部分。這不是一本入門書,本書的讀者應(yīng)該在以上三方面已經(jīng)具備相當?shù)幕A(chǔ)[2]:網(wǎng)絡(luò)編程方面,能輕松讀懂6.1節(jié)的兩個Python程序;C++方面,對12.8節(jié)的代碼不感到陌生;多線程方面,能明白第1章要解決什么問題。

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

            ref

            這本書的書名原本打算叫“Linux C++ 多線程系統(tǒng)編程”。寫完之后發(fā)現(xiàn),與其他Unix/Linux系統(tǒng)編程方面的書不同,這本書有明確的應(yīng)用場景,因此可以砍掉很多內(nèi)容,突出重點。甚至可以說我主要講別的書沒有講到的內(nèi)容。這不是一本面面俱到的書,因此最終的書名也就不叫“系統(tǒng)編程”了。

            同時,我認為很多教科書上介紹的一些做法是過時的(signal),一些是不推薦使用的(從外部終止線程、TCP OOB數(shù)據(jù)),一些是大多數(shù)情況下沒必要使用的(內(nèi)存池、lock-free 編程)。作為全面的教材和手冊,把這些內(nèi)容放進去可以理解。但是這本書定位是經(jīng)驗總結(jié),我略去了教科書上那些基本用不到的知識點,以免喧賓奪主,也建議讀者不要把精力花在那些次要問題上。

            • 這本書沒有花很大的篇幅去講signal,而是在第4.10節(jié)說明多線程程序不要使用signal作為IPC。并且,在muduo-protorpc的示例中給出了Linux專有的signalfd(2)的用法,可以避免傳統(tǒng)signal handler的常見陷阱,也更符合UNIX的“everything is a file”哲學(xué)。第4.4節(jié)說明不要從外部終止線程,因此也就不必去細究Pthreads cancellation point了。多線程程序最好不要fork()(第4.9節(jié))。
            • 這本書沒介紹daemon進程,我認為daemon是過時的做法。因為daemon進程的父進程是init(1),配置文件在本機,不便于多機統(tǒng)一監(jiān)控與管理(第9.8節(jié))。(注:如果是第三方標準的服務(wù)程序,又不需要經(jīng)常升級或改配置重啟,并且一旦崩潰,重啟就能繼續(xù)服務(wù),那么做成 daemon 讓init(1)接管是可以的,比如ntpd、sshd等。這里談的是自己開發(fā)維護的服務(wù)程序。)另外,Java/Python/Go寫的服務(wù)程序似乎也沒有做成daemon的習(xí)慣,C++程序沒有理由要特殊對待。補充一點,Linux的進程管理機制很落后(從UNIX繼承而來),子進程退出的事件只能被父進程以SIGCHLD信號的方式收到(而且這個signal可能丟失),kill(pid) 也存在很多race condition(你怎么保證pid在kill之前的一瞬間還代表你想kill的那個進程,而不是一個新啟動的進程?close(fd)就不會有這種 race condition。)。這些困難在用戶態(tài)無法克服,只能修改內(nèi)核,引入新的系統(tǒng)調(diào)用才能治本。例如 FreeBSD 9.0 引入了 pdfork()/pdkill() 等,將子進程變成文件描述符,這樣就能用IO事件框架統(tǒng)一處理了,也符合UNIX的“everything is a file”哲學(xué)。但愿Linux內(nèi)核也能盡快引入類似的系統(tǒng)調(diào)用,減輕程序員的負擔。
            • 這本書沒有講內(nèi)存池,而是說明不是每個程序都要自己寫內(nèi)存池(§12.2.8)。這本書也沒有把“避免內(nèi)存碎片”掛在嘴邊,而是論證為什么一般的程序不必在意它(§A.1.8);
            • 這本書只關(guān)注Linux,不考慮移植性。它推薦使用Linux專有的gettid()系統(tǒng)調(diào)用作為線程標識(第4.3節(jié)),而不是用pthread_self()。
            • 這本書不講POSIX中五花八門的定時函數(shù),而專講用Linux特有的timerfd來實現(xiàn)高精度定時(§7.8.2),因為它能方便地融入IO事件處理框架。muduo直接使用C++標準庫來管理定時器,而不是自己實現(xiàn)小頂堆(heap),這樣可以簡化實現(xiàn)(§8.2.1)。
            • 這本書只講mutex和condition variable作為最基礎(chǔ)的線程同步手段(第2章),并且我認為一個C++多線程程序代碼里不應(yīng)該直接出現(xiàn)pthread_mutex_lock之類的基本Pthreads調(diào)用。本書進一步建議只使用非遞歸的mutex(§2.1.1),這與某些網(wǎng)上文章的推薦正好相反。這本書第2.3節(jié)甚至建議不要使用讀寫鎖和信號量(semaphore),因為一是容易用錯,二是不見得能提高性能。mutex和condition variable是完備的,能實現(xiàn)多種更易用的同步設(shè)施,例如CountDownLatch和BlockingQueue。§12.8.3的代碼展示了用BlockingQueue和ThreadPool控制并發(fā)度的手法,做到了“No locks. No condition variables. No callbacks.”
            • 這本書不講lock-free編程,因為編寫可靠的lock-free代碼并分析驗證其正確性的難度遠大于編寫普通的使用mutex和condition variable的多線程代碼,后者已經(jīng)有了相當成熟的理論和工具。我認為lock-free不是每個多線程程序員應(yīng)該掌握的技術(shù),它投入高而用處少,可以適當了解,但不值得每個人都去深究。只需要少數(shù)人用它實現(xiàn)封裝好的數(shù)據(jù)結(jié)構(gòu),像我這樣的普通人就可以受益。
            • 這本書只講BSD Sockets作為進程間通信的手段,并且只用TCP長連接(§3.4)。這樣就砍掉了pipe、FIFO、POSIX message queue、shared memory、STREAMS、UNIX domain socket等等內(nèi)容,因為它們都只限本機進程間通信,無法擴展到多機。
            • 網(wǎng)絡(luò)編程方面(第6、7章),這本書不講Sockets API的基本用法,而且代碼中也不會直接使用它們。我認為在程序中直接使用 Sockets API是初學(xué)者的做法,當寫一個新網(wǎng)絡(luò)服務(wù)程序,如果一開始考慮的是怎么組織accept、read、epoll_wait等調(diào)用,這種做法無異于用鉛筆刀鋸大樹,事倍功半,也不利于將來的功能擴展和維護。稍微像樣點的公司都會用成熟的網(wǎng)絡(luò)庫(不一定開源),把網(wǎng)絡(luò)編程的復(fù)雜性封裝進去,暴露出良好易用的接口,讓開發(fā)人員使用更高層的building blocks(消息傳遞或RPC)從功能的角度去設(shè)計程序,避免一次次反復(fù)掉到TCP網(wǎng)絡(luò)編程的坑里。多個服務(wù)程序共享相同的基礎(chǔ)庫和事件處理框架的益處是顯而易見的,一方面把網(wǎng)絡(luò)編程的復(fù)雜性集中到一起,避免每個團隊都去踏一遍坑;另一方面,基礎(chǔ)庫的bug修復(fù)與性能優(yōu)化能惠及用到它的全部服務(wù)程序;最后,程序結(jié)構(gòu)上的相似性讓編程經(jīng)驗更加通用,多個服務(wù)程序在功能、性能、正確性等方面具有共性,能舉一反三觸類旁通,降低將來開發(fā)維護的成本。應(yīng)該避免每個程序都另起爐灶,單獨設(shè)計其IO事件處理結(jié)構(gòu)。
            • 這本書只講非阻塞IO結(jié)合IO復(fù)用(IO-Multiplexing)這一種并發(fā)風(fēng)格(歸納為三個半事件),并介紹在多線程下的擴展(one loop per thread)。IO復(fù)用方面,本書只講level-trigger,不講edge-trigger。一方面目前沒有up to date的測試表明ET更快,相反,我認為LT在讀取數(shù)據(jù)時可以節(jié)約一次read()調(diào)用(§8.7.2);另一方面,LT模式更容易與其他第三方庫結(jié)合(§7.15)。多線程程序管理并發(fā)socket fd有很多風(fēng)格可供選擇,例如epoll fd是多個線程共享一個(多對一)還是每個線程有自己的epoll fd(一對一),每個socket fd是只屬于一個epoll fd(多對一)還是可以同時屬于多個 epoll fd(多對多),每個socket fd是只能被固定的一個線程讀寫還是可以被多個線程讀寫(如果是后者,那么讀寫的時候是加鎖還是使用ONESHOT)。以上不是每種都可行,本書也沒有一一加以分析,而是建議使用one loop per thread這種適用性較強的風(fēng)格,首先是正確性容易驗證,其次是性能也能滿足要求。
            • 本書不講IPv6,因為目前世界上最大的公司的服務(wù)機群也用不完一個私有A類地址(10.0.0.0/8)。本書不講UDP,因為《Unix網(wǎng)絡(luò)編程》已經(jīng)講得很好了。
            • 這本書舉的網(wǎng)絡(luò)編程的例子不再是簡單的echo服務(wù),而是有格式(因此引入codec)、多連接之間會交換數(shù)據(jù)的網(wǎng)絡(luò)程序,更接近業(yè)務(wù)場景,也借機講解如何避免TCP網(wǎng)絡(luò)編程的常見陷阱。并且在示例代碼中給出了分布式單詞計數(shù)、多機求中位數(shù)等稍微復(fù)雜一點的程序。
            • 在C++方面,這本書沒有介紹動態(tài)鏈接庫熱更新這種“高級”技術(shù),而是說明,在分布式系統(tǒng)中,為了部署方便,應(yīng)該從源碼編譯全部的庫,與主程序鏈接為一個standalone的可執(zhí)行文件,以減小對運行環(huán)境的依賴(第10章)。第11章還討論了程序庫與應(yīng)用程序之間的接口設(shè)計。

            “信息”按照香農(nóng)的定義,是“減少不確定性”,這本書包含的信息正是減少選用編程設(shè)施(facilities)方面的不確定性,讓讀者集中精力攻克本質(zhì)問題。這本書介紹的方法不一定對于每個應(yīng)用場景都是最好的,但肯定是簡便易行的,是時間成本、功能、性能的一種合理折中。


            [1] 這本書前言的第一句話“本書主要講述采用現(xiàn)代 C++ 在 x86-64 Linux 上編寫多線程 TCP 網(wǎng)絡(luò)服務(wù)程序的主流常規(guī)技術(shù)”,封面印著“示范在多核時代采用現(xiàn)代 C++ 編寫多線程 TCP 網(wǎng)絡(luò)服務(wù)器的正規(guī)做法”。

            [2] 前言寫到:讀者應(yīng)該已經(jīng)大致讀過《現(xiàn)代操作系統(tǒng)》、《UNIX 環(huán)境高級編程》、《UNIX 網(wǎng)絡(luò)編程》、《C++ Primer》或與之內(nèi)容相近的書籍,熟悉基本概念,并掌握 Pthreads 和 Sockets API 的常規(guī)用法。

            posted @ 2013-07-17 11:17 陳碩 閱讀(2576) | 評論 (3)編輯 收藏

            2013年1月28日

            為什么多線程讀寫 shared_ptr 要加鎖?

            陳碩(giantchen_AT_gmail_DOT_com)

            2012-01-28

            我在《Linux 多線程服務(wù)端編程:使用 muduo C++ 網(wǎng)絡(luò)庫》第 1.9 節(jié)“再論 shared_ptr 的線程安全”中寫道:

            (shared_ptr)的引用計數(shù)本身是安全且無鎖的,但對象的讀寫則不是,因為 shared_ptr 有兩個數(shù)據(jù)成員,讀寫操作不能原子化。根據(jù)文檔(http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm#ThreadSafety), shared_ptr 的線程安全級別和內(nèi)建類型、標準庫容器、std::string 一樣,即:

            • 一個 shared_ptr 對象實體可被多個線程同時讀取(文檔例1);

            • 兩個 shared_ptr 對象實體可以被兩個線程同時寫入(例2),“析構(gòu)”算寫操作;

            如果要從多個線程讀寫同一個 shared_ptr 對象,那么需要加鎖(例3~5)。

            請注意,以上是 shared_ptr 對象本身的線程安全級別,不是它管理的對象的線程安全級別。

            后文(p.18)則介紹如何高效地加鎖解鎖。本文則具體分析一下為什么“因為 shared_ptr 有兩個數(shù)據(jù)成員,讀寫操作不能原子化”使得多線程讀寫同一個 shared_ptr 對象需要加鎖。這個在我看來顯而易見的結(jié)論似乎也有人抱有疑問,那將導(dǎo)致災(zāi)難性的后果。本文以 boost::shared_ptr 為例,與 std::shared_ptr 可能略有區(qū)別。

            shared_ptr 的數(shù)據(jù)結(jié)構(gòu)

            shared_ptr 是引用計數(shù)型(reference counting)智能指針,幾乎所有的實現(xiàn)都采用在堆(heap)上放個計數(shù)值(count)的辦法(除此之外理論上還有用循環(huán)鏈表的辦法,不過沒有實例)。具體來說,shared_ptr<Foo> 包含兩個成員,一個是指向 Foo 的指針 ptr,另一個是 ref_count 指針(其類型不一定是原始指針,有可能是 class 類型,但不影響這里的討論),指向堆上的 ref_count 對象。ref_count 對象有多個成員,具體的數(shù)據(jù)結(jié)構(gòu)如圖 1 所示,其中 deleter 和 allocator 是可選的。

            sp0

            圖 1:shared_ptr 的數(shù)據(jù)結(jié)構(gòu)。

            為了簡化并突出重點,后文只畫出 use_count:

            sp1

            以上是 shared_ptr<Foo> x(new Foo); 對應(yīng)的內(nèi)存數(shù)據(jù)結(jié)構(gòu)。

            如果再執(zhí)行 shared_ptr<Foo> y = x; 那么對應(yīng)的數(shù)據(jù)結(jié)構(gòu)如下。

            sp2

            但是 y=x 涉及兩個成員的復(fù)制,這兩步拷貝不會同時(原子)發(fā)生。

            中間步驟 1,復(fù)制 ptr 指針:

            sp3

            中間步驟 2,復(fù)制 ref_count 指針,導(dǎo)致引用計數(shù)加 1:

            sp4

            步驟1和步驟2的先后順序跟實現(xiàn)相關(guān)(因此步驟 2 里沒有畫出 y.ptr 的指向),我見過的都是先1后2。

            既然 y=x 有兩個步驟,如果沒有 mutex 保護,那么在多線程里就有 race condition。

            多線程無保護讀寫 shared_ptr 可能出現(xiàn)的 race condition

            考慮一個簡單的場景,有 3 個 shared_ptr<Foo> 對象 x、g、n:

            • shared_ptr<Foo> g(new Foo); // 線程之間共享的 shared_ptr
            • shared_ptr<Foo> x; // 線程 A 的局部變量
            • shared_ptr<Foo> n(new Foo); // 線程 B 的局部變量

            一開始,各安其事。

            sp5

            線程 A 執(zhí)行 x = g; (即 read g),以下完成了步驟 1,還沒來及執(zhí)行步驟 2。這時切換到了 B 線程。

            sp6

            同時編程 B 執(zhí)行 g = n; (即 write G),兩個步驟一起完成了。

            先是步驟 1:

            sp7

            再是步驟 2:

            sp8

            這是 Foo1 對象已經(jīng)銷毀,x.ptr 成了空懸指針!

            最后回到線程 A,完成步驟 2:

            sp9

            多線程無保護地讀寫 g,造成了“x 是空懸指針”的后果。這正是多線程讀寫同一個 shared_ptr 必須加鎖的原因。

            當然,race condition 遠不止這一種,其他線程交織(interweaving)有可能會造成其他錯誤。

            思考,假如 shared_ptr 的 operator= 實現(xiàn)是先復(fù)制 ref_count(步驟 2)再復(fù)制 ptr(步驟 1),會有哪些 race condition?

            雜項

            shared_ptr 作為 unordered_map 的 key

            如果把 boost::shared_ptr 放到 unordered_set 中,或者用于 unordered_map 的 key,那么要小心 hash table 退化為鏈表。http://stackoverflow.com/questions/6404765/c-shared-ptr-as-unordered-sets-key/12122314#12122314

            直到 Boost 1.47.0 發(fā)布之前,unordered_set<std::shared_ptr<T> > 雖然可以編譯通過,但是其 hash_value 是 shared_ptr 隱式轉(zhuǎn)換為 bool 的結(jié)果。也就是說,如果不自定義hash函數(shù),那么 unordered_{set/map} 會退化為鏈表。https://svn.boost.org/trac/boost/ticket/5216

            Boost 1.51 在 boost/functional/hash/extensions.hpp 中增加了有關(guān)重載,現(xiàn)在只要包含這個頭文件就能安全高效地使用 unordered_set<std::shared_ptr> 了。

            這也是 muduo 的 examples/idleconnection 示例要自己定義 hash_value(const boost::shared_ptr<T>& x) 函數(shù)的原因(書第 7.10.2 節(jié),p.255)。因為 Debian 6 Squeeze、Ubuntu 10.04 LTS 里的 boost 版本都有這個 bug。

            為什么圖 1 中的 ref_count 也有指向 Foo 的指針?

            shared_ptr<Foo> sp(new Foo) 在構(gòu)造 sp 的時候捕獲了 Foo 的析構(gòu)行為。實際上 shared_ptr.ptr 和 ref_count.ptr 可以是不同的類型(只要它們之間存在隱式轉(zhuǎn)換),這是 shared_ptr 的一大功能。分 3 點來說:

            1. 無需虛析構(gòu);假設(shè) Bar 是 Foo 的基類,但是 Bar 和 Foo 都沒有虛析構(gòu)。

            shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的類型是 Foo*

            shared_ptr<Bar> sp2 = sp1; // 可以賦值,自動向上轉(zhuǎn)型(up-cast)

            sp1.reset(); // 這時 Foo 對象的引用計數(shù)降為 1

            此后 sp2 仍然能安全地管理 Foo 對象的生命期,并安全完整地釋放 Foo,因為其 ref_count 記住了 Foo 的實際類型。

            2. shared_ptr<void> 可以指向并安全地管理(析構(gòu)或防止析構(gòu))任何對象;muduo::net::Channel class 的 tie() 函數(shù)就使用了這一特性,防止對象過早析構(gòu),見書 7.15.3 節(jié)。

            shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的類型是 Foo*

            shared_ptr<void> sp2 = sp1; // 可以賦值,F(xiàn)oo* 向 void* 自動轉(zhuǎn)型

            sp1.reset(); // 這時 Foo 對象的引用計數(shù)降為 1

            此后 sp2 仍然能安全地管理 Foo 對象的生命期,并安全完整地釋放 Foo,不會出現(xiàn) delete void* 的情況,因為 delete 的是 ref_count.ptr,不是 sp2.ptr。

            3. 多繼承。假設(shè) Bar 是 Foo 的多個基類之一,那么:

            shared_ptr<Foo> sp1(new Foo);

            shared_ptr<Bar> sp2 = sp1; // 這時 sp1.ptr 和 sp2.ptr 可能指向不同的地址,因為 Bar subobject 在 Foo object 中的 offset 可能不為0。

            sp1.reset(); // 此時 Foo 對象的引用計數(shù)降為 1

            但是 sp2 仍然能安全地管理 Foo 對象的生命期,并安全完整地釋放 Foo,因為 delete 的不是 Bar*,而是原來的 Foo*。換句話說,sp2.ptr 和 ref_count.ptr 可能具有不同的值(當然它們的類型也不同)。

            為什么要盡量使用 make_shared()?

            為了節(jié)省一次內(nèi)存分配,原來 shared_ptr<Foo> x(new Foo); 需要為 Foo 和 ref_count 各分配一次內(nèi)存,現(xiàn)在用 make_shared() 的話,可以一次分配一塊足夠大的內(nèi)存,供 Foo 和 ref_count 對象容身。數(shù)據(jù)結(jié)構(gòu)是:

            sp10

            不過 Foo 的構(gòu)造函數(shù)參數(shù)要傳給 make_shared(),后者再傳給 Foo::Foo(),這只有在 C++11 里通過 perfect forwarding 才能完美解決。

            (.完.)

            posted @ 2013-01-28 05:15 陳碩 閱讀(16614) | 評論 (7)編輯 收藏

            僅列出標題  下一頁
            <2016年5月>
            24252627282930
            1234567
            891011121314
            15161718192021
            22232425262728
            2930311234

            導(dǎo)航

            統(tǒng)計

            常用鏈接

            隨筆分類

            隨筆檔案

            相冊

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            久久精品国产亚洲αv忘忧草 | 久久久久综合国产欧美一区二区| 国产精品成人久久久久久久| 伊人久久大香线蕉精品不卡| 精品人妻久久久久久888| 久久99国产精品二区不卡| 伊人久久大香线蕉AV一区二区| 99999久久久久久亚洲| 无码人妻久久一区二区三区蜜桃| 久久精品毛片免费观看| 看全色黄大色大片免费久久久| 久久婷婷五月综合色奶水99啪 | 无码任你躁久久久久久老妇| 久久棈精品久久久久久噜噜| 久久久久亚洲AV成人网| 久久精品无码专区免费青青| 欧美一区二区久久精品| 伊人色综合久久| 欧美牲交A欧牲交aⅴ久久| 性做久久久久久久久老女人| 一本久久久久久久| 国产精品久久午夜夜伦鲁鲁| 成人综合久久精品色婷婷| 国产日韩欧美久久| 狠狠色丁香久久综合婷婷| 日韩精品久久久久久久电影蜜臀| 一本久久免费视频| 久久涩综合| 久久久久久久久久免免费精品 | 亚洲欧洲日产国码无码久久99| 久久国产精品免费一区二区三区| 99re久久精品国产首页2020| 亚洲AV无码久久精品蜜桃| 久久午夜夜伦鲁鲁片免费无码影视 | 久久久久人妻精品一区三寸蜜桃| 久久这里只精品国产99热| 久久精品国产乱子伦| 999久久久免费国产精品播放| 国产一级做a爰片久久毛片| 久久久久99精品成人片试看| 午夜精品久久久久久久久|