上一章的结尾已经实现了定时函数功能,但对于多线程同步依旧不行,比如线程A 调用线程B的Loop,想注册线程B中的回调函数;
1. In Loop
Muduo 在线程间调配任务会把其转移至IO 线程,因此可以不用锁保证线程安全;
void EventLoop::runInLoop(const Functor &cb) {
if (isInLoopThread()) {
cb();
} else {
queueInLoop(cb);
}
}
但IO 线程通常阻塞在poll 调用,因此使用eventfd 增加新Channel 从而高效唤醒,使IO 线程立即执行用户回调;
static int createEventfd() {
int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (evtfd < 0) {
LOG_SYSERR << "Failed in eventfd";
abort();
}
return evtfd;
}
创建eventfd 后设置为wakeupFd,并注册通道:
_wakeupChannel->setReadCallback(std::bind(&EventLoop::handleRead, this));
_wakeupChannel->enableReading();
将回调函数添加到等待队列:
void EventLoop::queueInLoop(const Functor &cb) {
{
MutexLockGuard lock(_mutex);
_pendingFunctors.push_back(cb);
}
if (!isInLoopThread() || _callPendingFunctors) {
wakeup();
}
}
因为pendingFunctors 会暴露给其他线程,因此需要添加锁({} 为了缩小临界区);
而wakeup 函数通过向eventfd 发送一个字节,唤醒位于poll 阻塞的IO 线程(单线程就没有这样的烦恼啦:D),唤醒后的处理函数就是简单地接受这个字节;
第二个唤醒条件表示当前正在进行pending 函数的处理, 即在IO 线程也需要wakeup,因为Functor 有可能会再次调用queueInLoop,以便新加的cb 可以被及时调用;(在pendingFunctors 的处理中若再次调用到了queueInLoop,cb回调 在当前pending 所有的回调处理完后继续Loop,若不wakeup 会等poll,wakeup 后poll 可以拿着eventfd 立即返回并再次处理active 和 pending)
2. Safe Timer Thread
存在线程A 在线程B 添加定时回调任务的可能,因此定时器需要保证线程安全;
TimerId TimerQueue::addTimer(const TimerCallback &cb, Timestamp when,
double interval) {
Timer *timer = new Timer(cb, when, interval);
_loop->runInLoop(std::bind(&TimerQueue::addTimerInLoop, this, timer));
return TimerId(timer);
}
void TimerQueue::addTimerInLoop(Timer *timer) {
_loop->assertInLoopThread();
bool earliestChanged = insert(timer);
if (earliestChanged) {
resetTimerfd(_timerfd, timer->expiration());
}
}
拆分函数即可做到;
3. EventLoopThread
IO 线程不一定使主线程,一个程序也不止一个IO 线程;
EventLoop *EventLoopThread::startLoop() {
assert(!_thread.started());
_thread.start();
{
MutexLockGuard lock(_mutex);
LOG_TRACE << "lock in startLoop is ok";
while (_loop == NULL) {
_cond.wait();
}
}
return _loop;
}
void EventLoopThread::threadFunc() {
EventLoop loop;
{
MutexLockGuard lock(_mutex);
_loop = &loop;
_cond.notify();
}
loop.loop();
// assert(exiting_);
}
这儿的Mutex 和 Condition 想换成C++11 标准库的condition_variable 和 unique_lock(mutex)
但总会产生resource dead lock avoided,应该是锁的问题,但我仔细梳理一下逻辑感觉也没有问题QAQ;换成POSIX 接口就没问题了;
关于标准库真的搞不懂一些设计,thread 没有给start 控制开启,condition_variable wait 必须要给unique_lock 作为参数,还有设计的比较玄学的时间库chrono…
记录一下!
Mutex.h
class MutexLock : boost::noncopyable {
private:
std::mutex _mutex;
std::unique_lock<std::mutex> _lock;
pid_t _holder;
/* data */
public:
MutexLock() : _holder(0), _lock(_mutex) {}
~MutexLock() { assert(_holder == 0); }
bool isLockThisThread() const {
return TinyMuduo::CurrentThread::tid() == _holder;
}
bool assertIsLocked() const { assert(isLockThisThread()); }
std::unique_lock<std::mutex> &getPthreadMutex() { return _lock; }
void lock() {
_lock.lock();
_holder = TinyMuduo::CurrentThread::tid();
}
void unlock() {
_lock.unlock();
_holder = 0;
}
};
class MutexLockGuard : boost::noncopyable {
private:
MutexLock &_lock;
public:
MutexLockGuard(MutexLock &mutex) : _lock(mutex) { _lock.lock(); }
~MutexLockGuard() { _lock.unlock(); }
};
Condition.h:
class Condition : boost::noncopyable {
public:
explicit Condition(MutexLock &mutex) : _mutex(mutex) {}
~Condition() {}
void wait() { _cv.wait(_mutex.getPthreadMutex()); }
// returns true if time out, false otherwise.
bool waitForSeconds(int seconds) {
return _cv.wait_for(_mutex.getPthreadMutex(),
std::chrono::seconds(seconds)) ==
std::cv_status::timeout;
}
void notify() { _cv.notify_one(); }
void notifyAll() { _cv.notify_all(); }
private:
std::condition_variable _cv;
MutexLock &_mutex;
};