eventfd和__thread的应用
muduo可以通过设置线程数量来决定开几个EventLoop,一个EventLoop有多个channel。当channel上有事件发生时,就应该在他所属的EventLoop上执行。
比如在一个mainReactor一个subReactor的情况下,mainReactor监听到一个新连接,要把这个新连接派发给subReactor。而subReactor可能大部分的时间都在loop(epoll_wait)阻塞的状态,mainReactor不能强把这个新连接塞给subReactor的Poller,这不合理也不安全,这就需要唤醒阻塞的subReactor,并且让它自己来把这个新连接添加到它的Poller上。
如何确定这个新连接是在它所属的EventLoop上处理的呢?
one loop per thread,我们可以通过系统中唯一的线程ID来确认他们所属的loop。
eventfd唤醒subReactor
eventfd用于创建一个专门唤醒事件的一个文件描述符。
当有新连接到来时,如果subReactor还在阻塞,不能一直等到subReactor阻塞返回,应该把它唤醒,立刻处理新连接。
我们可以让subReactor监听一个eventfd,在需要唤醒时往eventfd里写一个字节,这样subReactor就可以从loop(epoll_wait)阻塞状态返回了。
用传统的pipe也可以实现,但eventfd可以更高效的唤醒,因为他不必管理缓冲区。
巧用__thread关键字
__thread是GCC内置的线程局部存储设施(thread local storage)。可以用来修饰POD类型,不能修饰class类型,因为无法自动调用构造和析构函数。
__thread可以用于修饰全局变量、函数内的静态变量,但是不能用于修饰函数的局部变量会在class的普通成员变量。另外,__thread变量的初始化只能用编译期常量。例如:
__thread string t_obj1("hello"); //错误,不能调用对象的构造函数
__thread string* t_obj2 = new string; //错误,初始化必须用编译器常量
__thread string* t_obj3 = NULL; //正确,但是需要手工初始化并销毁对象
__thread修饰的变量每个线程都有一份独立实体,各个线程的变量值互不干扰。
获取当前线程id接口:
CurrentThread.h
#pragma once
#include <unistd.h>
#include <sys/syscall.h>
namespace CurrentThread
{
extern __thread int t_cachedTid;
void cacheTid();
inline int tid()
{
if(__builtin_expect(t_cachedTid == 0, 0))
{
cacheTid();
}
return t_cachedTid;
}
}
CurrentThread.cc
#include "CurrentThread.h"
namespace CurrentThread
{
__thread int t_cachedTid = 0;
void cacheTid()
{
if(t_cachedTid == 0)
{
//通过Linux系统调用,获取当前线程的pid值
t_cachedTid = static_cast<pid_t>(::syscall(SYS_gettid));
}
}
}
因为每个线程都有__thread修饰变量的一份独立实体,在线程调用CurrentThread::tid,获取的一定是当前线程的id。