久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

多線程編程基礎

 天道酬勤YXJ1 2016-05-19

線程定義

線程的定義在維基百科和各種教材書中都能找到,這里再簡單描述一下:操作系統(tǒng)中能夠被調度的最小單位,,有自己的context,、stack以及thread-local存儲,但與同一進程中的其他線程共享進程資源,。如果從另外一個角度來思考:比如把C語言的指針作為機器地址的抽象的話,,那么線程可以認為是機器流水線或者“虛擬處理器”的抽象。只是流水線之間的耦合并沒有線程來的那么緊密,,原因在于線程之間還是共享了很多東西,,編碼時則需要線程安全。

線程安全

很多場合,,線程安全總是和可重入混在一起,。需要引起注意的是,這兩個并不是同一個概念,。可重入(別名 異步信號安全)是一個更嚴格的要求,,首先它要求線程安全,其次當發(fā)生信號中斷并執(zhí)行完處理例程后繼續(xù)執(zhí)行仍然保證正確方可,。使用mutex的函數(shù)可能是線程安全的,,但不是可重入的。二者大致關系如下圖,,可重入函數(shù)只是線程安全函數(shù)的一部分,。

多線程編程基礎

(面積比例不代表函數(shù)數(shù)量比例)

不過無論是線程安全或者可重入,,二者都具備傳遞性:若當前函數(shù)調用了非線程安全函數(shù),則當前函數(shù)一定不是線程安全的,。

線程同步與線程安全的實現(xiàn)機制

最好就是不共享任何的資源,,從而避免競爭(race condition),比如比較兩個參數(shù)大小的函數(shù)

inline int max(int a, int b){ return a > b ? a : b; }

如果一定要共享,,那么可以通過互斥鎖來實現(xiàn),。但在共享前,需要識別的是是否僅僅是偽共享,,比如errno,。業(yè)務邏輯如果允許每個線程一份copy,而且無須同步的話,,則可通過將資源thread-local化來實現(xiàn),。

POSIX提供pthread_key_createpthread_get(set)specific接口來實現(xiàn),,而新的C++11更是將thread_local設置成了關鍵字更好的進行支持,。

互斥鎖

在操作系統(tǒng)提供的編程接口層面,開發(fā)者可用的同步手段很多,,其中以互斥鎖應用最為廣泛(信號量是一種將資源數(shù)目從1泛化到n的互斥鎖),。當線程進入臨界區(qū)前獲得鎖,只有獲得了鎖的線程才可能繼續(xù)執(zhí)行,,當退出臨界區(qū)后歸還鎖,。如果鎖被占用,則線程進入阻塞狀態(tài),。但是要預防錯誤的加鎖順序或者持有鎖但不釋放的場景,,否則會造成程序死鎖、異常,,陷入不易復現(xiàn)的bug,。通過合理的利用RAII機制來保證鎖的獲取與釋放是多線程編程的一種最佳實踐,以至于這也進入了C++11的新標準,,比如std::unique_lock,。

即使編碼時非常小心,已經(jīng)注意到了加鎖順序,、鎖申請必然釋放等規(guī)則,,但仍然有可能存在問題。

還記的發(fā)生在火星上的災難吧:優(yōu)先級翻轉?,F(xiàn)代操作系統(tǒng)通過優(yōu)先級繼承較好的解決了這個問題,,但程序員需要注意自己代碼所運行的平臺是否有這個機制,然后正確的設置線程屬性方可,。

此外,,如果所有優(yōu)先級都調到一個數(shù)量級,,那么還需要注意lock convoy問題。

發(fā)生lock convoy的場景猶如2人迎面通過一獨木橋,,2人相遇后均主動放棄退回,,然后再次上橋相遇。注意,,這不是死鎖,。我們的業(yè)務軟件需要避免這種設計。類似的問題比如當初accept的驚群問題以及當在條件變量中喚醒所有的等待線程時也會瞬時觸發(fā)單回合的類似場景,。

條件變量

條件變量也是同步的一種手段,,由一把鎖(mutex)和一個condition組成。它可以使線程阻塞在某一條件上,,比如queue.not_empty(),。當條件滿足時,線程喚醒,。需要注意是要小心虛假喚醒,,即當wait返回后,,需要再次判斷條件是否滿足,。C++11中的wait接口有了第二個參數(shù),允許傳入predicate,,借用lambda可以省掉幾行代碼,,極大簡化了編碼。比如

_cv_ready.wait(lock,[=]{return _tail != _head;})

當隊列頭不等于隊列尾的條件滿足時(不為空),,線程喚醒并再次檢查條件,。

讀寫鎖

允許讀鎖并發(fā),寫-寫,,寫讀互斥,。但尷尬的是,這是一個充滿誘惑的坑:由于語義復雜性,,其內部實現(xiàn)效率要比普通mutex慢很多,。因此當且僅當在多讀一寫的情況下,并且讀鎖臨界區(qū)非常大時,,比如要做IO,,才適合用讀寫鎖。不過針對不同的場景,,可以借鑒這種思路,,比如實現(xiàn)一種用于監(jiān)控統(tǒng)計功能的“寫讀鎖”,即寫遠遠大于讀的頻率,,等到運維人員讀某個統(tǒng)計時才合并所有的寫,。這樣可以有效降低cache bouncing,。

spinlock

spinlock類似互斥鎖,但等待鎖期間不會被切換,,而是一直空轉,。因此比較耗費cpu資源,尤其當持有鎖的線程被換出時,。因此使用spinlock的場景,,僅適用實時、短小且不會主動切換的場景(比如持有鎖期間肯定不包含IO之類的操作),。實際上mutex的實現(xiàn)中自帶了部分spinlock,,所以用戶態(tài)下除非必要,盡量選用互斥鎖,,否則容易適得其反,。

原子操作

原子操作可類比數(shù)據(jù)庫ACID的atomic。很多公司的平臺部門在做基礎庫時會使用asm匯編實現(xiàn)原子操作,。C++11則直接提供了std::atomic供程序員使用,。和前面的介紹相同,本文重點介紹使用陷阱:編譯器和CPU在生成或執(zhí)行指令時可能打亂某些看似無關的代碼(指令),。但實際上在多線程環(huán)境下存在依賴關系,如下例,,假設thread_1/2運行在同一進程的不同線程,則可能打印a值為0,。

atomic a;

atomic b;int thread_1(){ int t = 1;

a = t;

b = 2;

}int thread_2{ while(b != 2); cout < a=""><>

}

因此C++11中大部分atomic都需要指定當前場景所需的memory order來提高性能,。

除此之外,一個初學者常犯的錯誤,,比如a,b 兩個變量都是atomic屬性,,但if(a > b)這種寫法可不是線程安全的,畢竟a > b其實是多個指令并不是原子的,。眾多的lock-free實現(xiàn)也是基于atomic的,,但同時也有諸多問題需要注意,比如ABA problem,。所以,,在使用atomic的時候,盡量選擇使用在簡單變量的讀寫共享上,,即始終明確臨界區(qū)的概念中所謂的鎖,,并不是鎖定一個資源,而是鎖定對該資源的訪問操作,。

性能相關

當通過互斥或條件變量來實現(xiàn)線程同步,,不可避免的會發(fā)生主動的線程切換。而不恰當?shù)那袚Q則會影響到系統(tǒng)的吞吐和效率,這里僅從同步原語出發(fā)針對多線程編程總結一下,。

顯然,,多線程加鎖的消耗取決于競爭的激烈程度以及上下文切換的開銷。因此縮小臨界區(qū),,將不用鎖的尤其那些重量級的不用鎖的部分移到臨界區(qū)外是提高性能的不二法則,。而pthread_mutex_t實現(xiàn)基于Linux的futex,當臨界區(qū)足夠小時,,一次pthread_mutex_lock消耗很非常小,。

此外,通過對不同的業(yè)務邏輯采用恰當?shù)木€程模型,,也能夠避免競爭的激烈程度,。

線程模型

對于不同的業(yè)務邏輯,多線程的設計存在不同的模型,,注意,,這里的模型不是指操作系統(tǒng)的1:1 、N:1,、N:M線程模型,,而是如何使用線程來應對不同的業(yè)務場景。對此可以大致分為運算密集型,、IO密集型,。

眾所周知,對于多線程編程通常采用線程池技術來降低線程創(chuàng)建和退出的消耗,,但如何使用這些線程呢,,一般來說,策略分為

  • work group

  • pipeline

work-group模型

在工作組模型中,,請求(或稱數(shù)據(jù))是被一組線程處理的,如下圖:

多線程編程基礎

根據(jù)操作的不同,,可分為MIMD和SIMD兩種,。

  • 如果每個工作線程從共享隊列中獲取工作請求并處理,由于隊列中的操作和數(shù)據(jù)不盡相同——類似MIMD(多指令多數(shù)據(jù)),。

  • 如果工作組的線程,,每個負責處理數(shù)據(jù)或請求的一部分(例如,某列或行),,所有工作線程在不同的數(shù)據(jù)上執(zhí)行相同的操作——類似SIMD(單指令多數(shù)據(jù)),。

在實際編程中,leader-follower pattern為大家所熟知,。其工作流程為:線程池初始化后,,存在唯一的leader線程,等待工作請求的到來,請求到達后leader讀取請求并開始處理,,將自己身份變成follower,,同時從線程池中選出下一個線程作為新leader,當這次請求完成結果送回后該線程把自己重新送回到線程池中,。

多線程編程基礎

pipe-line模型

在流水線方式中,,數(shù)據(jù)流串行的被一組線程順序處理。每個線程依次在數(shù)據(jù)上執(zhí)行自身特定的操作,,并將執(zhí)行結果傳遞給流水線中的下一個線程,。編程中常見的即:生產(chǎn)者和消費者。

多線程編程基礎這里需要注意個平均分配到線程工作量不應差別太大,,否則很容易在后續(xù)優(yōu)化中會碰到串行化方面的難題:Amdahl's Law.

多線程編程基礎

即,,即使將某個部分效率大幅提升,但總的吞吐仍然維持在意想不到的小幅度提高,。

總結

本文主要介紹了在多線程編程中的諸多注意事項和常見多線程模型,,希望讀者在實戰(zhàn)中能過避開已知的坑,在這里祝大家線程安全,!

    本站是提供個人知識管理的網(wǎng)絡存儲空間,,所有內容均由用戶發(fā)布,不代表本站觀點,。請注意甄別內容中的聯(lián)系方式,、誘導購買等信息,謹防詐騙,。如發(fā)現(xiàn)有害或侵權內容,,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多