锁的封装一般考虑两个问题:
- 初始化和销毁
- 加锁和解锁
因为锁不允许拷贝和赋值语义,因此继承前面的noncopyable类。
参考 https://blog.youkuaiyun.com/littleflypig/article/details/88799617
封装
首先对mutex资源进行封装,这个类代表锁这个资源:
- 定义一个宏来对pthread函数返回值进行检查,因此pthread函数一般返回0代表成功,所以用CHECK(!exp)进行检查。
- 宏中的__FILE__和__LINE__都是GCC编译器预定义宏,一般可以用这些宏进行调试输出信息。
-还有一点注意加锁和解锁时成员变量islocking的修改顺序
#define CHECK(exp)\
if(!exp) \
{ \
fprintf(stderr,"file:%s,line:%d Exp:[" #exp "] not return 0\n",__FILE__,__LINE__);abort();\
}
class MutexLock:public noncopyable{
public:
MutexLock();
~MutexLock();
void lock();
void unlock();
bool islocking(){
return islocking_;
}
pthread_mutex_t* getMutexPtr(){
return &mutex_;
}
private:
void restoreMutexStatus() {
islocking_ = true;
}
pthread_mutex_t mutex_;
bool islocking_;
};
//锁初始化
MutexLock::MutexLock():islocking_(false) {
CHECK(!pthread_mutex_init(&mutex_,NULL));
}
//锁销毁
MutexLock::~MutexLock(){
CHECK(!pthread_mutex_destroy(&mutex_));
}
//加锁
void MutexLock::lock(){
CHECK(!pthread_mutex_lock(&mutex_)); //必须先加锁,保证islocking修改的原子性
islocking_ = true;
}
//解锁
void MutexLock::unlock(){
islocking_ = false; //先修改在解锁
CHECK(!pthread_mutex_unlock(&mutex_));
}
然后依据RAII(资源获取即初始化)在封装一个MutexLockGuard类,由它来进行对MutexLock的操作,构造的时候加锁,析构的时候解锁
class MutexLockGuard: public noncopyable{
public:
MutexLockGuard(MutexLock &mutex):mutex_(mutex){ mutex_.lock();};
~MutexLockGuard() {mutex_.unlock();};
private:
MutexLock &mutex_;
};
使用
用一个简单的例子来巩固一下两个类的使用。
下面例子开启了多个线程操作共享变量count,偶数线程对其加1,奇数线程对其减1。
首先考虑不加锁时:
const int threadNum = 10;
int count=5;
MutexLock mutex;
void *func(void *arg){
int num = reinterpret_cast<int>(arg);
cout<<num<<endl;
for(int i=0;i<10000000;++i){
if(num%2){
//MutexLockGuard lock(mutex);
count++;
}
else{
//MutexLockGuard lock(mutex);
count--;
}
}
return arg;
}
int main()
{
pthread_t tid[threadNum];
for(int i=0;i<threadNum;++i){
pthread_create(&tid[i],NULL,func,reinterpret_cast<void*>(i));
}
void* arg;
for(int i=0;i<threadNum;++i){
pthread_join(tid[i],&arg);
//cout<<reinterpret_cast<int>(arg)<<endl;
}
cout<<count<<endl;
}
对count的修改可能会出现race condition,当循环次数特别大时,此情况更容易出现,最终的count输出结果并不是5。
加锁后,count输出5。
还有几个pthread函数应用时的注意事项:
- pthread_create函数的最后一个参数,如果改用 &i,那么所有线程中得到的num是不确定的,这取决于主线程和新线程的执行顺序,这个顺序并不是固定的。
- pthread_t类型的tid并不是唯一的,不同进程中的tid可能相同,因此想唯一标示一个线程可以用gettid,其返回pid_t类型,不过glibc并没有实现这个调用,需要自己封装一下。
参考:https://blog.youkuaiyun.com/delphiwcdj/article/details/8476547
#include <sys/syscall.h>
pid_t gettid(void)
{
return syscall(__NR_gettid);
}
- 命名强制类型转换reinterpret_cast,能在指针和其他类型之间转换