muduo学习笔记:net部分之EventLoopThread和EventThreadPool

本文详细介绍了Muduo库中EventLoopThread和EventLoopThreadPool的实现原理。EventLoopThread封装了一个EventLoop并在线程中执行,而EventLoopThreadPool则是一个管理多个EventLoop的线程池,用于多线程TCP服务端的IO事件处理。线程池采用轮询或固定哈希策略分配EventLoop,确保多线程环境下的高效运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前文【muduo学习笔记:net部分之EventLoop】介绍了EventLoop的原理实现和基本使用。任何一个创建并运行了EventLoop的线程就是IO线程,在任何线程中都可以创建运行EventLoop。在一个程序中,可能不止一个IO线程,IO线程也比一定是主线程。为方便多线程使用,EventloopThread类封装了IO线程,该类创建了一个线程,并在线程函数中创建了一个EventLoop对象并执行事件循环。

EventThreadPool是封装了多个EventLoop的线程池,主要用在多线程TCP服务端。在单线程TCP服务端中,EventLoop既用于接受新的连接、也用于执行IO。而多线程中TCP服务端创建的EventLoop仅用于接受新的TCP连接,IO事件处理全部交由其他EventLoop执行,IO事件处理的分派策略后续介绍。

1、EventLoopThread

实现简单,封装了一个EventLoop,提供接口开始执行事件循环、获取创建的EventLoop对象。

1.1、代码实现

(1)构造初始化、析构

初始化成员变量,创建一个线程用于执行事件循环。

EventLoopThread::EventLoopThread(const ThreadInitCallback& cb, 
								 const string& name)
  : loop_(NULL),   		// 指向当前线程创建的EventLoop对象
    exiting_(false),   	// 退出线程标志位
    thread_(std::bind(&EventLoopThread::threadFunc, this), name), // 线程
    mutex_(),		// 互斥锁
    cond_(mutex_),	// 用于保证初始化成功
    callback_(cb)   // 线程初始化的回调函数
{
}

// 析构,退出事件循环,关闭线程
EventLoopThread::~EventLoopThread()
{
  exiting_ = true;
  if (loop_ != NULL) {
    loop_->quit();
    thread_.join();
  }
}

(2)启动事件循环

启动线程,执行线程函数,阻塞等到线程函数通知,最后返回创建的EvnetLoop对象指针。

EventLoop* EventLoopThread::startLoop()
{
  assert(!thread_.started());
  thread_.start();			// 启动线程,执行线程函数

  EventLoop* loop = NULL;
  {
    MutexLockGuard lock(mutex_);
    while (loop_ == NULL){
      cond_.wait();   // 保证线程函数准备工作完成,EventLoop指针对象不为空
    }
    loop = loop_;
  }

  return loop;
}

(3)线程函数

void EventLoopThread::threadFunc()
{
  EventLoop loop;  // 线程中创建一个EventLoop对象

  if (callback_){
    callback_(&loop);  // 执行初始化回调函数
  }

  {
    MutexLockGuard lock(mutex_);
    loop_ = &loop;
    cond_.notify();  // 初始化成功,通知用户启动成功
  }

  loop.loop();		// 线程中执行事件循环
  //assert(exiting_);
  MutexLockGuard lock(mutex_);
  loop_ = NULL;
}

1.2、测试

void print(EventLoop* p = NULL)
{
  printf("print: pid = %d, tid = %d, loop = %p\n", getpid(), CurrentThread::tid(), p);
}

void quit(EventLoop* p)
{
  print(p);
  p->quit();
}

int main()
{
  print();

  {
    EventLoopThread thr1;  // never start
  }

  {
    // dtor calls quit()
    EventLoopThread thr2;
    EventLoop* loop = thr2.startLoop();
    loop->runInLoop(std::bind(print, loop));
    CurrentThread::sleepUsec(500 * 1000);
  }

  {
    // quit() before dtor
    EventLoopThread thr3;
    EventLoop* loop = thr3.startLoop();
    loop->runInLoop(std::bind(quit, loop));
    CurrentThread::sleepUsec(500 * 1000);
  }
}

在主线程中创建三个ThreadLoopThread,但仅启动了后两个。

print: pid = 1588, tid = 1588, loop = (nil)
print: pid = 1588, tid = 1589, loop = 0x7fbcc527f9c0
print: pid = 1588, tid = 1590, loop = 0x7fbcc527f9c0

2、EventLoopThreadPool

EventThreadPool是封装了多个EventLoop的线程池,管理所有客户端上的IO事件,每个线程都有唯一一个事件循环。主线程的EventLoop负责新的客户端连接,线程池中的EventLoop负责客户端的IO事件,客户端IO事件的分配按照一定规则执行。

当线程池中的线程数量为0时,那么客户端共用服务端的EventLoop,退化成单线程模式了。

2.1、代码实现

头文件如下

class EventLoopThreadPool : noncopyable
{
 public:
  typedef std::function<void(EventLoop*)> ThreadInitCallback;

  EventLoopThreadPool(EventLoop* baseLoop, const string& nameArg);
  ~EventLoopThreadPool();

  // 设置线程池数量
  void setThreadNum(int numThreads) { numThreads_ = numThreads; }

  // 启动线程池
  void start(const ThreadInitCallback& cb = ThreadInitCallback());

  // 启动后有效,使用round-robin策略获取线程池中的EventLoop对象
  EventLoop* getNextLoop();  

  // 使用固定的hash方法,获取相同的线程对象
  EventLoop* getLoopForHash(size_t hashCode);

  std::vector<EventLoop*> getAllLoops();  // 返回所有EvenetLoop,含baseloop

  bool started() const { return started_; }

  const string& name() const { return name_; }

 private:

  EventLoop* baseLoop_;		// Acceptor所属EventLoop
  string name_;				// 线程池名称
  bool started_;			// 是否已经启动
  int numThreads_;			// 线程数
  int next_;				// 新连接到来,所选择的EventLoop对象下标
  std::vector<std::unique_ptr<EventLoopThread>> threads_; // IO线程列表
  std::vector<EventLoop*> loops_;	// EventLoop列表
};

实现如下

EventLoopThreadPool::EventLoopThreadPool(EventLoop* baseLoop, const string& nameArg)
  : baseLoop_(baseLoop),
    name_(nameArg),
    started_(false),
    numThreads_(0),
    next_(0)
{
}

EventLoopThreadPool::~EventLoopThreadPool()
{
  // Don't delete loop, it's stack variable
}

void EventLoopThreadPool::start(const ThreadInitCallback& cb)
{
  assert(!started_);
  baseLoop_->assertInLoopThread();

  started_ = true;

  for (int i = 0; i < numThreads_; ++i){
    char buf[name_.size() + 32];
    snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);
    // 启动EventLoopThread线程。在进入事件循环之前。会调用cb
    EventLoopThread* t = new EventLoopThread(cb, buf);
    threads_.push_back(std::unique_ptr<EventLoopThread>(t));
    loops_.push_back(t->startLoop());
  }
  if (numThreads_ == 0 && cb){
    cb(baseLoop_); // 仅仅有一个EventLoop。在这个EventLoop进入事件循环之前,调用cb
  }
}

EventLoop* EventLoopThreadPool::getNextLoop()
{
  baseLoop_->assertInLoopThread();
  assert(started_);
  EventLoop* loop = baseLoop_;// 假设loops_为空,则loop指向baseLoop_
  
  if (!loops_.empty()){  // 假设不为空,依照round-robin的调度方式选择一个EventLoop
    // round-robin
    loop = loops_[next_];
    ++next_;
    if (implicit_cast<size_t>(next_) >= loops_.size()) {
      next_ = 0;
    }
  }
  return loop;
}

EventLoop* EventLoopThreadPool::getLoopForHash(size_t hashCode)
{
  baseLoop_->assertInLoopThread();
  EventLoop* loop = baseLoop_;

  if (!loops_.empty()){
    loop = loops_[hashCode % loops_.size()];
  }
  return loop;
}

std::vector<EventLoop*> EventLoopThreadPool::getAllLoops()
{
  baseLoop_->assertInLoopThread();
  assert(started_);
  if (loops_.empty()){
    return std::vector<EventLoop*>(1, baseLoop_);
  }
  else{
    return loops_;
  }
}

2.2、测试

测试代码如下:

void print(EventLoop* p = NULL)
{
  printf("main(): pid = %d, tid = %d, loop = %p\n", getpid(), CurrentThread::tid(), p);
}

void init(EventLoop* p)
{
  printf("init(): pid = %d, tid = %d, loop = %p\n", getpid(), CurrentThread::tid(), p);
}

int main()
{
  print();

  EventLoop loop;
  loop.runAfter(11, std::bind(&EventLoop::quit, &loop));

  {
    printf("Single thread %p:\n", &loop);
    EventLoopThreadPool model(&loop, "single");
    model.setThreadNum(0);
    model.start(init);
    assert(model.getNextLoop() == &loop);
    assert(model.getNextLoop() == &loop);
    assert(model.getNextLoop() == &loop);
  }

  {
    printf("Another thread:\n");
    EventLoopThreadPool model(&loop, "another");
    model.setThreadNum(1);
    model.start(init);
    EventLoop* nextLoop = model.getNextLoop();
    nextLoop->runAfter(2, std::bind(print, nextLoop));
    assert(nextLoop != &loop);
    assert(nextLoop == model.getNextLoop());
    assert(nextLoop == model.getNextLoop());
    ::sleep(3);
  }

  {
    printf("Three threads:\n");
    EventLoopThreadPool model(&loop, "three");
    model.setThreadNum(3);
    model.start(init);
    EventLoop* nextLoop = model.getNextLoop();
    nextLoop->runInLoop(std::bind(print, nextLoop));
    assert(nextLoop != &loop);
    assert(nextLoop != model.getNextLoop());
    assert(nextLoop != model.getNextLoop());
    assert(nextLoop == model.getNextLoop());
  }

  loop.loop();
}

运行结果

main(): pid = 2228, tid = 2228, loop = (nil)
Single thread 0x7fffc96d04a0:
init(): pid = 2228, tid = 2228, loop = 0x7fffc96d04a0
Another thread:
init(): pid = 2228, tid = 2229, loop = 0x7fa4fc87f9c0
main(): pid = 2228, tid = 2229, loop = 0x7fa4fc87f9c0
Three threads:
init(): pid = 2228, tid = 2271, loop = 0x7fa4fc87f9c0
init(): pid = 2228, tid = 2272, loop = 0x7fa4f7fef9c0
init(): pid = 2228, tid = 2273, loop = 0x7fa4f77df9c0
main(): pid = 2228, tid = 2271, loop = 0x7fa4fc87f9c0
#include <muduo/net/TcpServer.h> #include <muduo/base/Logging.h> #include <boost/bind.hpp> #include <muduo/net/EventLoop.h> // 使用muduo开发回显服务器 class EchoServer { public: EchoServer(muduo::net::EventLoop* loop, const muduo::net::InetAddress& listenAddr); void start(); private: void onConnection(const muduo::net::TcpConnectionPtr& conn); void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp time); muduo::net::TcpServer server_; }; EchoServer::EchoServer(muduo::net::EventLoop* loop, const muduo::net::InetAddress& listenAddr) : server_(loop, listenAddr, "EchoServer") { server_.setConnectionCallback( boost::bind(&EchoServer::onConnection, this, _1)); server_.setMessageCallback( boost::bind(&EchoServer::onMessage, this, _1, _2, _3)); } void EchoServer::start() { server_.start(); } void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn) { LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> " << conn->localAddress().toIpPort() << " is " << (conn->connected() ? "UP" : "DOWN"); } void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp time) { // 接收到所有的消息,然后回显 muduo::string msg(buf->retrieveAllAsString()); LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, " << "data received at " << time.toString(); conn->send(msg); } int main() { LOG_INFO << "pid = " << getpid(); muduo::net::EventLoop loop; muduo::net::InetAddress listenAddr(8888); EchoServer server(&loop, listenAddr); server.start(); loop.loop(); } 这个代码应该怎么改?
最新发布
07-07
<think>嗯,用户遇到了Boost库中bind函数调用时的问题,特别是在使用muduo库的TCPConnection处理BufferTimestamp时出现错误。我需要逐步分析可能的原因,并给出解决方案。 首先,用户提到错误涉及`boost::bind`的`call_impl`,这可能参数传递或绑定对象的生命周期有关。常见问题包括参数类型不匹配、对象提前析构导致悬空指针,或者多线程环境下的竞争条件。 接着,考虑muduo库中的TCPConnection。该库使用Reactor模式,依赖非阻塞I/O回调机制。当使用`boost::bind`绑定成员函数时,必须确保对象在回调时仍然存在。如果TCPConnection被销毁,但回调仍被调用,就会访问无效内存,导致未定义行为。 检查用户代码中的`boost::bind`调用是否正确传递了参数。例如,绑定成员函数时,第一个参数应为对象实例的指针或引用。如果使用智能指针管理TCPConnection,可能需要使用`shared_from_this`来延长生命周期,避免对象在回调前被销毁。 另外,BufferTimestamp的处理可能涉及线程安全问题。muduo库通常在每个IO线程处理事件,但如果在多个线程中修改Buffer,需要确保适当的同步机制,如使用MutexLockCondition进行保护。 还要检查时间戳的处理是否正确。例如,传递时间戳时是否使用了正确的类型(如`muduo::Timestamp`),是否存在拷贝或转换错误。如果回调函数期望的是引用,但传递的是临时对象,可能导致悬空引用。 最后,建议用户使用Valgrind或AddressSanitizer检测内存问题,或者在调试模式下运行程序,观察错误发生的具体位置。同时,查看编译器的错误信息,确定`call_impl`的具体参数类型传递方式,确保所有参数在绑定时的类型匹配。 总结可能的解决步骤:1. 确保TCPConnection对象在回调期间有效;2. 检查`boost::bind`的参数类型数量;3. 使用智能指针管理对象生命周期;4. 验证线程安全同步机制;5. 调试工具辅助定位问题。</think>### 关于`boost::bind`调用问题的分析及解决方案 #### 1. 问题背景常见原因 在muduo网络库中使用`boost::bind`绑定`TcpConnection`的回调函数时,若涉及`Buffer`或`Timestamp`参数,可能因以下原因报错: - **参数类型不匹配**:`boost::bind`绑定的参数类型目标函数声明不一致[^4] - **对象生命周期问题**:`TcpConnection`对象可能已销毁,但回调仍在尝试访问其成员(悬空指针问题) - **线程安全问题**:多个线程访问共享资源(如`Buffer`)时未正确同步[^3][^4] #### 2. 典型错误场景分析 ```cpp // 错误示例:绑定临时对象 void TcpConnection::handleRead(const muduo::Timestamp& receiveTime) { boost::bind(&TcpConnection::processBuffer, this, _1, receiveTime); } ``` 此处若`receiveTime`是临时对象,可能导致时间戳数据失效。正确做法应传递**值类型**而非引用: ```cpp // 正确示例:按值传递时间戳 boost::bind(&TcpConnection::processBuffer, this, _1, receiveTime.clone()); ``` #### 3. 关键解决步骤 1. **验证对象生命周期** - 使用`shared_from_this()`管理`TcpConnection`对象[^5] ```cpp class TcpConnection : public std::enable_shared_from_this<TcpConnection> { void send(const Buffer& buf) { boost::bind(&TcpConnection::doSend, shared_from_this(), buf); } }; ``` 2. **检查参数类型一致性** ```cpp // 目标函数声明 void processBuffer(Buffer* buf, muduo::Timestamp ts); // 绑定时必须严格匹配参数类型 boost::bind(&TcpConnection::processBuffer, shared_from_this(), boost::placeholders::_1, receiveTime.timestamp()); ``` 3. **线程同步处理** ```cpp // 使用MutexLock保护Buffer操作 void TcpConnection::onMessage(const Buffer& inputBuffer) { MutexLockGuard lock(mutex_); processBuffer(&inputBuffer, getTimestamp()); } ``` #### 4. 调试建议 - 使用`gdb`观察调用栈,确认`this`指针有效性 - 通过`Valgrind`检测内存访问越界问题 - 启用Boost调试模式:`#define BOOST_BIND_ENABLE_STDCALL`
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

aworkholic

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

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

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

打赏作者

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

抵扣说明:

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

余额充值