muduo是目前在学习过程中遇到的最具有学习意义的网络库,下文将分析muduo库中的基础设施–Thread和ThreadPool.文中观点如果部分不符合您的理解,请留言指教,谢谢~
首先,介绍在多线程编程中不可缺少的同步措施–Mutex和Condition.
- Mutex
class MutexLock : boost::noncopyable
{
public:
MutexLock()//创建一个mutex
: holder_(0)
{
MCHECK(pthread_mutex_init(&mutex_, NULL));//MCHECK是muduo提供的宏定义检验函数
}
~MutexLock()
{
assert(holder_ == 0);
MCHECK(pthread_mutex_destroy(&mutex_));//销毁mutex
}
// must be called when locked, i.e. for assertion
bool isLockedByThisThread() const//是否被当前线程锁住
{
return holder_ == CurrentThread::tid();//防止跨线程调用
}
void assertLocked() const
{
assert(isLockedByThisThread());
}
// internal usage
void lock()
{
MCHECK(pthread_mutex_lock(&mutex_));//加锁
assignHolder();//加锁时获得当前线程的线程号,即当前线程拥有这个锁
}
void unlock()
{
unassignHolder();//表示目前没有线程拥有这个锁
MCHECK(pthread_mutex_unlock(&mutex_));//去锁
}
pthread_mutex_t* getPthreadMutex() /* non-const */
{
return &mutex_;
}
private:
friend class Condition;//条件变量必须持有了锁之后才能使用
class UnassignGuard : boost::noncopyable//防止条件变量在占有这个锁的时候,锁被销毁;将holder_设置为0,则既不能被其他线程调用也不能被当前线程销毁
{
public:
UnassignGuard(MutexLock& owner)
: owner_(owner)
{
owner_.unassignHolder();
}
~UnassignGuard()//表示锁被释放
{
owner_.assignHolder();
}
private:
MutexLock& owner_;
};
void unassignHolder()
{
holder_ = 0;
}
void assignHolder()
{
holder_ = CurrentThread::tid();
}
pthread_mutex_t mutex_;
pid_t holder_;
};
}
//该类负责管理互斥量的加锁和解锁
class MutexLockGuard : boost::noncopyable
{
public:
explicit MutexLockGuard(MutexLock& mutex)
: mutex_(mutex)//加锁
{
mutex_.lock();
}
~MutexLockGuard()
{
mutex_.unlock();
}
private:
MutexLock& mutex_;
};
有四种操作互斥锁的方式:创建,销毁,加锁,解锁。在muduo中,用一个低级的资源管理类MutexLock来实现这四种操作,再用一个较高级的资源管理类MutexLockGuard来管理MutexLock,即用RAII手法对资源进行两次封装,防止资源泄漏。
两个类都具有nocopy的属性,试想对Mutex的拷贝会在多线程程序中造成什么样的结果?有至少两个线程在同一时间拥有对一份资源的使用资格,后果不可设想。
在MutexLock中有一个好玩的私有变量:holder_. 该变量在一个线程对资源加锁时,将holder_设置为使用资源线程的索引;解锁时将holder_设置为0。初始化Mutex时将holder_设置为0;销毁时检查holder_是否为0。以上四个步骤保证了Mutex在某一个时间段内能被一个线程使用。
MutexLock与Condition是友元关系,具有很强的耦合度。
- Condition
class Condition : boost::noncopyable
{//等待时需要占有互斥量,发送信号时不需要占有互斥量
public:
explicit Condition(MutexLock& mutex)
: mutex_(mutex)
{
MCHECK(pthread_cond_init(&pcond_, NULL));
}//不抛出异常的构造那函数,以及初始化条件变量
~Condition()//销毁条件变量
{
MCHECK(pthread_cond_destroy(&pcond_));
}
void wait()//使用之前需要占有互斥量
{
MutexLock::UnassignGuard ug(mutex_);//目前条件变量所在关联的Mutex不被当前线程所持有
MCHECK(pthread_cond_wait(&pcond_, mutex_.getPthreadMutex()));
}
// returns true if time out, false otherwise.
bool waitForSeconds(double seconds);//等待一定时间的wait
void notify()
{
MCHECK(pthread_cond_signal(&pcond_));//通知一条线程
}
void notifyAll()//通知所有的线程
{
MCHECK(pthread_cond_broadcast(&pcond_));
}
private:
MutexLock& mutex_;
pthread_cond_t pcond_;
};
条件变量有五种操作方式:创建,销毁,等待,单一通知,全部通知。
在MutexLock中有一个内部类:UnassignGuard,该类的实例对象在Condition等待时创建,将holder_设置为0;当等待事件结束,又将holder_设置为原值。用MutexLock的析构函数检查等待事件是否发生在同一个线程中。目的是为了互斥量被意外的销毁。
Condition类中有一个waitForSecond函数,用于实现pthread_cond_timewait的封装。
条件变量在等待时需要持有互斥锁,但是在通知的时候不需要持有锁。