简述muduo库中的Thread类

本文根据自己的看法对muduo库中的thread类进行了一定程度的简写。

为什么要对线程进行封装?这是我在当初阅读这部分源码的时候萦绕在心头的一个问题。现在我想我可以对这个问题稍作解答。

封装线程的目的是为其主线程构建一个方便操作副线程的接口。

我们在进行多线程编程的时候,势必要对副线程进行管理,在理想情况下,程序里的线程都是用同一个class创建的,这样容易在线程的启动和销毁阶段做一些统一的薄记工作。比如说调用一次muduo::CurrentThread::tid()把当前线程id缓存起来,减少陷入内核的次数。再比如统计当前有多少活动的线程,进程一共创建了多少线程,每个线程都干了什么。java可以像处理对象一样处理变量一样处理线程,C++的话可以通过ThreadClass实现差不多的功能。

首先要明确的一点是,线程之间虽然共享地址空间,但是操作系统仍然赋予了她们一部分专属于自己的空间,用于区分它们,比如线程的tid,这里明确一下操作系统中和进程线程有关的几个id变量。

  • pid,每个进程的id,类型为pid_t,可以通过getpid()取得。
  • 线程id,类型为pthread_t,这个id在每个进程里是唯一的,它只用于在进程里区分某个线程,也就是说不同的进程里可能有两个线程的线程id是一样的。可以由pthread_self()取得。
  • tid,线程的真实id,操作系统保证这个值对于每个线程是唯一的,也就是说进程1下面的线程要和进程2下面的线程沟通,只能使用tid。这个值只能通过linux的系统调用取得,syscall(SYS_gettid)

在muduo中主要使用的是线程的tid,可是我们总不能每次想要知道tid的时候都调用一次系统调用,那样很浪费时间,而且对于主线程来说,你也没有办法直接取得某个副线程的tid,不过,总有办法的,来看看muduo是怎么做的。

struct ThreadData
{
	typedef Thread::ThreadFunc ThreadFunc;
	ThreadFunc func_;
	string name_;
	pid_t* tid_;
	CountDownLatch* latch_;

	ThreadData(const ThreadFunc &func,const string& name,pid_t*tid,CountDownLatch *latch)
		:	func_(func),
			name_(name),
			tid_(tid),
			latch_(latch)
	{}
	void runInThread()
	{
		*tid_=CurrentThread::tid();
		tid_=NULL;
		latch_->countDown();
		latch_=NULL;

		CurrentThread::t_threadName=name_.empty()?"Thread":name_.c_str();
		prctl(PR_SET_NAME,CurrentThread::t_threadName);

		func_();
		CurrentThread::t_threadName="finished";
	}
};
void* startThread(void* obj)
{
	ThreadData* data=static_cast<ThreadData*>(obj);
	data->runInThread();
	delete data;
	return NULL;
}
void Thread::start()
{
	assert(!started_);
	started_=true;
	ThreadData* data=new ThreadData(func_,name_,&tid_,&latch_);
	if(pthread_create(&pthreadId_,NULL,&startThread,data))
	{
		started_=false;
		delete data;
	}
	else
	{
		latch_.wait();
		assert(tid_>0);
	}
}

使用了一个中间结构,ThreadData,装载了我们希望赋予副线程的名字,希望从副线程那里取回的tid指针,还有我们希望它执行的函数。startThread是一个外部的函数,用来调用ThreadData的runInThread,因为pthread_create只接受静态函数,所以我们需要一个跳板,这边是startThread存在的意义。runInThread里我们修改线程名字,获取线程tid的值,运行fun。

代码中出现了名为CurrentThread的namespace,它的具体定义如下

namespace CurrentThread
{
	extern __thread int t_cachedTid;
	extern __thread char t_tidString[32];
	extern __thread int t_tidStringLength;
	extern __thread const char* t_threadName;
	void cacheTid();
	inline int tid()
	{
		if(__builtin_expect(t_cachedTid==0,0))
			cacheTid();
		return t_cachedTid;
	}
	inline const char* tidString()
	{
		return t_tidString;
	}
	inline int tidStringLength()
	{
		return t_tidStringLength;
	}
	inline const char*name()
	{
		return t_threadName;
	}
}

定义了一系列的全局变量,以供线程记录它自己的信息,减少调用系统调用的次数。值得一说的是__thread 关键字,这个关键字只能修饰和c语言兼容的变量类型,不支持调用虚函数,构造函数,析构函数等,被它声明的变量在每一个线程中都可以有自己独立的值。

最后来看看ThreadClass怎么处理线程的销毁。

线程的销毁有几种方式:

  1. 自然死亡。从线程主函数返回,线程正常退出。
  2. 非正常死亡。从线程主函数跑出异常或线程触发segfault新号等非法操作。
  3. 自杀。在线程中调用pthread_exit()来立刻退出线程。
  4. 他杀。其他线程调用pthread_cancel()来强制终止某个线程。

线程正常退出的方式只有一种,即自然死亡。其他的方法都没法保证线程中申请的资源得以归还回去。muduo::Thread不是传统意义上的RAII class,因为它析构的时候没有销毁持有的Pthreads,也就是说Thread的析构不会等待线程结束。一般而言,我们会让Thread对象长于线程,然后通过Thread::join()来等待线程结束并且释放线程资源。如果thread对象的生命期短于线程,那么析构时会自动detach线程,避免资源泄露。

猜你喜欢

转载自blog.csdn.net/qq_33113661/article/details/88577645