logfile類和asynclogging類各有自己的buffer(在下文中,分別記為file_buffer和async_buffer)。
當使用者使用log_*寫入日誌內容時,將會把日誌內容寫入到async_buffer中,當async_buffer寫滿了,就會把async_buffer中的內容寫入到file_buffer中。在logfile類中,會自動將file_buffer的內容flush到硬碟。
這是資料流的大致流向,讀者先有乙個大致的方向。具體是怎麼非同步的,下面會慢慢細說。
asynclogging類有乙個執行緒成員變數,asynclogging類的**由兩個執行緒執行。乙個是使用log_*進行寫日誌的執行緒(更確切地說是使用了log_*的所有執行緒),另外乙個就是asynclogging類的執行緒成員變數了,分別把這兩個執行緒稱為前台和後台執行緒,他們也分別是同步問題中的生產者和消費者。一般情況下,前台執行緒和後台執行緒各有兩個buffer。前面說的async_buffer是前台和後台buffer的統稱。
先貼上前台執行緒**。**中用到了currentbuffer_和nextbuffer_兩個前台buffer。還有乙個buffers_智慧型指標陣列(其型別為boost::ptr_vector),其存放已經滿了的前台buffer。
else
else
cond_.notify(); //通知後台執行緒,已經有乙個滿了的buffer了。
}}可以看到前台執行緒所做的工作比較簡單,如果currentbuffer_夠用,就把日誌內容寫入到currentbuffer_中,如果不夠用(就認為其滿了),就把currentbuffer_放到已滿buffer陣列中,等待消費者執行緒(即後台執行緒)來取。並且把currentbuffer_指向nextbuffer_的buffer。
可以看到前台執行緒把兩個前台buffer用掉了,下面可以看看後台執行緒是怎麼歸還這個兩個buffer的。
後台執行緒**由於比較長,這裡將刪除一些和本文無關的**。
在asynclogging類中還有下面的宣告:
typedef boost::ptr_vectorbuffervector;
typedef buffervector::auto_type bufferptr;
voidasynclogging::threadfunc()
//無論cond是因何而醒來,都要將currentbuffer_放到buffers_中。
//如果是因為時間到而醒,那麼currentbuffer_還沒滿,此時也要將之寫入logfile中。
//如果已經有乙個前台buffer滿了,那麼在前台執行緒中就已經把乙個前台buffer放到buffers_中
//了。此時,還是需要把currentbuffer_放到buffers_中(注意,前後放置是不同的buffer,
//因為在前台執行緒中,currentbuffer_已經被換成nextbuffer_指向的buffer了)
buffers_.push_back(currentbuffer_.release());
currentbuffer_ =boost::ptr_container::move(newbuffer1); /*---歸還乙個buffer---*/
bufferstowrite.swap(buffers_); //交換
if (!nextbuffer_)
}//將已經滿了的buffer內容寫入到logfile中。由logfile進行io操作。
for (size_t i = 0; i reset();
} //前台buffer是由newbuffer1 2 歸還的。現在把bufferstowrite的buffer歸還給後台buffer
if (!newbuffer2)
bufferstowrite.clear();
output.flush(); //flush to drive. less than3 mins a time
} output.flush();
}
從**可以看到後台執行緒的主要如下:
先用執行緒同步中的條件變數進行睡眠,睡眠時間為日誌庫的flush時間。所以,當條件變數的條件滿足(即前台執行緒把乙個已經滿了的buffer放到了buffers_中),或者超時。無論是哪種情況,都還會有乙個currentbuffer_前台buffer正在使用。將這個currentbuffer_放到已滿buffers_陣列中。這樣buffers_就有了待進行io的buffer了。
將buffertowrite和buffers_進行swap。這就完成了將寫了日誌記錄的buffer從前台執行緒到後台執行緒的轉變。後台執行緒慢慢進行io即可。
這個過程就完成了日誌的非同步輸出。下面說一下後台執行緒怎麼進行具體的io的。這個過程將涉及到另外乙個類 logfile。
下面給出file類的具體內容。
class logfile::file :boost::noncopyable
~file()
writtenbytes_ += len;
} void flush()
size_t writtenbytes() const
private:
size_t write(const char* logline, size_t len)
file* fp_;
char buffer_[64*1024];
size_t writtenbytes_;
};
muduo裡面的自動flush還是比較容易看懂的。在前面貼出的後台執行緒中的最後兩條語句都是output.flush(); 其中,倒數第二句就是自動flush了。後台執行緒每次休眠的時間都是flushinterval_,即重新整理間隔。無論後台執行緒是被前台執行緒喚醒還是超時醒來,其的醒來都是在flushinterval_時間內醒來。醒來後一路執行下去,總會執行到output.flush(); flush日誌內容到磁碟。
在muduo的配套書籍《linux多執行緒服務端程式設計 使用muduo c++網路庫》中(p.110)說到,rolling的條件通常有兩個:檔案大小(例如每寫滿1gb就換下乙個檔案)和時間(例如每天零點新建乙個日誌檔案,不論前乙個檔案有沒有寫滿)。
在muduo中,確實會根據檔案大小和時間來主動滾動檔案。不過,時間並不是每天的零點。具體是什麼時候,是不確定的,其是根據日誌的疏密情況來判斷的。但還是會在凌晨的第乙個小時裡滾動檔案。
還是貼**吧。要注意閱讀注釋部分。
else //這裡根據時間來判斷是否到了滾動的時候
else if(now - lastflush_ > flushinterval_)
}else
}}後台執行緒有兩種情況會醒來,一是至少乙個前台buffer滿了;二是超時。
muduo 日誌庫學習 一
大佬部落格 muduo的日誌庫由logstream logging logfile asynclogging組成。這裡主要說明一下,這些檔案 主要是檔案裡面對應的類 之間是怎麼關聯,並協同工作的。logstream類裡面有乙個buffer成員 乙個模板類,並非muduo buffer類 該類主要負責...
muduo庫學習篇 Thread類學習
多執行緒程式設計在任何語言中基本都是乙個繞不開的話題,如果我們想要發揮計算機多核的優勢,提高程式的響應速度,就一定要使用到多執行緒程式設計技術。因此muduo庫一定少不了thread的封裝,接下來我們開始學習muduo庫thread類的封裝。如果讓我自己設計乙個thread類的話我能想到的有哪些 上...
muduo庫原始碼學習 base Logfile
本檔案使用的是c 17版本 ifndef muduo base logfile h define muduo base logfile h include include include namespace muduo class logfile noncopyable 檔案日誌類 endif mu...