主要实现:简单的异步日志,工作线程只需把日志消息写入缓冲,不必阻塞与耗时的IO操作,由一个背景线程专门负责IO操作。
采用的数据结构,前端为双缓冲区,后端为BlockingQueue,缓冲区的初始化和析构都由背景线程负责,两块缓冲交替使用。
大致的逻辑是:前端使用两块缓冲A和B,A为写缓冲,B为预备缓冲,当写满了A,则交换A和B,并通知背景线程来走B。
背景线程接到前端线程的通知,被唤醒,取走B,放入BlockingQueue,重新准备一块全新的缓冲,放回B,然后去走BlockingQueue中的全部数据,退出临界区,把数据写到硬盘。
还有一种特殊情况,前端写入太快,超过了后端的处理速度。只能自己去申请新的缓冲区了。
这里仅仅贴出临界区的代码
核心类BlockQueue的头文件:
class BlockQueue
{
public:
BlockQueue();
~BlockQueue();
void append(const string& msg);
void stop();
private:
void writtingThread();
typedef std::shared_ptr<LogBuffer> BufPtr;
mutex m_mut;
BufPtr m_pcurBuf;
BufPtr m_pnextBuf;
BufPtr m_pvaildBuf;
condition_variable m_cond;
queue<BufPtr> blockingQueue;
bool isRunning;
queue<BufPtr> writeQueue;
};
//前端临界区的代码:
void BlockQueue::append(const string& msg){
mutex::scoped_lock sclock(m_mut);//进入临界区
if(m_pcurBuf->isValid()){ //第一块缓冲可用,直接写入
m_pcurBuf->appendLog(msg);
return;
}
else if(m_pnextBuf->isValid()){ //第一块缓冲写满,预备缓冲可用
m_pcurBuf->setFinishTime();
std::swap(m_pcurBuf,m_pnextBuf);
assert(m_pcurBuf->isValid());
m_pcurBuf->setStartTime();
m_pcurBuf->appendLog(msg);
m_cond.notify_one();//通知背景线程
return;
}
else{ //预备缓冲不可用,只能由工作线程自己初始化新缓冲了
m_pcurBuf->setFinishTime();
blockingQueue.push(m_pcurBuf);
m_pcurBuf = make_shared<LogBuffer> ();
m_pcurBuf->setStartTime();
m_pcurBuf->appendLog(msg);
m_cond.notify_one(); //通知背景线程
}
}
//后端临界区的代码:
void BlockQueue::writtingThread(){
while(isRunning){
{//进入临界区
mutex::scoped_lock sclock(m_mut);
while(m_pnextBuf->isValid() && blockingQueue.empty())//等待条件成立
m_cond.wait(sclock);
if(!m_pnextBuf->isValid()){//取走预备缓冲,交换备用缓冲的指针
blockingQueue.push(m_pnextBuf);
assert(m_pvaildBuf->isValid());
std::swap(m_pvaildBuf,m_pnextBuf);
}
while(!blockingQueue.empty()){//把blockingQueue的数据全部取出
BufPtr ptr = blockingQueue.front();
blockingQueue.pop();
writeQueue.push(ptr);
}
}//退出临界区
m_pvaildBuf = make_shared<LogBuffer> (); //初始化新的备用缓冲
while(!writeQueue.empty()){//IO操作
BufPtr ptr = writeQueue.front();
writeQueue.pop();
ptr->writeToDisk();
}
}
}