一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于shared_ptr的C++非侵入式引用計(jì)數(shù)解決方案的缺陷

Linux愛好者 ? 來源:Linux愛好者 ? 作者:Linux愛好者 ? 2022-06-13 14:24 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

問題描述

在基于C++的大型系統(tǒng)的設(shè)計(jì)實(shí)現(xiàn)中,由于缺乏語言級別的GC支持,資源生存周期往往是一個(gè)棘手的問題。系統(tǒng)地解決這個(gè)問題的方法無非兩種:

  • 使用GC庫
  • 使用引用計(jì)數(shù)

嚴(yán)格地說,引用計(jì)數(shù)其實(shí)也是一種最樸素的GC。相對于現(xiàn)代的GC技術(shù),引用計(jì)數(shù)的實(shí)現(xiàn)簡單,但相應(yīng)地,它也存在著循環(huán)引用和線程同步開銷等問題。

關(guān)于這二者孰優(yōu)孰劣,已經(jīng)有過很多討論,在此就不攪這股混水了。我一直也沒有使用過C++的GC庫,在實(shí)際項(xiàng)目中總是采用引用計(jì)數(shù)的方案。而作為Boost的擁躉,首選的自然是shared_ptr。一直以來我也對shared_ptr百般推崇,然而最近的一些項(xiàng)目開發(fā)經(jīng)驗(yàn)卻讓我在shared_ptr上栽了坑,對C++引用計(jì)數(shù)也有了一些新的的認(rèn)識(shí),遂記錄在此。

本文主要針對基于boost::shared_ptr的C++引用計(jì)數(shù)實(shí)現(xiàn)方案進(jìn)行一些討論。C++引用計(jì)數(shù)方案往往伴隨著用于自動(dòng)管理引用計(jì)數(shù)的智能指針。按是否要求資源對象自己維護(hù)引用計(jì)數(shù),C++引用計(jì)數(shù)方案可以分為兩類:

  • 侵入式:侵入式的引用計(jì)數(shù)管理要求資源對象本身維護(hù)引用計(jì)數(shù),同時(shí)提供增減引用計(jì)數(shù)的管理接口。通常侵入式方案會(huì)提供配套的侵入式引用計(jì)數(shù)智能指針。該智能指針通過調(diào)用資源對象的引用計(jì)數(shù)管理接口來自動(dòng)增減引用計(jì)數(shù)。COM對象與CComPtr便是侵入式引用計(jì)數(shù)的一個(gè)典型實(shí)例。
  • 非侵入式:非侵入式的引用計(jì)數(shù)管理對資源對象本身沒有任何要求,而是完全借助非侵入式引用計(jì)數(shù)智能指針在資源對象外部維護(hù)獨(dú)立的引用計(jì)數(shù)。shared_ptr便是基于這個(gè)思路。

第一宗罪

初看起來,非侵入式方案由于對資源對象的實(shí)現(xiàn)沒有任何要求,相較于侵入式方案更具吸引力。然而事實(shí)卻并非如此。下面就來分析一下基于shared_ptr的非侵入式引用計(jì)數(shù)。在使用shared_ptr的引用計(jì)數(shù)解決方案中,引用計(jì)數(shù)完全由shared_ptr控制,資源對象對與自己對應(yīng)的引用計(jì)數(shù)一無所知。

而引用計(jì)數(shù)與資源對象的生存期息息相關(guān),這就意味著資源對象喪失了對生存期的控制權(quán),將自己的生殺大權(quán)拱手讓給了shared_ptr。這種情況下,資源對象就不得不依靠至少一個(gè)shared_ptr實(shí)例來保障自己的生存。換言之,資源對象一旦“沾染”了shared_ptr,就一輩子都無法擺脫!考察以下的簡單用例:

用例一:

Resource*p=newCResource;
{
shared_ptrq(p);
}
p->Use()//CRASH

單純?yōu)榱私鉀Q上述的崩潰,可以自定義一個(gè)什么也不做的deleter:

structnoop_deleter{
voidoperator()(void*){
//NO-OP
}
};

然后將上述用例的第三行改為:

shared_ptrq(p,noop_deleter());

但是這樣一來,shared_ptr就喪失了借助RAII自動(dòng)釋放資源的能力,違背了我們利用智能指針自動(dòng)管理資源生存期的初衷(話說回來,這倒并不是說noop_deleter這種手法毫無用處,Boost.Asio中就巧妙地利用shared_ptr、weak_ptr和noop_deleter來實(shí)現(xiàn)異步I/O事件的取消)。

從這個(gè)簡單的用例可以看出,shared_ptr就像是毒品一樣,一旦沾染就難以戒除。更甚者,染毒者連換用其他“毒品”的權(quán)力都沒有:shared_ptr的引用計(jì)數(shù)管理接口是私有的,無法從shared_ptr之外操控,也就無法從shared_ptr遷移到其他類型的引用計(jì)數(shù)智能指針。

不僅如此,資源對象沾染上shared_ptr之后,就只能使用最初的那個(gè)shared_ptr實(shí)例的拷貝來維系自己的生存期。考察以下用例:用例二:

{
shared_ptrp1(newCResource);
shared_ptrp2(p1);//OK
CResource*p3=p1.get();
shared_ptrp4(p3);//ERROR
//CRASH
}

該用例的執(zhí)行過程如下:

  1. p1在構(gòu)造的同時(shí)為資源對象創(chuàng)建了一份外部引用計(jì)數(shù),并將之置為1
  2. p2拷貝自p1,與p1共享同一個(gè)引用計(jì)數(shù),將之增加為2
  3. p4并非p1的拷貝,因此在構(gòu)造的同時(shí)又為資源對象創(chuàng)建了另外一個(gè)外部引用計(jì)數(shù),并將之置為1
  4. 在作用域結(jié)束時(shí),p4析構(gòu),由其維護(hù)的額外的引用計(jì)數(shù)降為0,導(dǎo)致資源對象被析構(gòu)
  5. 然后p2析構(gòu),對應(yīng)的引用計(jì)數(shù)降為1
  6. 接著p1析構(gòu),對應(yīng)的引用計(jì)數(shù)也歸零,于是p1在臨死之前再次釋放資源對象 最后,由于資源對象被二次釋放,程序崩潰

至此,我們已經(jīng)認(rèn)識(shí)到了shared_ptr的第一宗罪——傳播毒品

  • 毒性一:一旦開始對資源對象使用shared_ptr,就必須一直使用
  • 毒性二:無法換用其他類型的引用計(jì)數(shù)之智能指針來管理資源對象生存期
  • 毒性三:必須使用最初的shared_ptr實(shí)例拷貝來維系資源對象生存期

第二宗罪

乘勝追擊,再揭露一下shared_ptr的第二宗罪——散布病毒。有點(diǎn)聳人聽聞了?其實(shí)道理很簡單:由于使用了shared_ptr的資源對象必須仰仗shared_ptr的存在才能維系生存期,這就意味著使用資源的客戶對象也必須使用shared_ptr來持有資源對象的引用——于是shared_ptr的勢力范圍成功地從資源對象本身擴(kuò)散到了資源使用者,侵入了資源客戶對象的實(shí)現(xiàn)。

同時(shí),資源的使用者往往是通過某種形式的資源分配器來獲取資源。自然地,為了向客戶轉(zhuǎn)交資源對象的所有權(quán),資源分配器也不得不在接口中傳遞shared_ptr,于是shared_ptr也會(huì)侵入資源分配器的接口。

有一種情況可以暫時(shí)擺脫shared_ptr,例如:

shared_ptrAllocateResource(){
shared_ptrpResource(newCResource);
InitResource(pResource.get());
returnpResource;
}
voidInitResource(IResource*r){
//Doresourceinitialization...
}

以上用例中,在InitResource的執(zhí)行期間,由于AllocateResource的堆棧仍然存在,pResource不會(huì)析構(gòu),因此可以放心的在InitResource的參數(shù)中使用裸指針傳遞資源對象。這種基于調(diào)用棧的引用計(jì)數(shù)優(yōu)化,也是一種常用的手段。但在InitResource返回后,資源對象終究還是會(huì)落入shared_ptr的魔掌。

由此可以看出,shared_ptr打著“非侵入式”的幌子,雖然沒有侵入資源對象的實(shí)現(xiàn),卻侵入了資源分配接口以及資源客戶對象的實(shí)現(xiàn)。而沾染上shared_ptr就擺脫不掉,如此傳播下去,簡直就是侵入了除資源對象實(shí)現(xiàn)以外的其他各個(gè)地方!這不是病毒是什么?

然而,基于shared_ptr的引用計(jì)數(shù)解決方案真的不會(huì)侵入資源對象的實(shí)現(xiàn)嗎?

第三宗罪

在一些用例中,資源對象的成員方法(不包括構(gòu)造函數(shù))需要獲取指向?qū)ο笞陨?,即包含了this指針的shared_ptr。Boost.Asio的chat示例便展示了這樣一個(gè)用例:chat_session對象會(huì)在其成員函數(shù)中發(fā)起異步I/O操作,并在異步I/O操作回調(diào)中保存一個(gè)指向自己的shared_ptr以保證回調(diào)執(zhí)行時(shí)自身的生存期尚未結(jié)束。

這種手法在Boost.Asio中非常常見,在不考慮shared_ptr帶來的麻煩時(shí),這實(shí)際上也是一種相當(dāng)優(yōu)雅的異步流程資源生存期處理方法。但現(xiàn)在讓我們把注意力集中在shared_ptr上。

通常,使用shared_ptr的資源對象必須動(dòng)態(tài)分配,最常見的就是直接從堆上new出一個(gè)實(shí)例并交付給一個(gè)shared_ptr,或者也可以從某個(gè)資源池中分配再借助自定義的deleter在引用計(jì)數(shù)歸零時(shí)將資源放回池中。無論是那種用法,該資源對象的實(shí)例在創(chuàng)建出來后,都總是立即交付給一個(gè)shared_ptr(記為p)。

有鑒于之前提到的毒性三,如果資源對象的成員方法需要獲取一個(gè)指向自己的shared_ptr,那么這個(gè)shared_ptr也必須是p的一個(gè)拷貝——或者更本質(zhì)的說,必須與p共享同一個(gè)外部引用計(jì)數(shù)。然而對于資源對象而言,p維護(hù)的引用計(jì)數(shù)是外部的陌生事物,資源對象如何得到這個(gè)引用計(jì)數(shù)并由此構(gòu)造出一個(gè)合法的shared_ptr呢?這是一個(gè)比較tricky的過程。為了解決這個(gè)問題,Boost提供了一個(gè)類模板e(cuò)nable_shared_from_this:

所有需要在成員方法中獲取指向this的shared_ptr的類型,都必須以CRTP手法繼承自enable_shared_from_this。即:

classCResource:
publicboost::enable_shared_from_this
{
//...
};

接著,資源對象的成員方法就可以使用enable_shared_from_this::shared_from_this()方法來獲取所需的指向?qū)ο笞陨淼膕hared_ptr了。問題似乎解決了。但是,等等!這樣的繼承體系不就對資源對象的實(shí)現(xiàn)有要求了嗎?換言之,這不正是對資源對象實(shí)現(xiàn)的赤裸裸的侵入嗎?這正是shared_ptr的第三宗罪——欺世盜名

第四宗罪

最后一宗罪,是鋪張浪費(fèi)。對了,說的就是性能。

基于引用計(jì)數(shù)的資源生存期管理,打一出生起就被扣著線程同步開銷大的帽子。早期的Boost版本中,shared_ptr是借助Boost.Thread的mutex對象來保護(hù)引用計(jì)數(shù)。在后期的版本中采用了lock-free的原子整數(shù)操作一定程度上降低了線程同步開銷。然而即使是lock-free,本質(zhì)上也仍然是串行化訪問,線程同步的開銷多少都會(huì)存在。

也許有人會(huì)說這點(diǎn)開銷與引用計(jì)數(shù)帶來的便利相比算不得什么。然而在我們項(xiàng)目的異步服務(wù)器框架的壓力測試中,大量引用計(jì)數(shù)的增減操作,一舉吃掉了5%的CPU。換言之,1/20的計(jì)算能力被浪費(fèi)在了與業(yè)務(wù)邏輯完全無關(guān)的引用計(jì)數(shù)的維護(hù)上!而且,由于是異步流程的特殊性,也無法應(yīng)用上面提及的基于調(diào)用棧的引用計(jì)數(shù)優(yōu)化。

那么針對這個(gè)問題就真的沒有辦法了嗎?其實(shí)仔細(xì)檢視一下整個(gè)異步流程,有些資源雖然會(huì)先后被不同的對象所引用,但在其整個(gè)生存周期內(nèi),每一時(shí)刻都只有一個(gè)對象持有該資源的引用。用于數(shù)據(jù)收發(fā)的緩沖區(qū)對象就是一個(gè)典型。它們總是被從某個(gè)源頭產(chǎn)生,然后便一直從一處被傳遞到另一處,最終在某個(gè)時(shí)刻被回收。

對于這樣的對象,實(shí)際上沒有必要針對流程中的每一次所有權(quán)轉(zhuǎn)移都進(jìn)行引用計(jì)數(shù)操作,只要簡單地在分配時(shí)將引用計(jì)數(shù)置1,在需要釋放時(shí)再將引用計(jì)數(shù)歸零便可以了。

對于侵入式引用計(jì)數(shù)方案,由于資源對象自身持有引用計(jì)數(shù)并提供了引用計(jì)數(shù)的操作接口,可以很容易地實(shí)現(xiàn)這樣的優(yōu)化。但shared_ptr則不然。shared_ptr把引用計(jì)數(shù)牢牢地攥在手中,不讓外界碰觸;外界只有通過shared_ptr的構(gòu)造函數(shù)、析夠函數(shù)以及reset()方法才能夠間接地對引用計(jì)數(shù)進(jìn)行操作。

而由于shared_ptr的毒品特性,資源對象無法脫離shared_ptr而存在,因此在轉(zhuǎn)移資源對象的所有權(quán)時(shí),也必須通過拷貝shared_ptr的方式進(jìn)行。一次拷貝就對應(yīng)一對引用計(jì)數(shù)的原子增減操作。

對于上述的可優(yōu)化資源對象,如果在一個(gè)流程中被傳遞3次,除去分配和釋放時(shí)的2次,還會(huì)導(dǎo)致6次無謂的原子整數(shù)操作。整整浪費(fèi)了300%!

事實(shí)證明,在將基于shared_ptr的非侵入式引用計(jì)數(shù)方案更改為侵入式引用計(jì)數(shù)方案并施行上述優(yōu)化后,我們的異步服務(wù)器框架的性能有了明顯的提升。

結(jié)語

最后總結(jié)一下shared_ptr的四宗罪:

  • 傳播毒品一旦對資源對象染上了shared_ptr,在其生存期內(nèi)便無法擺脫。
  • 散布病毒在應(yīng)用了shared_ptr的資源對象的所有權(quán)變換的整個(gè)過程中的所有接口都會(huì)受到shared_ptr的污染。
  • 欺世盜名在enable_shared_from_this用例下,基于shared_ptr的解決方案并非是非侵入式的。
  • 鋪張浪費(fèi)由于shared_ptr隱藏了引用計(jì)數(shù)的操作接口,只能通過拷貝shared_ptr的方式間接操縱引用計(jì)數(shù),使得用戶難以規(guī)避不必要的引用計(jì)數(shù)操作,造成無謂的性能損失。

探明這四宗罪算是最近一段時(shí)間的項(xiàng)目設(shè)計(jì)開發(fā)過程的一大收獲。寫這篇文章的目的不是為了將shared_ptr一棒子打死,只是為了總結(jié)基于shared_ptr的C++非侵入式引用計(jì)數(shù)解決方案的缺陷,也讓自己不再盲目迷信shared_ptr。

原文標(biāo)題:共享指針?biāo)淖谧?/p>

文章出處:【微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 指針
    +關(guān)注

    關(guān)注

    1

    文章

    484

    瀏覽量

    71210
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2119

    瀏覽量

    75361
  • 計(jì)數(shù)
    +關(guān)注

    關(guān)注

    1

    文章

    57

    瀏覽量

    20379

原文標(biāo)題:共享指針?biāo)淖谧?/p>

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點(diǎn)推薦

    記一次詭異的內(nèi)存泄漏

    最近在補(bǔ)一些基礎(chǔ)知識(shí),恰好涉及到了智能指針std::weak_ptr在解決std::shared_ptr時(shí)候循環(huán)引用的問題
    的頭像 發(fā)表于 02-19 13:44 ?936次閱讀
    記一次詭異的內(nèi)存泄漏

    在OpenVINO? C++代碼中啟用 AddressSanitizer 時(shí)的內(nèi)存泄漏怎么解決?

    long); #10 0xffffbd13f788 in ov::Core::compile_model(std::shared_ptr const&, std::__cxx11
    發(fā)表于 06-23 07:16

    C++標(biāo)準(zhǔn)庫第二版》總結(jié)濃縮精華

    。(2)scoped_array:與scoped_ptr的唯一不同是動(dòng)態(tài)數(shù)組,單個(gè)對象。除非對性能有非??量桃螅蚓幾g器不支持標(biāo)準(zhǔn)庫,否則不建議使用。(3)shared_ptr:與unique_
    發(fā)表于 10-23 20:31

    《Boost程序完全開發(fā)指南》重點(diǎn)總結(jié)分享

    )scoped_array:與scoped_ptr的唯一不同是動(dòng)態(tài)數(shù)組,單個(gè)對象。除非對性能有非??量桃?,或編譯器不支持標(biāo)準(zhǔn)庫,否則不建議使用。(3)shared_ptr:與unique_
    發(fā)表于 10-23 20:33

    C陷阱與缺陷CC++經(jīng)典著作)

    電子發(fā)燒友網(wǎng)站提供《C陷阱與缺陷CC++經(jīng)典著作).txt》資料免費(fèi)下載
    發(fā)表于 06-03 16:26 ?0次下載

    基于模板濾波的侵入負(fù)荷辨識(shí)系統(tǒng)

    針對侵入負(fù)荷監(jiān)測系統(tǒng)在設(shè)備投入、復(fù)雜性以及擴(kuò)展性上的缺陷,在詳細(xì)分析侵入負(fù)荷監(jiān)測系統(tǒng)工作原
    發(fā)表于 02-01 13:51 ?0次下載
    基于模板濾波的<b class='flag-5'>非</b><b class='flag-5'>侵入</b>負(fù)荷辨識(shí)系統(tǒng)

    侵入自動(dòng)測試系統(tǒng)的研制

    侵入系統(tǒng)的關(guān)鍵技術(shù)點(diǎn)。最后結(jié)合現(xiàn)場實(shí)際運(yùn)行,提出引入了侵入概念后的安全實(shí)施
    發(fā)表于 03-29 11:06 ?0次下載

    ABB全新的侵入溫度解決方案榮獲德國創(chuàng)新獎(jiǎng)

    這款侵入溫度傳感器的誕生標(biāo)志著溫度測量新紀(jì)元的開啟。
    的頭像 發(fā)表于 06-04 09:29 ?4191次閱讀

    C++引用的使用場景

    C++引用的使用場景
    的頭像 發(fā)表于 06-29 15:18 ?4388次閱讀
    <b class='flag-5'>C++</b>:<b class='flag-5'>引用</b>的使用場景

    C++中的const和引用的討論

    今天給大家分享一下這段時(shí)間學(xué)習(xí)c++的總結(jié)學(xué)習(xí):c++里面的const關(guān)鍵字和引用。
    的頭像 發(fā)表于 12-24 15:35 ?1102次閱讀

    C++基礎(chǔ)語法中的引用、封裝和多態(tài)

    本期是C++基礎(chǔ)語法分享的第六節(jié),今天給大家來分享一下: (1)引用; (2)宏; (3)成員初始化列表; (4)封裝; (5)繼承; (6)多態(tài); 引用 左值引用 常規(guī)
    的頭像 發(fā)表于 09-12 09:58 ?1549次閱讀

    C++中的智能指針

    C++11中,有unique_ptr、shared_ptr以及weak_ptr三種,auto_ptr因?yàn)樽陨磙D(zhuǎn)移所有權(quán)的原因,在
    的頭像 發(fā)表于 08-05 11:11 ?1182次閱讀

    C++引用和指針

    之前的文章我們已經(jīng)介紹了C++中的基本類型如int,bool和double等,除了基本類型C++還有一些更復(fù)雜的數(shù)據(jù)類型復(fù)合類型,所謂的復(fù)合類型就是通過其他類型定義的類型,本篇文章我們將會(huì)著重介紹C++的復(fù)合類型
    的頭像 發(fā)表于 03-17 14:00 ?887次閱讀

    HARDWARIO侵入監(jiān)測-旋轉(zhuǎn)監(jiān)測

    電子發(fā)燒友網(wǎng)站提供《HARDWARIO侵入監(jiān)測-旋轉(zhuǎn)監(jiān)測.zip》資料免費(fèi)下載
    發(fā)表于 06-16 10:03 ?0次下載
    HARDWARIO<b class='flag-5'>非</b><b class='flag-5'>侵入</b><b class='flag-5'>式</b>監(jiān)測-旋轉(zhuǎn)監(jiān)測

    C++智能指針的底層實(shí)現(xiàn)原理

    C++智能指針的頭文件: #include 1. shared_ptr: 智能指針從本質(zhì)上來說是一個(gè)模板類,用類實(shí)現(xiàn)對指針對象的管理。 template class shared_ptr
    的頭像 發(fā)表于 11-09 14:32 ?1091次閱讀
    <b class='flag-5'>C++</b>智能指針的底層實(shí)現(xiàn)原理