webserver采用异步日志系统:
逻辑:创建一个线程,不断读消息队列(数组实现的循环队列)中的消息,写入日志文件;需要写日志时,将消息放入消息队列中即可。
Log
类中维护一个消息队列和互斥锁,当需要写日志时,对消息队列进行加锁解锁操作,避免竞争。
Log
类初始化时,创建一个线程,线程循环等待消息队列不为空:
void async_write_log() { string log; while (!m_log_queue->empty()) { m_mutex.lock(); log = m_log_queue->front(); m_log_queue->pop(); //消息取出来之后,即时归还锁,*写文件的过程不要占用锁* m_mutex.unlock(); //写消息 fwrite((void *)log.c_str(), 1, log.size(), m_fp); fflush(m_fp); } }
消息队列中维护一个互斥锁和条件变量,队列不为空,线程不断取日志写日志,当队列为空时empty
阻塞等待条件变量,当向队列中push
消息时条件变量唤醒,队列不为空empty
不再阻塞,线程又开始写日志。
bool empty() { m_mutex.lock(); while (m_cnt == 0) { //如果条件变量等待出错了 if (!m_cond.wait(m_mutex.get())) { m_mutex.unlock(); return true; } //否则等到了,m_cnt此时一定不为空,便可以退出循环 } m_mutex.unlock(); return false; }
向队列中push消息后,条件变量触发,消息队列不为空:
bool push(string str) { m_mutex.lock(); if (str == "" || m_cnt == m_max_size) { m_cond.broadcast(); m_mutex.unlock(); return false; } m_data[m_tail++] = str; if (m_tail == m_max_size) m_tail = 0; m_cnt++; m_cond.broadcast(); m_mutex.unlock(); return true; }
当写日志线程写的比较慢时,日志会不断的累加到消息队列中,写日志线程不断去取任务,做任务。
再来看下循环队列是如何实现的?
一个固定大小的数组m_data
,通过head
和tail
变量来实现弹出和弹入队列的操作,维护一个当前size变量m_cnt
和最大size变量m_max_size
,如果m_cnt == m_max_size
说明队列满了,否则说明队列不满。
head
指向队首元素
tail
指向队尾下一个元素
pop操作时:head++,m_cnt--
,如果head
超出了数组,就置为0。下次pop时如果队列不为空,直接pop出0号位置。
push操作时:tail++,m_cnt++
,如果tail
超出了数组,就置为0。下次push时如果队列不满,直接push到0号位置。
来看一个简单的示例:
#include <iostream> using namespace std; class XunHuanDuiLie { public: XunHuanDuiLie(int length) : m_data(new int(length)) , m_cnt(0) , m_max_cnt(length) , head(0) , tail(0) { for (int i = 0; i < m_max_cnt; i++) { m_data[i] = -1; } } int top() { if (empty()) { cout << "queue is empty return -1" << endl; return -1; } return m_data[head]; } void pop() { if (empty()) { cout << "queue is empty" << endl; return; } head++; m_cnt--; if (head == m_max_cnt) { head = 0; } return; } void push(int val) { //首先判空 if (full()) { cout << "queue is full" << endl; return ; } //tail代表末尾元素的下一位 m_data[tail++] = val; m_cnt++; if (tail == m_max_cnt) { //tail到了末尾 tail = 0; } return; } bool full() { return m_cnt == m_max_cnt; } bool empty() { return m_cnt == 0; } int size() { return m_cnt; } void output() { cout << "m_cnt: " << m_cnt << endl; cout << "head: " << head << endl; cout << "tail: " << tail << endl; for (int i = 0; i < m_max_cnt; i++) { cout << m_data[i] << " "; } cout << endl; } private: int *m_data; int m_cnt; int m_max_cnt; int head; int tail; }; int main() { XunHuanDuiLie queue(4); //push 了4次 queue.push(1); queue.push(2); queue.push(3); queue.push(4); //pop五次 cout << queue.top() << endl; queue.pop(); cout << queue.top() << endl; queue.pop(); cout << queue.top() << endl; queue.pop(); cout << queue.top() << endl; queue.pop(); cout << queue.top() << endl; queue.pop(); queue.push(5); cout << queue.top() << endl; //queue.pop(); queue.push(6); cout << queue.top() << endl; queue.pop(); queue.output(); system("pause"); return 0; }
m_cnt
变量至关重要,它记录的是当前元素的个数。元素的出队操作并没有正真删除元素,而是改变了head的指向,入队时覆盖该位置的值即可。
而关于pop操作和push操作是否会影响队列中已存在的元素的困惑,关键点在于:
如果当前队列不为空,就可以出队。
如果当前队列不满,就可以入队。
日志系统中的循环队列也是采用这种方式。