muduo源代码分析1:一个线程安全的Counter示例
现在开始讲《Linux多线程服务端编程:使用muduoC++网络库》一书的代码,对服务器多线程C++编程做一系列的介绍,首先本节将介绍最简单的一个线程安全示例Counter,其实现的是一个线程安全的计数器,首先下载书中源代码:
git clone http://github.com/chenshuo/recipes.git
代码是基于moduo网络库和标准boost库实现,故首先要安装boost和moduo,boost库的安装参看boost安装。
示例代码 Counter.cc在目录/recipes/thread/test下,首先介绍下如何编译Counter.cc,Counter.cc的编译还涉及到3个其他文件,均在目录/recipes/thread/下,分别为Mutex.h、Thread.h和Thread.cc,makefile内容如下:
CXX = g++
DEBUG = -g -O2
CFLAGS = $(DEBUG) -Wall -c
RM = rm -rf
LIBS = -lpthread -lboost_system -lboost_thread
TARGET = Counter
OBJS =Counter.o Thread.o
$(TARGET) : $(OBJS)
$(CXX) $^ -o $@ $(LIBS)
Counter.o : Counter.cc
$(CXX) $(CFLAGS) $< -o $@
Thread.o : ../Thread.cc
$(CXX) $(CFLAGS) $< -o $@
clean:
$(RM) $(TARGET) *.o
注意这里涉及boost库和多线程的编译,需要连接静态文件:-lpthread -lboost_system -lboost_thread。
下面来看代码,首先看Counter.cc的代码:
#include "../Mutex.h"
using muduo::MutexLock;
using muduo::MutexLockGuard;
// A thread-safe counter
class Counter : boost::noncopyable
{
// copy-ctor and assignment should be private by default for a class.
public:
Counter() : value_(0) {}
Counter& operator=(const Counter& rhs);
int64_t value() const;
int64_t getAndIncrease();
friend void swap(Counter& a, Counter& b);
private:
mutable MutexLock mutex_;
int64_t value_;
};
int64_t Counter::value() const
{
MutexLockGuard lock(mutex_);
return value_;
}
int64_t Counter::getAndIncrease()
{
MutexLockGuard lock(mutex_);
int64_t ret = value_++;
return ret;
}
void swap(Counter& a, Counter& b)
{
MutexLockGuard aLock(a.mutex_); // potential dead lock
MutexLockGuard bLock(b.mutex_);
int64_t value = a.value_;
a.value_ = b.value_;
b.value_ = value;
}
Counter& Counter::operator=(const Counter& rhs)
{
if (this == &rhs)
return *this;
MutexLockGuard myLock(mutex_); // potential dead lock
MutexLockGuard itsLock(rhs.mutex_);
value_ = rhs.value_;
return *this;
}
int main()
{
Counter c;
c.getAndIncrease();
}
类Counter继承了boost::noncopyable类,作用是禁用拷贝赋值函数和赋值运算。这个例子很简单,对成员变量value进行计数,可以返回value值,可以递加value的值,且可以交换两个对象的成员value的值。首先看函数value():
int64_t Counter::value() const
{
MutexLockGuard lock(mutex_);
return value_;
}
声明为const有两个作用:1.声明为const,才能被const对象调用;2.声明为const,不允许修改成员变量。这里用到了类MutexLockGuard,其定义在Mutex.h文件中:
class MutexLockGuard : boost::noncopyable
{
public:
explicit MutexLockGuard(MutexLock& mutex) : mutex_(mutex)
{
mutex_.lock();
}
~MutexLockGuard()
{
mutex_.unlock();
}
private:
MutexLock& mutex_;
};
主要用到了MutexLock类,其定义同样在Mutex.h文件中:
class MutexLock : boost::noncopyable
{
public:
MutexLock()
: holder_(0)
{
pthread_mutex_init(&mutex_, NULL);
}
~MutexLock()
{
assert(holder_ == 0);
pthread_mutex_destroy(&mutex_);
}
bool isLockedByThisThread()
{
return holder_ == CurrentThread::tid();
}
void assertLocked()
{
assert(isLockedByThisThread());
}
// internal usage
void lock()
{
pthread_mutex_lock(&mutex_);
holder_ = CurrentThread::tid();
}
void unlock()
{
holder_ = 0;
pthread_mutex_unlock(&mutex_);
}
pthread_mutex_t* getPthreadMutex() /* non-const */
{
return &mutex_;
}
private:
pthread_mutex_t mutex_;
pid_t holder_;
};
这里定义了线程互斥变量pthread_mutex_t mutex_,holder_在锁定的时候保存锁定线程的线程id,调用lock锁定互斥变量,调用unlock解锁互斥变量,holder_重新置为0。我们再回到MutexLockGuard的定义,可以看到MutexLockGuard通过构造函数传入一个互斥变量,并锁定该互斥变量,在析构的时候会解锁该互斥变量。现在再看Value函数就很容易理解了,MutexLockGuard定义局部变量lock,此时会调用构造函数锁定mutex_,当value函数执行完成后,局部变量lock生命周期结束,调用析构函数释放mutex_。这里注意两点,一点是互斥成员变量声明为mutex_,这样即便在const的函数中其值也是可变的;swap函数定义为友元函数,其本身不是类成员函数,但由于声明为友元,故可以访问类的私有变量。这是最简答的一个多线程加锁的例子,但仍然是有问题的,例如Counter是动态创建的,且通过一个指针来访问,那么解析这个对象的时候是否有线程正在执行该对象是未知的。