moduo网络库的reactor模式(上)

本文介绍了moduo网络库的reactor模式,包括线程同步(互斥器和条件变量)、线程封装、事件循环以及测试部分。重点讨论了如何使用互斥器和条件变量进行线程同步,并简述了事件循环的实现和测试情况。

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

moduo网络库的reactor模式

moduo网络库的reactor模式基本构成为“non-blocking I/O + I/O multiplexing”,程序的基本结构是一个事件循环(event loop),以事件驱动(event-driven)和事件回调(event callback)的方式实现业务逻辑。此文参照陈硕的moduo网络库稍作简化,抽出reactor模式的基本框架。 

1、线程同步:采用底层同步原语,互斥器+条件变量

线程同步尽量使用高级并发编程构建。若要使用底层同步原语,则尽量只使用互斥器和条件变量。互斥器和条件变量构成了多线程编程的全部必备同步原语,用它们即可完成任何多线程同步任务。

(1)封装互斥器

封装互斥器保护对象共享资源,不同对象间不会构成锁争用。其中:

MutexLock封装临界区。用RAII手法封装互斥器的创建与摧毁。

MutexLockGuard封装临界区的进入和退出,即加锁和解锁。它是一个栈上对象,作用域刚好等于临界区域。

#ifndef MUTEXLOCKGUARD_H_
#define MUTEXLOCKGUARD_H_

#include <pthread.h>
#include "Thread.hpp"
#include <iostream>

class MutexLock //: boost::noncopyable
{
private:
    pthread_mutex_t mutex_;
    MutexLock(const MutexLock&);
    MutexLock& operator=(const MutexLock&);

public:
    MutexLock(){ pthread_mutex_init(&mutex_,NULL); }
    ~MutexLock(){ pthread_mutex_destroy(&mutex_); }
    void lock() { pthread_mutex_lock(&mutex_); }
    void unlock() { pthread_mutex_unlock(&mutex_); }
    pthread_mutex_t* getPthreadMutex() { return &mutex_; }
};

class MutexLockGuard //: boost::noncopyable
{
private:
    MutexLock mutex_; 
    MutexLockGuard(const MutexLockGuard&);
    MutexLockGuard& operator=(const MutexLockGuard&);

public:
    explicit MutexLockGuard( MutexLock& mutex ) : mutex_(mutex)
    {
        mutex_.lock();
        std::cout<<"tid "<<CurrentThreadtid()<<": MutexLockGuard lock!"<<std::endl;
    }
    ~MutexLockGuard()
    {
        mutex_.unlock();
        std::cout<<"tid "<<CurrentThreadtid()<<": MutexLockGuard unlock!"<<std::endl;
    }
};

#define MutexLockGuard(x) static_assert(false, "missing mutex guard var name")
//C++0x中引入了static_assert这个关键字,用来做编译期间的断言,因此叫做静态断言。
//如果第一个参数常量表达式的值为false,会产生一条编译错误,错误位置就是该static_assert语句所在行,第二个参数就是错误提示字符串。

#endif // MUTEXLOCKGUARD_H_

(2)封装条件变量

条件变量是非常底层的同步原语,用于线程同步。很少直接使用,一般只用于实现高层同步措施,如阻塞队列(blockingQueue<T>),倒计时(countDownLatch)等。需要注意:

在wait端,要与mutex一起使用以保护while中条件表达式的读写。用while而非if来等待条件表达式的成立是防止虚假唤醒(spurious wakeup)。

在signal/broadcast端,修改条件表达式需要用mutex保护。

另外,条件变量析构时要调用int pthread_cond_destroy(pthread_cond_t *cond);让系统自行回收资源,否则会造成内存泄漏。

#ifndef CONDITION_H_
#define CONDITION_H_

#include "MutexLockGuard.hpp"
#include <pthread.h>

class Condition
{
  public:
    explicit Condition(MutexLock& mutex) : mutex_(mutex)
    {
      pthread_cond_init(&pcond_, NULL);
    }

   ~Condition()
   {
      pthread_cond_destroy(&pcond_);
   }

   void wait()
   {
      pthread_cond_wait(&pcond_, mutex_.getPthreadMutex());
   }

   void notify()
   {
      pthread_cond_signal(&pcond_);
   }

   void notifyAll()
   {
      pthread_cond_broadcast(&pcond_);
   }

  private:
    MutexLock& mutex_;
    pthread_cond_t pcond_;
};

#endif 

2、封装线程thread

常规下创建一个新线程使用

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict_attr, void*(*start_rtn)(void*), void *restrict arg);

其中第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的地址,最后一个参数是运行函数的参数。

为进一步分离业务逻辑,将pthread_create中线程运行函数固定形式,为detail::startThread()。将实际需要运行的线程函数封装到一个资源类detail::ThreadData中成为一个数据成员std::function<void()> detail::ThreadData::func_。该资源类的一个具体实现detail::ThreadData* data作为线程函数的参数传入,并在此之前已通过接口Thread::Thread(std::function<void()>)将实际需要运行的线程函数传入并保存在数据成员std::function<void()> detail::ThreadData::func_中。

pthread_create成功创建一个新线程后,在线程函数detail::startThread()中通过传入的具体资源对象detail::ThreadData data*调用detail::ThreadData::runInThread(),最终调用实际需要运行的线程函数detail::ThreadData::func_()。

#ifndef THREAD_H_
#define THREAD_H_

#include <thread>
#include <memory>
#include <functional>
#include <string>
#include <unistd.h>
#include <iostream>
#include <sys/syscall.h>
//#define gettid() syscall(__NR_gettid)

pid_t gettid()
{
    return static_cast<pid_t>(syscall(SYS_gettid));
}
__thread pid_t t_cachedTid = 0;

pid_t CurrentThreadtid()
{
    if (t_cachedTid == 0)
    {
        t_cachedTid = gettid();
    }
    return t_cachedTid;
}

namespace detail
{

struct ThreadData
{
    typedef std::function<void ()> ThreadFunc;
    ThreadFunc func_;
    std::string name_;
    pid_t tid_;

    ThreadData(ThreadFunc func, const std::string& name, pid_t tid)
      : func_(std::move(func)),
        name_(name),
        tid_(tid)
    { }

    void runInThread()
    {
        tid_ = CurrentThreadtid();
        func_();
        name_=std::string("finished");
        std::cout<<"tid "<<CurrentThreadtid()<<": Thread::runInThread() end!"<<std::endl;
    }
};

void* startThread(void* obj)
{
    ThreadData* data = static_cast<ThreadData*>(obj);
    data->runInThread();
    delete data;
    std::cout<<"tid "<<CurrentThreadtid()<<": Thread end!"<<std::endl;
    return NULL;
}
 
}

class Thread
{
public:
    //typedef void* (*ThreadFunc)(void*);
    typedef std::function<void ()> ThreadFunc;
    Thread(ThreadFunc func) : func_(func), tidp_(0), tid_() {}
    void start()
    {
         detail::ThreadData* data = new detail::ThreadData(func_, name_, tid_);
         std::cout<<"tid "<<CurrentThreadtid()<<": create a new thread"<<std::endl;
         if(pthread_create(&tidp_, NULL, &detail::startThread, (void*)data))
         {
             delete data;
             std::cout<<"thread create error"<<std::endl;
         }
    }

private:
    ThreadFunc func_;
    std::string name_;
    pid_t tid_;
    pthread_t tidp_;
};

#endif

3、事件循环

Reactor模式基本构成为“non-blocking I/O + I/O multiplexing”,遵循“one loop per thread” 。程序的基本结构是一个事件循环(event loop),通过I/O多路复用器select、poll和epoll等实现。再以事件驱动(event-driven)和事件回调(event callback)的方式实现业务逻辑,此处暂时省略该方式,事件循环仅调用系统函数poll延时1s。

其中,EventLoop类实现单个线程内的事件循环主体框架,在此基础上再进一步封装为EventLoopThread类,实现多线程应用。

EventLoop类中:EventLoop::loop()为事件循环主体函数,其中EventLoop::isInLoopThread()检测该EventLoop对象是否运行在所创建的线程中,以遵循“one loop per thread”。

EventLoopThread类中:EventLoop* EventLoopThread::startLoop()为接口函数,其中void EventLoopThread::threadFunc()为线程函数,该函数即为传入上一节Thread类数据成员std::function<void()> detail::ThreadData::func_中的线程函数。在该线程函数中具现一个栈上对象EventLoop loop;进行事件循环loop.loop();。此外,接口函数startLoop()的返回指针即为该线程函数中的栈上对象loop,需要进行线程同步才能返回给用户使用,用以实现其他多线程业务如定时任务等。

#ifndef EVENT_LOOP_H_
#define EVENT_LOOP_H_

#include <thread>
#include <poll.h>
#include <unistd.h>
#include <iostream>
#include <sys/syscall.h>
#include "Thread.hpp"

class EventLoop{
public:
    EventLoop() : tid_(CurrentThreadtid()) {}
    bool isInLoopThread() const { return tid_==CurrentThreadtid(); }
    void loop();
private:
    pid_t tid_;
};

void EventLoop::loop()
{
    if( !isInLoopThread() ){
        std::cout<<"tid "<<CurrentThreadtid()<<": This EventLoop had been created!"<<std::endl;
    }else{
        std::cout<<"tid "<<CurrentThreadtid()<<": looping..."<<std::endl;
        poll(NULL, 0, 1*1000); //poll() just waiting for a second
    }
}

#endif

 

#ifndef EVENT_LOOP_THREAD_H_
#define EVENT_LOOP_THREAD_H_

#include "EventLoop.hpp"
#include "Thread.hpp"
#include "MutexLockGuard.hpp"
#include "Condition.hpp"
#include <memory>
#include <iostream>

class EventLoopThread
{
public:
  EventLoopThread() 
    : loop_(NULL), exiting_(false), thread_(std::bind(&EventLoopThread::ThreadFunc, this)), mutex_(), cond_(mutex_) {}
  //~EventLoopThread();
  EventLoop* startLoop();
  
private:
  void ThreadFunc();

  EventLoop* loop_; 
  bool exiting_;
  Thread thread_; 
  MutexLock mutex_;
  Condition cond_;
};

EventLoop* EventLoopThread::startLoop()
{
  //assert(!thread_.started());
  thread_.start();
  
  {
    MutexLockGuard lock(mutex_);
    while (loop_ == NULL)
    {
      std::cout<<"tid "<<CurrentThreadtid()<<": waiting"<<std::endl;
      cond_.wait();
    }
    std::cout<<"tid "<<CurrentThreadtid()<<": received notification"<<std::endl;
  }
  //sleep(3); //just for thread security
  return loop_;
}

void EventLoopThread::ThreadFunc()
{
    EventLoop loop;

    {
      MutexLockGuard lock(mutex_);
      loop_ = &loop;
      cond_.notify();
      std::cout<<"tid "<<CurrentThreadtid()<<": notified"<<std::endl;
    }

    loop.loop();
    //assert(exiting_);
}

#endif

4、测试

#include "EventLoopThread.hpp"
#include "EventLoop.hpp"
#include "Thread.hpp"
#include <iostream>

using namespace std;

int main()
{
  cout<<"Main: pid: "<<getpid()<<" tid: "<<CurrentThreadtid()<<endl;//main thread
  //sleep(1);

  EventLoopThread ELThread1;
  EventLoop* loop = ELThread1.startLoop();//thread 2
  sleep(1);

  loop->loop(); //test "one thread one loop"
  sleep(3);

  return 0;
}
baddy@ubuntu:~/Documents/Reactor/s0$ g++ -std=c++11 -pthread -o testEventLoopThreadDemo MutexLockGuard.hpp Condition.hpp Thread.hpp EventLoop.hpp EventLoopThread.hpp testEventLoopThread.cpp 
baddy@ubuntu:~/Documents/Reactor/s0$ /usr/bin/valgrind ./testEventLoopThreadDemo 
==16751== Memcheck, a memory error detector
==16751== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==16751== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==16751== Command: ./testEventLoopThreadDemo
==16751== 
Main: pid: 16751 tid: 16751
tid 16751: create a new thread
tid 16751: MutexLockGuard lock!
tid 16751: waiting
tid 16752: Thread::func_() started!
tid 16752: MutexLockGuard lock!
tid 16752: notified
tid 16751: received notification
tid 16751: MutexLockGuard unlock!
tid 16752: MutexLockGuard unlock!
tid 16752: looping...
tid 16751: This EventLoop had been created!
tid 16752: Thread end!
==16751== 
==16751== HEAP SUMMARY:
==16751==     in use at exit: 0 bytes in 0 blocks
==16751==   total heap usage: 7 allocs, 7 frees, 74,176 bytes allocated
==16751== 
==16751== All heap blocks were freed -- no leaks are possible
==16751== 
==16751== For counts of detected and suppressed errors, rerun with: -v
==16751== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

其中cout部分打印线程ID使用了关键字__pthread。主线程ID是16751,用户创建一个EventLoopThread对象后调用接口函数EventLoop* EventLoopThread::startLoop(),函数内部创建一个新线程,ID为16752,并开始运行线程函数,同时主线程运行的接口函数返回给用户的返回值需要与新线程进行线程同步。


1  防止虚假唤醒

 使用条件变量pthread_cond_wait()函数的时候一定要先获得与该条件变量相关的mutex。且条件变量为了防止虚假唤醒,一定要在一个循环里面调用pthread_cond_wait()函数,我在EventLoop* EventLoopThread::startLoop()使用了:

{
    MutexLockGuard lock(mutex_);
    while (loop_ == NULL)
    {
      cond_.wait();
    }
}

 


参考资料:

https://github.com/chenshuo/muduo

### 使用CMake生成名为`moduo`的动态库 要通过CMake生成一个名为`moduo`的动态库,可以按照以下方法操作。以下是详细的说明以及实现方式: #### 配置CMakeLists.txt文件 在项目根目录下创建或编辑 `CMakeLists.txt` 文件,定义目标动态库及其源文件列表。 ```cmake # 定义最低支持的 CMake 版本 cmake_minimum_required(VERSION 3.10) # 设置项目名称和语言 project(moduo LANGUAGES CXX) # 添加动态库的目标名 add_library(moduo SHARED src/moduo.cpp src/utils.cpp) # 如果有额外依赖项,可以通过 target_link_libraries 进行链接 target_link_libraries(moduo PRIVATE other_dependency) ``` 上述代码片段中,`SHARED` 关键字用于指定生成的是共享库(即动态库)。如果需要设置特定平台下的后缀或者前缀,还可以使用如下命令[^1]: ```cmake set_target_properties(moduo PROPERTIES PREFIX "lib" SUFFIX ".so") # Linux 平台示例 ``` #### 检查CMake版本兼容性 为了确保使用的CMake版本满足需求,在构建之前应验证其版本号。可通过运行以下命令来确认当前环境中的CMake版本是否合适: ```bash cmake --version ``` 当发现现有工具链不匹配时,可依据提示更新至推荐版本 (如引用提到的3.10.2)[^4] 或者手动调整配置以适应较低版本的功能限制。 #### 解决可能遇到的问题 - **错误消息:“无法找到对应于Ninja的构建程序”** 此类问题通常源于未正确安装或配置所需的构建系统(Ninja)。解决办法之一是在 Android Studio 的 Gradle 脚本里明确定义所期望采用的具体版本号 : ```gradle android { ... externalNativeBuild { cmake { version "3.10.2" } } } ``` - **缓存冲突引起的报错** 当切换工作区路径而保留旧有的编译产物时可能会触发类似于“source xxx 不同于 yyy”的警告信息 [^3]. 清理原有构建数据后再重新执行一次完整的初始化过程即可规避此类状况发生. #### 构建流程概述 完成以上准备工作之后, 执行标准的三步法来进行实际建造动作: 1. 创建专门存放中间结果的工作子目录. 2. 利用 cmake 命令加载顶层描述文档并解析成内部表示形式. 3. 启动真正的组装进程, 生产出最终制品. 具体指令序列如下所示: ```bash mkdir build && cd build cmake .. make ``` 这样就能成功利用CMake框架打造出预期命名模式下的.so类型的导出组件了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值