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操作是否会影响队列中已存在的元素的困惑,关键点在于:
如果当前队列不为空,就可以出队。
如果当前队列不满,就可以入队。
日志系统中的循环队列也是采用这种方式。