读写锁
读写锁有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。当占有读写锁的读锁时,任何线程希望以写模式对此锁进行加锁时都会阻塞,但是这种情况下,读写锁通常会阻塞随后的读锁请求,这样可以避免读模式锁长期占用,而等待的写模式锁一直得不到满足。当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。
#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
两个函数成功返回0,失败返回错误码。
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读模式的锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写模式的锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁
- 缺点:读写锁的开销是非常大的,并且在大型项目中非常容易出错,我们可能会在读的时候不小心修改了数据,这是致命的错误,并且当我们需要写加锁的时候,读锁是需要一直等待写完成的。
- 在性能方面来说,读写锁不见得比普通mutex更高效。无论如何reader lock加锁的开销不会比mutex lock小,因为它要更新当前的reader的数目。如果临界区很小,锁竞争不激烈,往往mutex更快。
智能指针加锁实现读写锁的功能
这个时候我们就想到利用智能指针和锁来模拟读写锁提高性能。因为智能指针中有引用计数,我们就利用这个特性来模拟。
- 首先,当我们是只读的时候,我们就复制一个变量,这个时候智能指针的引用计数就大于1,当没有人在读的时候引用计数就等于1。
- 当我们需要更改数据的时候(写操作),我们就判断其引用计数是否为1,如果为1则证明没有人在访问数据,我们在原数据进行更改即可。当大于1的时候就有人在读取数据,我们就需要复制一个副本进行更改即可。
具体操作就看下面的代码实现:
这是我们自己实现的类,MutexlockGuard类就和c++11线程库中的std::lock_guard()是相同的作用。构造时上锁,析构时解锁。
#include<iostream>
#include<thread>
#include<mutex>
#include<pthread.h>
#include<unistd.h>
#include<assert.h>
class Mutexlock
{
public:
Mutexlock() : holder_(0)
{
pthread_mutex_init(&mutex_, NULL);
}
~Mutexlock()
{
pthread_mutex_destroy(&mutex_);
}
//删除拷贝构造和拷贝赋值函数
// Mutexlock(const Mutexlock &) = delete;
//Mutexlock operator = (const Mutexlock &) = delete;
bool isLockedByThisThread()
{
return holder_ == pthread_self();
}
void assertLocked()
{
assert(isLockedByThisThread());
}
void lock()
{
pthread_mutex_lock(&mutex_);
holder_ = pthread_self();
}
void unlock()
{
holder_ = 0;
pthread_mutex_unlock(&mutex_);
}
pthread_mutex_t* getPthreadMutex()
{
return &mutex_;
}
private:
pthread_mutex_t mutex_;
pid_t holder_;
};
class MutexlockGuard
{
public:
explicit MutexlockGuard(Mutexlock& mutex) : mutex_(mutex)
{
mutex_.lock();
}
~MutexlockGuard()
{
mutex_.unlock();
}
private:
Mutexlock mutex_;
};
class Foo;
typedef std::vector<Foo> Foolist;
typedef std::shared_ptr<Foolist> FoolistPtr;
Mutexlock mutex;
FoolistPtr g_foos;
void read_data()
{
FoolistPtr foos;
{
MutexlockGuard lock(mutex);
foos= g_foos;
assert(!g_foos.unique());
}
//其它操作
}
void write_data(const Foo &f)
{
MutexlockGuard lock(mutex);
//判断引用计数是否为1
if(!g_foos.unique())
{
//如果有其它人正在读,我们则复制一份,在副本上进行修改
g_foos.reset(new Foolist(*g_foos)); //当reset之后,原g_foos的引用计数会-1,所以原始的当读操作完毕后会析构掉。
std::cout << "copy the whole list\n";
}
assert(g_foos.unique());
g_foos->push_back(f);
}
上述就是我们用shared_ptr和互斥锁实现的读写锁操作。