muduo源码分析之Thread线程
相关文件
muduo/base/Thread.h//线程类
muduo/base/Thread.cc
muduo/base/currentThread.h //线程真实pid,即tid
muduo/base/cuurentThread.cc
作用
开启一个线程,执行用户函数。
使用
#include <muduo/base/Thread.h>
#include <boost/bind.hpp>
#include <unistd.h>
#include <iostream>
using namespace std;
using namespace muduo;
class Foo
{
public:
Foo(int count) : count_(count)
{
}
void MemberFun()
{
while (count_--)
{
cout<<"this is a test ..."<<endl;
sleep(1);
}
}
void MemberFun2(int x)
{
while (count_--)
{
cout<<"x="<<x<<" this is a test2 ..."<<endl;
sleep(1);
}
}
int count_;
};
void ThreadFunc()
{
cout<<"ThreadFunc ..."<<endl;
}
void ThreadFunc2(int count)
{
while (count--)
{
cout<<"ThreadFunc2 ..."<<endl;
sleep(1);
}
}
int main(void)
{
Thread t1(ThreadFunc); //无参函数
Thread t2(boost::bind(ThreadFunc2, 3));//有参函数
Foo foo(3);
Thread t3(boost::bind(&Foo::MemberFun, &foo));//无参类方法
Foo foo2(3);
Thread t4(boost::bind(&Foo::MemberFun2, &foo2, 1000));//有参类方法
t1.start(); //开始执行
t2.start();
t3.start();
t4.start();
t1.join(); //阻塞等待线程结束
t2.join();
t3.join();
t4.join();
return 0;
}
Thread源码分析
Thread类
类图
这里涉及到一个线程标识符tid
Linux中,每个进程有一个pid,类型pid_t,由getpid()取得。Linux下的POSIX线程也有一个id,类型 pthread_t,由pthread_self()取得,该id由线程库维护,其id空间是各个进程独立的(即不同进程中的线程可能有相同的id)。Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),只是该进程与主进程(启动线程的进程)共享一些资源而已,比如代码段,数据段等。
有时候我们可能需要知道线程的真实pid。比如进程P1要向另外一个进程P2中的某个线程发送信号时,既不能使用P2的pid,更不能使用线程的pthread id,而只能使用该线程的真实pid,称为tid。
有一个函数gettid()可以得到tid,但glibc并没有实现该函数,只能通过Linux的系统调用syscall来获取。
return syscall(SYS_gettid)
class Thread : noncopyable
{
public:
typedef std::function<void ()> ThreadFunc; //线程函数
explicit Thread(ThreadFunc, const string& name = string()); //构造
~Thread(); //析构
void start();
int join(); // return pthread_join()
bool started() const { return started_; }
pid_t tid() const { return tid_; } //线程真实pid
const string& name() const { return name_; } //线程名字
static int numCreated() { return numCreated_.get(); } //已经创建的线程数
private:
void setDefaultName(); //设置默认name_
bool started_;
bool joined_;
pthread_t pthreadId_;
pid_t tid_; //线程标识符
ThreadFunc func_;
string name_;
CountDownLatch latch_; //计数+条件变量,计数等于0时条件变量做通知
static AtomicInt32 numCreated_; //创建线程数,原子性操作
};
构造函数
主要对数据成员进行初始化,还没创建线程执行入口函数。
Thread::Thread(ThreadFunc func, const string& n)
: started_(false),
joined_(false),
pthreadId_(0),
tid_(0),
func_(std::move(func)),
name_(n),
latch_(1)
{
setDefaultName();
}
void Thread::setDefaultName()
{
int num = numCreated_.incrementAndGet();
if (name_.empty()) //没有设置名字,使用默认的name
{
char buf[32];
snprintf(buf, sizeof buf, "Thread%d", num);
name_ = buf;
}
}
start()方法
将数据封装在ThreadData类,执行pthread_create创建线程。
void Thread::start()
{
assert(!started_);
started_ = true;
// FIXME: move(func_)
detail::ThreadData* data = new detail::ThreadData(func_, name_, &tid_, &latch_);
if (pthread_create(&pthreadId_, NULL, &detail::startThread, data))
{
started_ = false;
delete data; // or no delete?
LOG_SYSFATAL << "Failed in pthread_create";
}
else
{
latch_.wait(); //主线程等待子线程初始化完毕才开始工作,在runInThread()中
assert(tid_ > 0);
}
}
线程执行函数
使用了CountDownLatch类,这个类主要成员是计数值和条件变量,当计数值为0,条件变量发出通知。
既可以用于所有子线程等待主线程发起 “起跑” ;
也可以用于主线程等待子线程初始化完毕才开始工作。
//线程入口函数
//
void* startThread(void* obj)
{
ThreadData* data = static_cast<ThreadData*>(obj);
data->runInThread();
delete data;
return NULL;
}
void runInThread()
{
*tid_ = muduo::CurrentThread::tid();//Thread类获取tid
tid_ = NULL;
latch_->countDown();//计数减一,因为初始化为1,为零时条件变量通知主线程初始化完成
latch_ = NULL;
muduo::CurrentThread::t_threadName = name_.empty() ? "muduoThread" : name_.c_str();
::prctl(PR_SET_NAME, muduo::CurrentThread::t_threadName);
try
{
func_();//执行用户函数
muduo::CurrentThread::t_threadName = "finished";
}
catch (const Exception& ex) //捕捉异常
{
muduo::CurrentThread::t_threadName = "crashed";
fprintf(stderr, "exception caught in Thread %s\n", name_.c_str());
fprintf(stderr, "reason: %s\n", ex.what());
fprintf(stderr, "stack trace: %s\n", ex.stackTrace());
abort();
}
catch (const std::exception& ex)
{
muduo::CurrentThread::t_threadName = "crashed";
fprintf(stderr, "exception caught in Thread %s\n", name_.c_str());
fprintf(stderr, "reason: %s\n", ex.what());
abort();
}
catch (...)
{
muduo::CurrentThread::t_threadName = "crashed";
fprintf(stderr, "unknown exception caught in Thread %s\n", name_.c_str());
throw; // rethrow
}
}
};
tid的获取
在ThreadData::runInThread()中调用了CurrentThread::tid()给Thread获取线程tid。
前面说到,有一个函数gettid()可以得到tid,但glibc并没有实现该函数,只能通过Linux的系统调用syscall来获取。
muduo中使用系统调用获取tid后保存在__thread变量中,减少多次系统调用。
CurrentThread并是不一个类,该命名空间中的是线程自己保留的变量,用__thread关键字修饰。
__thread变量是每个线程都有一份独立实体,各个线程的变量值互不干扰。
extern __thread int t_cachedTid;
extern __thread char t_tidString[32];
extern __thread int t_tidStringLength;
extern __thread const char* t_threadName;
CurrentThread::tid()
//CurrentThread.h
inline int tid()
{
if (__builtin_expect(t_cachedTid == 0, 0))
{
cacheTid();
}
return t_cachedTid;
}
//Thread.h
void CurrentThread::cacheTid()
{
if (t_cachedTid == 0)
{
t_cachedTid = detail::gettid();
t_tidStringLength = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid);
}
}
//Thread.h
//namespace detail
pid_t gettid()
{
//线程的真实tid
return static_cast<pid_t>(::syscall(SYS_gettid));
}