偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

多線程異步【日志系統(tǒng)】,高效、強(qiáng)悍的實(shí)現(xiàn)方式:雙緩沖!

開(kāi)發(fā)
在上一篇文章中,我主要從思路、概念的角度,來(lái)描述如何利用雙緩沖機(jī)制。這篇文章,我們就忠于書(shū)中原文,一起來(lái)學(xué)習(xí)一下作者的思考過(guò)程,并給出一些對(duì)性能起決定作用的關(guān)鍵代碼。

[[431670]]

別人的經(jīng)驗(yàn),我們的階梯!

大家好,我是道哥,今天我為大伙兒解說(shuō)的技術(shù)知識(shí)點(diǎn)是:【在多線程環(huán)境下,如何實(shí)現(xiàn)一個(gè)高效的日志系統(tǒng)】。

在很久之前,曾經(jīng)寫(xiě)過(guò)一篇文章《【最佳實(shí)踐】生產(chǎn)者和消費(fèi)者模式中的雙緩沖技術(shù)》,討論了:在一個(gè)產(chǎn)品級(jí)的日志系統(tǒng)中,如何利用雙緩沖機(jī)制來(lái)解決生產(chǎn)者-消費(fèi)者相關(guān)的問(wèn)題。

前段時(shí)間,有位小伙伴私信給我,希望可以具體聊一下這個(gè)實(shí)現(xiàn)方案。

本來(lái)答應(yīng)在國(guó)慶期間完成的,但是我的拖延癥一犯再犯,一直拖到今天,終于把這個(gè)作業(yè)給補(bǔ)上了。

雙緩沖這個(gè)思路并不是我原創(chuàng)的,而是參考了大神陳碩老師的一本書(shū)《Linux 多線程服務(wù)端編程》。

從書(shū)名就可以看出,討論的是服務(wù)器端的相關(guān)編程內(nèi)容,而且是多線程場(chǎng)景下的,因此可以隱約看出,書(shū)中給出的參考代碼的質(zhì)量是很高的。

如果您的主力開(kāi)發(fā)語(yǔ)言是 C++,強(qiáng)烈推薦您去研究下這本書(shū)。

很多 C++ 語(yǔ)言的細(xì)節(jié)問(wèn)題,作者都給出了自己專業(yè)、嚴(yán)謹(jǐn)?shù)乃伎己徒鉀Q方案。

言歸正傳!

在上一篇文章中,我主要從思路、概念的角度,來(lái)描述如何利用雙緩沖機(jī)制。

這篇文章,我們就忠于書(shū)中原文,一起來(lái)學(xué)習(xí)一下作者的思考過(guò)程,并給出一些對(duì)性能起決定作用的關(guān)鍵代碼。

先來(lái)看一下書(shū)中的性能測(cè)試結(jié)果:

 

 

單片機(jī)中常用的環(huán)形緩沖區(qū)

一說(shuō)到緩沖區(qū),相信各位小伙伴一定看過(guò)很多關(guān)于緩沖緩沖區(qū)的文章和代碼,在單片機(jī)中的使用率很高。

 

 

所謂的環(huán)形緩沖區(qū),就是一塊平整的內(nèi)存區(qū)域,讓它的尾部連接到首部即可。

  • 另一個(gè)類似的結(jié)構(gòu):環(huán)形隊(duì)列,本質(zhì)上都是一樣的。

維護(hù)環(huán)形緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu)中,有head和tail指針。

當(dāng)寫(xiě)入的時(shí)候,把輸入寫(xiě)入到tail指針的位置,寫(xiě)完之后,遞增tail的指針值;

當(dāng)讀取的時(shí)候,從head指針的位置開(kāi)始讀取,讀完之后,也遞增head的指針值。

這樣的操作方式,比較適合那種簡(jiǎn)單的單輸入、單輸出場(chǎng)景。

只要處理好:當(dāng) head 和 tail 這兩個(gè)指針交匯的時(shí)候如何處理即可。

但是在x86的操作系統(tǒng)中,在多核 + 多線程的工作環(huán)境下,無(wú)論是從功能上、還是從性能上來(lái)考慮,這樣的環(huán)形緩沖區(qū)就滿足不了需求了。

還是拿日志系統(tǒng)來(lái)舉例:在一個(gè)應(yīng)用程序中,可能會(huì)有多個(gè)線程同時(shí)調(diào)用日志系統(tǒng)的寫(xiě)入API接口函數(shù),這就需要保證線程安全。

  • 這樣的線程稱作 前臺(tái)/前端 線程。

日志數(shù)據(jù)存儲(chǔ)在內(nèi)存中之后,最終是要輸出的,比如:寫(xiě)入到文件系統(tǒng)、通過(guò)網(wǎng)絡(luò)上傳到服務(wù)端、輸出到其他的監(jiān)控系統(tǒng)等等。

實(shí)現(xiàn)輸出操作的也是一個(gè)線程,假如需要寫(xiě)入到文件系統(tǒng),那么在寫(xiě)入期間,這個(gè)線程就需要一直持有緩沖區(qū)中的日志數(shù)據(jù)。

  • 這樣的線程稱作 后臺(tái)/后端 線程。

但是,文件系統(tǒng)的寫(xiě)入速度是很慢的(畢竟要操作硬盤啊),如果這個(gè)時(shí)候又有前臺(tái)線程需要寫(xiě)日志信息了,該如何處理?

總不能暴力的說(shuō):后臺(tái)線程正在把現(xiàn)有的日志數(shù)據(jù)存儲(chǔ)到硬盤上,已經(jīng)持有了內(nèi)存緩沖區(qū),前臺(tái)線程你是后來(lái)的,先等著!

多線程異步日志:雙緩沖機(jī)制

在這本書(shū)中,作者對(duì)這樣的日志系統(tǒng)規(guī)定了幾個(gè)關(guān)鍵的要求,都是與實(shí)際的業(yè)務(wù)需求相關(guān)的:

  1. 線程安全:多個(gè)線程可以并發(fā)寫(xiě)日志,不造成競(jìng)爭(zhēng),兩個(gè)線程的日志信息不會(huì)交叉出現(xiàn);
  2. 吞吐量大;
  3. 日志消息有多種級(jí)別,格式可配置等等;

為了達(dá)到這個(gè)目的,作者提出了“雙緩沖”思路(Double Buffering)。

基本思路是:

準(zhǔn)備兩塊 buffer: A 和 B;

前端負(fù)責(zé)往 buffer A 填數(shù)據(jù)(日志信息);

后端負(fù)責(zé)把 buffer B 的數(shù)據(jù)寫(xiě)入文件。

當(dāng) buffer A 寫(xiě)滿之后,交換 A 和 B,讓后端將 buffer A 的數(shù)據(jù)寫(xiě)入文件,而前端則往 buffer B 填入新的日志信息,如此反復(fù)。

其實(shí)還是蠻好理解的哈,我們還是來(lái)畫(huà)圖描述一下:

 

 

當(dāng) buffer A 寫(xiě)滿之后,交換兩個(gè)緩沖區(qū):

 

 

雙緩沖機(jī)制為什么高效

使用兩個(gè)buffer緩沖區(qū)的好處是:

在大部分的時(shí)間中,前臺(tái)線程和后臺(tái)線程不會(huì)操作同一個(gè)緩沖區(qū),這也就意味著前臺(tái)線程的操作,不需要等待后臺(tái)線程緩慢的寫(xiě)文件操作(因?yàn)椴恍枰i定臨界區(qū))。

還有一點(diǎn)就是:后臺(tái)線程把緩沖區(qū)中的日志信息,寫(xiě)入到文件系統(tǒng)中的頻率,完全由自己的寫(xiě)入策略來(lái)決定,避免了每條新日志信息都觸發(fā)(喚醒)后端日志線程。

例如:可以根據(jù)實(shí)際使用場(chǎng)景,定義一個(gè)刷新頻率,例如:3秒。

只要刷新時(shí)間到了,即使緩沖區(qū)中的日志信息很少,也要把它們存儲(chǔ)到文件系統(tǒng)中。

換言之,前端線程不是將一條條日志信息分別傳送給后端線程,而是將多條信息拼成一個(gè)大的 buffer 傳送給后端,相當(dāng)于是批量處理,減少了線程喚醒的頻率,降低開(kāi)銷。

盡可能的降低 Lock 的時(shí)間

在剛才的描述中,有這么一句話:在[大部分的時(shí)間中],前臺(tái)線程和后臺(tái)線程不會(huì)操作同一個(gè)緩沖區(qū)。

也就是是說(shuō),在小部分時(shí)間內(nèi),它們還是有可能操作同一個(gè)緩沖區(qū)的。

那就是:當(dāng)前臺(tái)的寫(xiě)入緩沖區(qū) buffer A 被寫(xiě)滿了,需要與 buffer B 進(jìn)行交換的時(shí)候。

交換的操作,是由后臺(tái)線程來(lái)執(zhí)行的,具體流程是:

  1. 后臺(tái)線程被喚醒,此時(shí) buffer B 緩沖區(qū)是空的,因?yàn)樵谏弦淮芜M(jìn)入睡眠之前,buffer B 中數(shù)據(jù)已經(jīng)被寫(xiě)入到文件系統(tǒng)中了;
  2. 把 buffer A 與 buffer B 進(jìn)行交換;
  3. 把 buffer B 中的數(shù)據(jù)寫(xiě)入到文件系統(tǒng);
  4. 開(kāi)始休眠;

在第2個(gè)步驟中:交換緩沖區(qū),就是把兩個(gè)指針變量的值交換一下而已,利用C++語(yǔ)言中的swap操作,效率很高。

在執(zhí)行交換緩沖區(qū)的時(shí)候,可能會(huì)有前臺(tái)線程寫(xiě)入日志,因此這個(gè)步驟需要在 Lock 的狀態(tài)下執(zhí)行。

可以看出:這個(gè)雙緩沖機(jī)制的前后臺(tái)日志系統(tǒng),需要鎖定的代碼僅僅是交換兩個(gè)緩沖區(qū)這個(gè)動(dòng)作,Lock 的時(shí)間是極其短暫的!這就是它提高吞吐量的關(guān)鍵所在!

參考代碼

在示例代碼中,作者對(duì)雙緩沖機(jī)制進(jìn)行了擴(kuò)展,采用4個(gè)緩沖區(qū),這樣可以進(jìn)一步減少或避免前端線程的等待時(shí)間。

數(shù)據(jù)結(jié)構(gòu)如下:

 

 

這里的 nextBuffer_ 相當(dāng)有是currentBuffer_的“備胎”。

當(dāng)前臺(tái)線程發(fā)現(xiàn)currentBuffer_不可用時(shí)(空間已滿,或者正在被后臺(tái)線程操作),可以立刻寫(xiě)入到這個(gè)"備胎"緩沖區(qū)中,從而降低了前臺(tái)線程的等待時(shí)間。

下面是前臺(tái)線程的寫(xiě)入代碼:

 

 

 

前端線程在生成一條日志消息的時(shí)候,會(huì)調(diào)用append()函數(shù)。

在這個(gè)函數(shù)中,如果當(dāng)前緩沖區(qū)(currentBuffer_)剩余的空間足夠大,直接把消息消息拷貝(追加)進(jìn)去,這是最常見(jiàn)的情況。

如果當(dāng)前緩沖區(qū)的剩余空間,小于這次日志信息的寫(xiě)入長(zhǎng)度,就把它移動(dòng)到 buffer_ 集合中(一個(gè)Vector),此時(shí)會(huì)發(fā)送喚醒信號(hào)給后端線程,然后把 nextBuffer_ 這個(gè)備胎 move 為 currentBuffer_。

  • move 是 C++ 中的操作,意思是移動(dòng),而不是拷貝/復(fù)制。

當(dāng)然了,如果前端的寫(xiě)入速度太快,一下子就把兩塊緩沖區(qū)都用完了,那么只好分配一塊新的 buffer 作為當(dāng)前緩沖區(qū),這是極少發(fā)生的情況。

再來(lái)看看后端的代碼實(shí)現(xiàn),這里只貼出了最關(guān)鍵的臨界區(qū)內(nèi)的代碼,也就是前文所說(shuō)的“小部分時(shí)間”的情況:

 

 

這段代碼中最重要的就是 swap 函數(shù),它把前后臺(tái)使用的緩沖區(qū)進(jìn)行了交換。

當(dāng)前后臺(tái)緩沖區(qū)交換之后,就離開(kāi)了臨界區(qū),此時(shí)后臺(tái)線程就可以慢慢的往文件系統(tǒng)中寫(xiě)入數(shù)據(jù)了。

另外,這段代碼中還有一個(gè)地方比較有意思,就是對(duì)備胎 nextBuffer_ 的操作:

當(dāng)前臺(tái)中使用的備胎 nextBuffer_ 已經(jīng)被消耗掉時(shí),后臺(tái)線程及時(shí)地為它補(bǔ)充一個(gè)新的備胎。

可以繼續(xù)優(yōu)化的地方

在本章的最后部分,作者提出了一個(gè)更加嚴(yán)苛的情況:

異步日志系統(tǒng)中,使用了一個(gè)全局鎖,盡管臨界區(qū)很小,但是如果線程數(shù)目較多,鎖爭(zhēng)用也可能影響性能。

一種解決方法是像 Java 的 ConCurrentHashMap 那樣使用多個(gè)桶子(bucket),前端線程寫(xiě)日志的時(shí)候根據(jù)線程id哈希到不同的 bucket 中,以減少競(jìng)爭(zhēng)。

這種解決方案本質(zhì)上就是提供更多的緩沖區(qū),并且把不同的緩沖區(qū)分配給不同的線程(根據(jù)線程 id 的哈希值)。

那些哈希到相同緩沖區(qū)的線程,同樣是存在爭(zhēng)用的情況的,只不過(guò)爭(zhēng)用的概率被降低了很多。

本文轉(zhuǎn)載自微信公眾號(hào)「IOT物聯(lián)網(wǎng)小鎮(zhèn)」

 

責(zé)任編輯:姜華 來(lái)源: IOT物聯(lián)網(wǎng)小鎮(zhèn)
相關(guān)推薦

2018-04-02 14:29:18

Java多線程方式

2010-07-14 10:30:26

Perl多線程

2010-03-17 17:30:26

JAVA多線程實(shí)現(xiàn)

2023-06-13 13:39:00

多線程異步編程

2024-05-10 07:44:23

C#進(jìn)程程序

2023-10-24 09:03:05

C++編程

2024-08-06 09:43:54

Java 8工具編程

2025-02-24 09:30:00

日志系統(tǒng)系統(tǒng)開(kāi)發(fā)

2017-04-17 19:31:03

Android多線程

2023-03-24 15:44:52

Java多線程工具

2015-05-13 14:22:44

RedisNoSQL數(shù)據(jù)庫(kù)

2009-08-21 11:31:59

異步和多線程的區(qū)別

2025-03-04 08:40:28

2019-10-29 19:49:48

Java線程安全

2010-03-03 17:44:07

Python多線程

2023-05-10 07:47:08

Python并發(fā)編程

2024-12-03 00:44:50

2023-11-07 10:01:34

2024-03-28 12:51:00

Spring異步多線程

2024-11-15 11:00:00

C#多線程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)