AsyncLogging::AsyncLogging(const string& basename,
off_t rollSize,
int flushInterval)
: flushInterval_(flushInterval),
running_(false),
basename_(basename),
rollSize_(rollSize),
thread_(std::bind(&AsyncLogging::threadFunc, this), "Logging"),
latch_(1),
mutex_(),
cond_(mutex_),
currentBuffer_(new Buffer),
nextBuffer_(new Buffer),
buffers_()
{
currentBuffer_->bzero();
nextBuffer_->bzero();
buffers_.reserve(16);
}
在AsyncLogging中,除了和LogFile类相同的日志文件的滚动设置和刷新间隔设置之外,多了很多东西。其中比较重要的几个是thread_、currentBuffer_、nextBuffer_。
thread_线程的调用函数是threadFunc,该函数也是异步日志的后台线程,最重要的部分。
void AsyncLogging::threadFunc()
{
assert(running_ == true);
latch_.countDown();
LogFile output(basename_, rollSize_, false);
BufferPtr newBuffer1(new Buffer);
BufferPtr newBuffer2(new Buffer); //提前准备两个buffer
newBuffer1->bzero();
newBuffer2->bzero();
BufferVector buffersToWrite;
buffersToWrite.reserve(16);
while (running_)
{
assert(newBuffer1 && newBuffer1->length() == 0);
assert(newBuffer2 && newBuffer2->length() == 0);
assert(buffersToWrite.empty());
{
muduo::MutexLockGuard lock(mutex_);
if (buffers_.empty()) // unusual usage!
{
cond_.waitForSeconds(flushInterval_);
}
buffers_.push_back(std::move(currentBuffer_));
currentBuffer_ = std::move(newBuffer1);
buffersToWrite.swap(buffers_);
if (!nextBuffer_)
{
nextBuffer_ = std::move(newBuffer2);
}
}
assert(!buffersToWrite.empty());
if (buffersToWrite.size() > 25)
{
char buf[256];
snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger buffers\n",
Timestamp::now().toFormattedString().c_str(),
buffersToWrite.size()-2);
fputs(buf, stderr);
output.append(buf, static_cast<int>(strlen(buf)));
buffersToWrite.erase(buffersToWrite.begin()+2, buffersToWrite.end());
}
for (const auto& buffer : buffersToWrite)
{
// FIXME: use unbuffered stdio FILE ? or use ::writev ?
output.append(buffer->data(), buffer->length());
}
if (buffersToWrite.size() > 2)
{
// drop non-bzero-ed buffers, avoid trashing
buffersToWrite.resize(2);
}
if (!newBuffer1)
{
assert(!buffersToWrite.empty());
newBuffer1 = std::move(buffersToWrite.back());
buffersToWrite.pop_back();
newBuffer1->reset();
}
if (!newBuffer2)
{
assert(!buffersToWrite.empty());
newBuffer2 = std::move(buffersToWrite.back());
buffersToWrite.pop_back();
newBuffer2->reset();
}
buffersToWrite.clear();
output.flush();
}
output.flush();
}
注意,这里的LogFile是线程安全性设置的是false,因为只有后台线程执行写入文件的操作,所以不需要线程安全,可以提高写入速度。
在while循环中,会等待条件变量,而且使用的是waitForSeconds设置超时时间的等待。这样即使没有被唤醒,也可以定期将信息写入文件。buffersToWrite.swap(buffers_) 交换buffers_和buffersToWrite,因为buffersToWrite是局部变量,所以在之后的写入操作中,不需要加锁。
void AsyncLogging::append(const char* logline, int len)
{
muduo::MutexLockGuard lock(mutex_);
if (currentBuffer_->avail() > len)
{
currentBuffer_->append(logline, len);
}
else
{
buffers_.push_back(std::move(currentBuffer_));
if (nextBuffer_)
{
currentBuffer_ = std::move(nextBuffer_);
}
else
{
currentBuffer_.reset(new Buffer); // Rarely happens
}
currentBuffer_->append(logline, len);
cond_.notify();
}
}
这个接口是提供写入使用的,如果currentBuffer_有足够的空间,那么就写入到currentBuffer_中,否则将currentBuffer_ move到buffers_中,并且将nextBuffer_ move给currentBuffer_ ,这样写入始终都是用的currentBuffer_ ,而这也是为什么要使用两个buffer的原因。但是有一种情况,写入数据太快,后台程序还没有来得及将执行 nextBuffer_ = std::move(newBuffer2);给nextBuffer_重新设置值,那么就要重新申请一个Buffer,不过这种情况很少发生。在该函数的最后,调用条件变量的notify,这时候后台线程就可以从waitForSeconds中返回开始工作了。