【web server】日志系统

本文介绍了如何使用循环队列实现一个异步日志系统,通过一个线程不断读取消息队列并写入日志文件。文章详细阐述了消息队列的结构,包括互斥锁和条件变量的使用,以及如何处理队列满和空的情况。此外,还展示了循环队列的具体实现,包括push和pop操作,并通过一个实例展示了其工作原理。这种设计确保了即使日志写入速度较慢,也能有效地处理日志积累,保持系统的高效运行。
摘要由CSDN通过智能技术生成

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,通过headtail变量来实现弹出和弹入队列的操作,维护一个当前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操作是否会影响队列中已存在的元素的困惑,关键点在于:
如果当前队列不为空,就可以出队。
如果当前队列不满,就可以入队。

日志系统中的循环队列也是采用这种方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaoyuelon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值