moduo网络库的reactor模式基本构成为“non-blocking I/O + I/O multiplexing”,程序的基本结构是一个事件循环(event loop),以事件驱动(event-driven)和事件回调(event callback)的方式实现业务逻辑。此文参照陈硕的moduo网络库稍作简化,抽出reactor模式的基本框架。
线程同步尽量使用高级并发编程构建。若要使用底层同步原语,则尽量只使用互斥器和条件变量。互斥器和条件变量构成了多线程编程的全部必备同步原语,用它们即可完成任何多线程同步任务。
封装互斥器保护对象共享资源,不同对象间不会构成锁争用。其中:
MutexLock封装临界区。用RAII手法封装互斥器的创建与摧毁。
MutexLockGuard封装临界区的进入和退出,即加锁和解锁。它是一个栈上对象,作用域刚好等于临界区域。
#ifndef MUTEXLOCKGUARD_H_
#define MUTEXLOCKGUARD_H_
#include
#include "Thread.hpp"
#include
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 "<
条件变量是非常底层的同步原语,用于线程同步。很少直接使用,一般只用于实现高层同步措施,如阻塞队列(blockingQueue
在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
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
常规下创建一个新线程使用
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
pthread_create成功创建一个新线程后,在线程函数detail::startThread()中通过传入的具体资源对象detail::ThreadData data*调用detail::ThreadData::runInThread(),最终调用实际需要运行的线程函数detail::ThreadData::func_()。
#ifndef THREAD_H_
#define THREAD_H_
#include
#include
#include
#include
#include
#include
#include
//#define gettid() syscall(__NR_gettid)
pid_t gettid()
{
return static_cast(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 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 "<(obj);
data->runInThread();
delete data;
std::cout<<"tid "< ThreadFunc;
Thread(ThreadFunc func) : func_(func), tidp_(0), tid_() {}
void start()
{
detail::ThreadData* data = new detail::ThreadData(func_, name_, tid_);
std::cout<<"tid "<
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
#ifndef EVENT_LOOP_H_
#define EVENT_LOOP_H_
#include
#include
#include
#include
#include
#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 "<
#ifndef EVENT_LOOP_THREAD_H_
#define EVENT_LOOP_THREAD_H_
#include "EventLoop.hpp"
#include "Thread.hpp"
#include "MutexLockGuard.hpp"
#include "Condition.hpp"
#include
#include
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 "<
#include "EventLoopThread.hpp"
#include "EventLoop.hpp"
#include "Thread.hpp"
#include
using namespace std;
int main()
{
cout<<"Main: pid: "<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