Linux多线程服务端编程学习(二):封装互斥量与线程

本文介绍了如何在Linux环境下进行多线程服务端编程,包括互斥量的封装以防止死锁,自定义条件变量类Condition简化操作,使用C++实现CountDownLatch同步工具,通过gettid()获取线程唯一标识,并讲解了muduo库中Thread类的设计与应用。
摘要由CSDN通过智能技术生成

本文的内容主要参照了陈硕先生编写的muduo网络库,本篇文章源码的地址为:https://github.com/freshman94/NetLib

互斥量

使用RAII的手法封装互斥量,避免因忘记解锁造成的死锁问题。

#include <base/noncopyable.h>
#include <assert.h>
#include <pthread.h>

class MutexLock : noncopyable {
public:
	MutexLock() {
		pthread_mutex_init(&mutex_, NULL);
	}
	~MutexLock() {
		pthread_mutex_destroy(&mutex_);
	}

	void lock() {
		pthread_mutex_lock(&mutex_);
		//holder_ = CurrentThread::tid();
	}
	void unlock() {
		pthread_mutex_unlock(&mutex_);
	}
	pthread_mutex_t* get() {
		return &mutex_;
	}
private:
	friend class Condition;
	pthread_mutex_t mutex_;
};

class MutexLockGuard : noncopyable {
public:
	explicit MutexLockGuard(MutexLock& mutex)
		: mutex_(mutex) {
		mutex_.lock();
	}
	~MutexLockGuard() {
		mutex_.unlock();
	}
private:
	MutexLock & mutex_;
};

类MutexLock封装了互斥量的初始化和销毁动作,并提供了接口lock和unlock分别执行加锁和解锁操作。实际中使用的应该是类MutexLockGuard,它在在构造时会自动加锁,在作用域会自动调用其析构函数,执行解锁动作。

自定义的条件变量类Condition

pthread库的条件变量太过冗杂,自定义Condition类,只实现所需的功能

class Condition : noncopyable{
public:
	explicit Condition(MutexLock& mutex)
		: mutex_(mutex)
		{
		pthread_cond_init(&cond_, NULL);
	}

	~Condition(){
		pthread_cond_destroy(&cond_);
	}

	void wait(){
		pthread_cond_wait(&cond_, mutex_.get());
	}

	void notify(){
		pthread_cond_signal(&cond_);
	}

	void notifyAll(){
		pthread_cond_broadcast(&cond_);
	}
	private:
	MutexLock& mutex_;
	pthread_cond_t cond_;
};

使用C++实现的CountDownLatch

CountDownLatch是java实现的一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在C++中,可以使用互斥量和条件变量来实现相应的功能。

class CountDownLatch : noncopyable{
public:
	explicit CountDownLatch(int count);
	void wait();
	void countDown();
	int getCount() const;

private:
	mutable MutexLock mutex_;
	Condition condition_;
	int count_;
};

其成员变量包含一个互斥量和条件变量。count_表示等待的线程数量,用于构造CountDownLatch。关键的成员函数的定义为:

void CountDownLatch::wait(){
	MutexLockGuard lock(mutex_);
	while (count_ > 0)
		condition_.wait();
}

void CountDownLatch::countDown(){
	MutexLockGuard lock(mutex_);
	--count_;
	if (count_ == 0)
		condition_.notifyAll();
}

使用gettid()获取线程标识

对于pthread_t类型,glibc的Pthreads实际上把pthread_t用作一个结构体指针。Pthreads只保证同一进程之内,同一时刻的各个线程的id不同;不能保证同一进程先后多个线程具有不同的id,更不要说一台机器上多个进程之间的id唯一性了。因此,pthread_t并不适合作程序中线程的标识符。

建议使用gettid系统调用的返回值作为线程id,类型为pid_t。pthread_self获得的是POSIX thread ID,gettid是内核中的线程的ID。glibc没有封装这个系统调用,需要自己实现。

pid_t gettid(){
	return static_cast<pid_t>(syscall(SYS_gettid));
}

但是每次获取线程ID都执行一次系统调用,似乎有些浪费,陈硕先生设计的muduo库中,采取的方法是用__thread变量来缓存gettid的返回值,这样只有在本线程第一次调用的时候才进行系统调用,以后都是直接从缓存的线程id拿到结果。

	inline int tid(){
		if (__builtin_expect(t_cachedTid == 0, 0)){
			cacheTid();
		}
		return t_cachedTid;
	}
	
void CurrentThread::cacheTid(){
	if (t_cachedTid == 0){
		t_cachedTid = gettid();
		t_tidStringLength = snprintf(t_tidString, sizeof t_tidString, "%5d ", 
									t_cachedTid);
	}
}

更易使用的线程Thread

muduo库中的Thread类定义为

Thread::Thread(ThreadFunc func, const string& name)
	: started_(false),
	joined_(false),
	pthreadId_(0),
	tid_(0),
	func_(std::move(func)),
	name_(name),
	latch_(1)
{
	setDefaultName();
}

其中ThreadFunc func是线程的启动函数,name是通过调用prctl函数,给线程定义的名字。

通过调用start函数即可让一个线程启动

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;
		LOG_FATAL << "Failed in pthread_create";
	}
	//CountDownLatch是为了确保thread的func真正地启动起来,这时start函数才能返回。
	else{
		latch_.wait();
		assert(tid_ > 0);
	}
}
void* startThread(void* obj)
{
	ThreadData* data = static_cast<ThreadData*>(obj);
	data->runInThread();
	delete data;
	return NULL;
}
void runInThread() {
	*tid_ = CurrentThread::tid();	//cache tid
	tid_ = NULL;
	latch_->countDown();
	latch_ = NULL;

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

	func_();
	CurrentThread::t_threadName = "finished";
}


其中ThreadData是其定义的一个内部类,用于保存线程的各种信息:线程启动函数、线程名字,线程tid等。这里解释下CountDownLatch类型的变量latch_的作用:为了保证线程函数真正地运行起来之后,start函数才能返回。

对于muduo库实现的Thread类,一般而言,我们会让Thread对象的生命期长于线程,然后通过调用Thread::join()来等待线程结束并释放线程资源。如果Thread对象的生命期短于线程,那么其析构时会自动调用pthread_detach函数来分离线程,避免了资源的泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值