29.muduo学习笔记之net_EventLoop.{h&cc}

本文详细介绍了EventLoop类的实现原理,包括其核心组件Channel、Poller、TimerQueue等的使用,以及如何通过EventLoop实现线程间的通信和事件处理。

1. 说明

  1. 一个EventLoop类,任何一个线程,只要创建并运行了EventLoop,都称之为IO线程(为了方便使用,定义了EventLoopThread类,封装了IO线程)
  2. 使用了Channel,Poller,TimerQueue,Timestamp类
  3. noncopyable

2. 变量

  1. 类型定义

    • Functor
    • ChannelList
  2. looping_; /* atomic */

    • bool类型,表示是否处于loop()函数执行中,它的值变化都在loop()函数中
  3. std::atomic quit_;

    • bool类型,判断是否退出,loop()中循环的结束条件
  4. bool eventHandling_; /* atomic */

    • 表示是否处于处理事件事件中,在loop()中poll()选择需要处理的通道(事件)后,在对这些事件的处理过程中把这个变量置为true
  5. bool callingPendingFunctors_; /* atomic */

    • 是否处于doPendingFunctors()函数中
  6. int64_t iteration_;

    • 在构造函数中赋初值为0,以后只在loop()中有用到,即每次poll()后+1,应该是记录poll()的次数吧
  7. const pid_t threadId_

    • 当前对象所属线程id
  8. Timestamp pollReturnTime_;

    • 调用poll(),返回时间戳
  9. std::unique_ptr poller_;

    • 每个EventLoop都会有一个Poller,
  10. std::unique_ptr timerQueue_;

    • 每个EventLoop都有个TimerQueue,定时器列表,作为定时器
  11. int wakeupFd_;

    • 用于eventfd,在构造函数中用createEventfd()生成eventfd赋值,作为线程唤醒需要使用的fd
    • 进程间通信有几种方式:pipe,socket,eventfd,线程间通信还有条件变量等
  12. std::unique_ptr wakeupChannel_;

    • 该通道将会纳入poller_来管理,用来唤醒当前线程,这是一个内部channel,不会暴露给用户
  13. boost::any context_;

    • boost::any,任意数据类型,C++17也加入C++标准
  14. ChannelList activeChannels_;

    • Poller返回的活动通道
  15. Channel* currentActiveChannel_;

    • 当前正在处理的活动通道
  16. mutable MutexLock mutex_;

    • 锁,没什么说的
  17. std::vector pendingFunctors_ GUARDED_BY(mutex_)

    • Functor的队列,这个就是需要处理的functor队列,在doPendingFunctors()函数中处理完就清空了,要理解需要结合runInLoop(),queueInLoop()等函数

3. 函数

1. 私有

  1. void abortNotInLoopThread();

    • 打印日志并退出,当前线程和此对象线程不一样,在assertInLoopThread()中调用
  2. void handleRead(); // waked up

    • 唤醒处理函数,调用read,里面的内容没有什么意义
  3. void doPendingFunctors();

    • 不是简单的在临界区内依次调用Functor,而是把回调列表swap到functors中,这样一方面减小了临界区的长度(意味着不会阻塞其他线程的queueInLoop()),另一方面也避免了死锁(因为Functor可能再次调用queueInLoop())
    • 把pendingFunctors_交换到新的局部空间,然后逐个调用这些函数
  4. void printActiveChannels() const;

    • 把活动的channel信息打印到日志

2. 共有

  1. 构造

    • 变量的初始化,设置当前channel的读回调函数为私有函数handleRead
  2. 析构

    • 释放资源
  3. void loop()

    • 循环调用poller_->poll(),返回需要处理的通道channel,处理这些通道的回调函数
    • 最后doPendingFunctors(),处理其他线程或者当前线程异步增加的需要处理的函数,详见下面的runInLoop()和queueInLoop(),这样让IO线程也能处理一些计算任务
  4. void quit()

    • 把quit_置为true,如果不是由当前IO线程调用的,要先用wakeup()唤醒当前线程
  5. Timestamp pollReturnTime()

    • 返回pollReturnTime_
  6. int64_t iteration()

    • 返回iteration_
  7. void runInLoop(Functor cb)

    • 在IO线程中执行某个回调函数,该函数可以跨线程调用
    • 如果在当前IO线程调用runInLoop.则同步调用cb()回调函数
    • else 如果在其他线程调用runInLoop.则调用queueInLoop(cb)异步地将cb添加到队列
  8. void queueInLoop(Functor cb)

    • 如果调用当前行数的线程不是当前IO线程,则需要唤醒,或者是当前线程,但此时正在调用pendingfunctor,也需要唤醒
  9. size_t queueSize()

    • 返回pendingFunctors_.size()
  10. TimerId runAt(Timestamp time, TimerCallback cb)

    • 就是把cb()加入定时器队列,在time时间执行
  11. TimerId runAfter(double delay, TimerCallback cb)

    • 把cd()加入定时器队列,当前时间+delay后执行
  12. TimerId runEvery(double interval, TimerCallback cb)

    • 把cb()加入定时器队列,每隔interval间隔时间执行一次
  13. void cancel(TimerId timerId)

    • 调用poller_->cancel(timerId),结束这个定时器任务
  14. void wakeup()

    • 唤醒当前对象线程,实际是调用write(),这样loop()函数中的poll()就会选到当前线程,然后执行
  15. void updateChannel(Channel* channel);

    • 断言当前对象线程正在执行,调用poller_->updateChannel(channel);
  16. void removeChannel(Channel* channel);

    • 保证能找到这个channel,调用poller_->removeChannel(channel);移除这个channel
  17. bool hasChannel(Channel* channel)

    • 调用poller_->hasChannel(channel);
  18. void assertInLoopThread()

    • 如果当前线程不是本对象线程,就调用abortNotInLoopThread(),打印日志并退出
  19. bool isInLoopThread()

    • 就是判断threadId_和当前正在运行的线程id是否相同
  20. bool eventHandling()

    • 返回eventHandling_,看是否处于loop()处理事件过程中
  21. void setContext(const boost::any& context)

    • 如名
  22. const boost::any& getContext()

    • 如名
  23. boost::any* getMutableContext()

    • 如名
  24. static EventLoop* getEventLoopOfCurrentThread()

    • 静态函数,返回t_loopInThisThread,即当前EventLoop对象

4. 全局

  1. t_loopInThisThread

    • 线程局部变量,在构造函数中赋值为当前对象
  2. kPollTimeMs

    • 在poll()中当参数传过去
  3. createEventfd()

    • 调用eventfd()生成eventfd返回
  4. IgnoreSigPipe类

    • 构造函数中调用signal()忽略SIGPIPE信号
    • 并且全局已经定义这个类了:initObj变量,也就是已经调用这个signal()了
### 编译时出现`undefined reference to muduo::net::EventLoop::~EventLoop()`的原因分析 当遇到此类链接错误时,通常是因为链接器无法找到特定符号的定义。对于`muduo::net::EventLoop::~EventLoop()`这类错误,可能原因有多个方面: - **库文件缺失或不匹配**:如果使用的静态库(`.a`)版本与源码期望的不同,则可能导致某些成员函数未能正确编译进入最终二进制文件中[^2]。 - **ABI兼容性问题**:使用不同GCC版本编译可能会引入ABI(应用程序二进制接口)差异,特别是启用C++11特性之后。这可以通过设置预处理器宏来缓解,即添加`-D_GLIBCXX_USE_CXX11_ABI=0`作为编译选项之一[^3]。 - **链接顺序不当**:在命令行指定依赖关系时,应确保先列出目标对象(.o),再跟随其所需的所有外部库;否则即使存在相应实现也可能因解析时机不对而被认为“未定义”。此外,在多库场景下,还需注意各库间的相互依存次序[^4]。 针对上述情况的具体解决方案可以考虑以下几个方向: #### 修改Makefile或CMakeLists.txt配置 为了使项目能够顺利构建并运行,建议按照以下方式调整构建脚本中的相关内容: ```makefile # Makefile 示例 g++ -std=c++11 -I/path/to/muduo/include \ -L/path/to/muduo/lib \ -Wl,-rpath,/path/to/muduo/lib \ main.o -lmuduo_net_cpp11 -lmuduo_base_cpp11 -lpthread -ldl ``` 或者如果是基于CMake的话, ```cmake # CMakeLists.txt 示例 set(CMAKE_CXX_STANDARD 11) add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) find_library(MUDUO_BASE_LIB NAMES muduo_base_cpp11 PATHS /path/to/muduo/lib NO_DEFAULT_PATH) find_library(MUDUO_NET_LIB NAMES muduo_net_cpp11 PATHS /path/to/muduo/lib NO_DEFAULT_PATH) target_link_libraries(${PROJECT_NAME} PRIVATE ${MUDUO_NET_LIB} ${MUDUO_BASE_LIB}) ``` 以上改动旨在确保采用支持C++11特性的库变体,并且通过适当设定路径让链接过程能找到所需的动态/静态库资源。 #### 验证第三方库的有效性和完整性 考虑到可能存在旧版遗留下来的残留物影响新安装的结果,推荐重新下载最新稳定发行包并依照官方文档说明完成本地部署工作。同时利用工具如`nm`检查所选`.a`档案内是否存在预期条目以确认无误后再继续后续操作。 #### 更新环境变量以便于开发测试阶段临时解决问题 有时出于快速验证目的可暂时修改LD_LIBRARY_PATH等环境变量指向正确的共享库位置,但这不是长久之计,正式发布前仍需完善工程化管理流程确保打包产物的一致性。 ```bash export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值