Muduo源码笔记系列:
muduo源码阅读笔记(0、下载编译muduo)
muduo源码阅读笔记(1、同步日志)
muduo源码阅读笔记(2、对C语言原生的线程安全以及同步的API的封装)
muduo源码阅读笔记(3、线程和线程池的封装)
muduo源码阅读笔记(4、异步日志)
muduo源码阅读笔记(5、Channel和Poller)
muduo源码阅读笔记(6、EvevntLoop和Thread)
muduo源码阅读笔记(7、EventLoopThreadPool)
muduo源码阅读笔记(8、定时器TimerQueue)
muduo源码阅读笔记(9、TcpServer)
muduo源码阅读笔记(10、TcpConnection)
muduo源码阅读笔记(11、TcpClient)
前言
终于到了Muduo网络库最最核心的部分,这里还是建议大家亲自看看源码。源码很好读,博客最多起到辅助作用。因为EventLoop和Thread是绑定的,所以,可能这两部分放在一起更适合。
了解ExevntLoop和Thread后,Muduo库的One Loop Per Thread思想,就有了一个大体的轮廓,这种设计思想,真的很高效,因为,每个线程都有自己的epoll、IO事件处理,定时器、任务队列等。每个线程内部资源都是自我维护和处理的(自己的事情自己做), 除了向线程的EventLoop的任务队列中添加、执行任务有一段极小的临界区需要加锁外,不涉及任何锁的竞争。 巧妙的利用单线程天生串行执行的优势外,还为每个线程设置各自的任务队列,利用任务队列,使任务也串行执行。最终实现了无锁化编程。活该Muduo高性能、高并发。
提供的接口:
class EventLoop : noncopyable{
public:
typedef std::function<void()> Functor;
EventLoop();
~EventLoop(); // force out-line dtor, for std::unique_ptr members.
///
/// Loops forever.
///
/// Must be called in the same thread as creation of the object.
///
void loop();
/// Quits loop.
///
/// This is not 100% thread safe, if you call through a raw pointer,
/// better to call through shared_ptr for 100% safety.
void quit();
/// Runs callback immediately in the loop thread.
/// It wakes up the loop, and run the cb.
/// If in the same loop thread, cb is run within the function.
/// Safe to call from other threads.
void runInLoop(Functor cb);
/// Queues callback in the loop thread.
/// Runs after finish pooling.
/// Safe to call from other threads.
void queueInLoop(Functor cb);
size_t queueSize() const;
// timers
// ...
// internal usage
void wakeup();
void updateChannel(Channel* channel);
void removeChannel(Channel* channel);
// pid_t threadId() const { return threadId_; }
void assertInLoopThread()
bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }
// 获取当前线程的EventLoop
static EventLoop* getEventLoopOfCurrentThread();
private:
void abortNotInLoopThread();
void handleRead(); // waked up
void doPendingFunctors();
typedef std::vector<Channel*> ChannelList;
bool looping_; /* atomic */ // 在loop()中?
std::atomic<bool> quit_; // EventLoop是否退出
bool eventHandling_; /* atomic */ // 正在处理事件?
bool callingPendingFunctors_; /* atomic */ // 正在处理任务回调?
int64_t iteration_; // loop()循环的次数
const pid_t threadId_; // EventLoop和哪个线程绑定?
Timestamp pollReturnTime_; // epoll_wait返回时的时间戳
std::unique_ptr<Poller> poller_; // epoll/poll
std::unique_ptr<TimerQueue> timerQueue_; // 定时器
int wakeupFd_; // tickleFd,用于手动唤醒epoll_wait,以便即使处理任务
// unlike in TimerQueue, which is an internal class,
// we don't expose Channel to client.
std::unique_ptr<Channel> wakeupChannel_; // 管理tickleFd的channel
boost::any context_; // 待定
// scratch variables
ChannelList activeChannels_; // poller_收集到的fd(channel)事件
Channel* currentActiveChannel_; // loop()正在处理的channel
mutable MutexLock mutex_; // 只对pendingFunctors_提供保护
std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_); // 任务队列
};
Muduo在EventLoop中,使用了Linux系统中EventFd,作为wakeupChannel_的成员。这里主要为了将线程即时唤醒处理回调任务。如果你阅读过sylar的源码应该在这里会有所感知,EventLoop::wakeup()
函数其实作用和sylar中的IOManager::tickle()
类似。
此外,为了和Muduo的EventLoop适配,Muduo定时器的实现也是利用Linux上提供的TimeFd,在TimerQueue构造函数中,也会为该fd构造一个Channel,并将该Channel注册到EventLoop的Poller中,这样极大的方便了定时器的管理与维护。
简单画了一下EventLoop中核心函数EventLoop::loop()
的执行流程图:
实现的伪代码:
// 线程全局变量,for将线程和EventLoop绑定
__thread EventLoop* t_loopInThisThread = 0;
EventLoop::EventLoop()
: // ...
threadId_(CurrentThread::tid()),
poller_(Poller::newDefaultPoller(this)), // epoll
timerQueue_(new TimerQueue(this)), // Muduo定时器采用的是timerfd的接口,TimerQueue对象内部也构造了对应的channel,在构造时,会向EventLopp的Poller中注册该fd。
wakeupFd_(createEventfd()), // 创建一个非阻塞eventfd,作为tickle
wakeupChannel_(new Channel(this, wakeupFd_)){ // 将eventfd封装成channel
LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;
if (t_loopInThisThread){
LOG_FATAL << "Another EventLoop " << t_loopInThisThread
<< " exists in this thread " << threadId_;
}else{
t_loopInThisThread = this; // 设置线程局部变量
}
// 为eventfd设置读回调
wakeupChannel_->setReadCallback(
std::bind(&EventLoop::handleRead, this));
// we are always reading the wakeupfd
wakeupChannel_->enableReading();
}
EventLoop::~EventLoop(){
// 善后
LOG_DEBUG << "EventLoop " << this << " of thread " << threadId_
<< " destructs in thread " << CurrentThread::tid();
wakeupChannel_->disableAll();
wakeupChannel_->remove();
::close(wakeupFd_);
t_loopInThisThread = NULL;
}
void EventLoop::loop(){
assert(!looping_);
assertInLoopThread(); // 在自己的线程中?
looping_ = true;
quit_ = false; // FIXME: what if someone calls quit() before loop() ?
LOG_TRACE << "EventLoop " << this << " start looping";
while (!quit_){
activeChannels_.clear();
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); // epoll_wait
++iteration_;
if (Logger::logLevel() <= Logger::TRACE){
printActiveChannels();
}
// TODO sort channel by priority
eventHandling_ = true; // 开始处理每一个fd被触发的事件
for (Channel* channel : activeChannels_){
currentActiveChannel_ = channel;
currentActiveChannel_->handleEvent(pollReturnTime_); // 利用channel处理事件
}
currentActiveChannel_ = NULL;
eventHandling_ = false; // 所有fd的事件被处理完
doPendingFunctors(); // 处理任务回调
}
LOG_TRACE << "EventLoop " << this << " stop looping";
looping_ = false;
}
void EventLoop::quit(){
quit_ = true;
// 有线程安全问题
// There is a chance that loop() just executes while(!quit_) and exits,
// then EventLoop destructs, then we are accessing an invalid object.
// Can be fixed using mutex_ in both places.
if (!isInLoopThread()){ // 其他线程调用quit,不确定线程是否是Active的,需要调用wakeup();
wakeup();
} // 在处理事件或者任务回调时,内部自己调用了quit,说名此时线程是Active的,不用去唤醒
}
void EventLoop::runInLoop(Functor cb){
if (isInLoopThread()) {// 线程自己调用runInLoop,本来就是Active,直接顺手处理
cb();
}else{ // 其他线程调用runInLoop,需要加锁
queueInLoop(std::move(cb));
}
}
void EventLoop::queueInLoop(Functor cb){
{
MutexLockGuard lock(mutex_);
pendingFunctors_.push_back(std::move(cb)); // 任务放入任务队列
}
if (!isInLoopThread() || callingPendingFunctors_){
// 其他线程调用queueInLoop || 线程在处理任务队列中的任务时调用了queueInLoop
//以便让线程不要阻塞在epoll_wait上从而及时处理任务回调。
wakeup();
}
}
void EventLoop::updateChannel(Channel* channel){
assert(channel->ownerLoop() == this);
assertInLoopThread();
poller_->updateChannel(channel);
}
void EventLoop::removeChannel(Channel* channel){
assert(channel->ownerLoop() == this);
assertInLoopThread();
if (eventHandling_){
// channel只有自己能remove自己,其他的channel禁止删除并非自己的channel
assert(currentActiveChannel_ == channel ||
std::find(activeChannels_.begin(), activeChannels_.end(), channel) == activeChannels_.end());
}
poller_->removeChannel(channel);
}
// 唤醒线程
void EventLoop::wakeup(){
uint64_t one = 1;
// tickle一下
ssize_t n = sockets::write(wakeupFd_, &one, sizeof one);
if (n != sizeof one){
LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
}
}
void EventLoop::handleRead(){
uint64_t one = 1;
// tickle 回调处理
ssize_t n = sockets::read(wakeupFd_, &one, sizeof one);
if (n != sizeof one){
LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8";
}
}
void EventLoop::doPendingFunctors(){
std::vector<Functor> functors;
callingPendingFunctors_ = true;
// 写时置换,同异步日志,即减少持锁时间,又减少死锁可能。
{
MutexLockGuard lock(mutex_);
functors.swap(pendingFunctors_);
}
for (const Functor& functor : functors){
functor();
}
callingPendingFunctors_ = false;
}
在EventLoop接口的成员变量的定义顺序中,poller_的定义位于timerQueue_之上,这个定义的顺序很关键,首先在EventLoop在构造时,先按成员变量的定义顺序构造成员变量,再会构造EventLoop本身。在析构的时候,会先析构自身,再会去按成员变量的定义顺序的倒序,去析构成员变量。考虑到timerQueue_的析构是依赖poller_的,Muduo的定义顺序(先析构timerQueue_,再析构poller_),正好规避了这个问题。
简单讲,EventLoop作用就是让EventLoop::loop()跑在线程上。
EventLoopThread的实现代码是自解释的,代码量很少,也很容易理解。
提供的接口:
class EventLoopThread : noncopyable{
public:
typedef std::function<void(EventLoop*)> ThreadInitCallback;
EventLoopThread(const ThreadInitCallback& cb = ThreadInitCallback(),
const string& name = string());
~EventLoopThread();
EventLoop* startLoop();
private:
void threadFunc();
EventLoop* loop_ GUARDED_BY(mutex_); // 依赖EventLoop
bool exiting_; // 线程是否living
Thread thread_; // 线程实体
MutexLock mutex_;
Condition cond_ GUARDED_BY(mutex_);
ThreadInitCallback callback_;
};
有了EventLoop和EventLoopThread后,结合muduo源码阅读笔记(6、ExevntLoop和Thread)一个EventLoopThread线程启动流程如下:
EventLoopThread::startLoop() ->
Thread::start() ->
pthread_create(…, &detail::startThread,…) ->
startThread(void* obj) ->
ThreadData::runInThread() ->
Thread::func_() ->
EventLoopThread::threadFunc() ->
EventLoop::loop()
实现的伪代码:
EventLoopThread::EventLoopThread(const ThreadInitCallback& cb,
const string& name)
: loop_(NULL),
exiting_(false),
thread_(std::bind(&EventLoopThread::threadFunc, this), name),
mutex_(),
cond_(mutex_),
callback_(cb){
}
EventLoopThread::~EventLoopThread(){
exiting_ = true;
if (loop_ != NULL){ // not 100% race-free, eg. threadFunc could be running callback_.
// still a tiny chance to call destructed object, if threadFunc exits just now.
// but when EventLoopThread destructs, usually programming is exiting anyway.
loop_->quit();
thread_.join();
}
}
EventLoop* EventLoopThread::startLoop(){
assert(!thread_.started());
thread_.start();
EventLoop* loop = NULL;
{
MutexLockGuard lock(mutex_);
while (loop_ == NULL){ // 解决条件变量惊群效应/虚唤醒
cond_.wait();
}
loop = loop_;
}
return loop;
}
void EventLoopThread::threadFunc(){
EventLoop loop;
if (callback_){
callback_(&loop);
}
{
MutexLockGuard lock(mutex_);
loop_ = &loop;
cond_.notify();
}
loop.loop();
//assert(exiting_);
MutexLockGuard lock(mutex_);
loop_ = NULL;
}
在Muduo网络库的设计中,EventLoop 统一使用文件描述符(file descriptor)的方式来处理事件,主要是基于以下一些好处和设计原则:
一致性: 使用文件描述符作为事件的抽象,使得对于不同类型的事件(包括套接字、定时器等)的处理方式一致。这种一致性简化了 EventLoop 内部的设计和实现,使得对于事件的处理更加通用。
多路复用: 文件描述符是多路复用(Multiplexing)机制的核心。通过将多个文件描述符注册到同一个 EventLoop 中,可以使用诸如 select、poll、epoll 等多路复用技术,实现同时监听多个事件并进行有效的事件分发。
高效性: 文件描述符的处理在操作系统层面已经高度优化,使用多路复用机制可以高效地管理和调度大量的事件。这对于实现高性能的网络库尤为重要。
如果读者有阅读其他网络库源码就会不可思议的发现,Muduo的One Loop Per Thread设计思想太精妙了,这种设计几乎不存在锁的竞争!!!
本章完结