读写锁优化

功能与实现原理

允许多个线程同时加读锁,但只允许一个线程加写锁,可以写嵌套,即一个线程里面加写锁了,可以继续在这个线程加写锁,不会阻塞。
当有线程在读取时,写入线程阻塞,当写入线程执行时,所有的读取线程都被阻塞。
允许嵌套加锁

  1. 当前线程写入加锁时可以嵌套加读锁或写锁
  2. 当前线程读取加锁时可以嵌套加读锁,但不能嵌套加写锁,否则当前线程会陷入死锁,原理为:已经作为只读区了,就不应该存在写了

加读锁的时候:

  • 判断当前线程是不是使用写锁的线程,如果是的话,可以加读锁。
  • 如果当前线程不是使用写锁的线程,需要等待写锁线程使用完、以及正在等待写的先使用完写锁,因为写的优先级高。

加写锁的时候:

  • 判断当前线程是不是使用写锁的线程,如果是的话,可以加写锁,因为允许嵌套写。
  • 如果当前线程不是使用写锁的线程,需要等待读锁线程以及写锁线程都使用完,但可以不管正在等待读写的线程,因为自己先从条件变量抢到了锁。

释放读锁的时候:

  • 如果有写锁正在等待,通知它

释放写锁的时候:

  • 注意嵌套写锁全都释放了以后,才是真正的释放。
  • 释放后,如果有写锁正在等待,先通知写锁,因为写锁的优先级高。
  • 如果有读锁正在等待,再通知读锁。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void RWLock::ReadLock()
{
std::unique_lock<std::mutex> lck(m_mtx);
// ==时为独占写状态,不需要加锁
if (std::this_thread::get_id() != this->m_writethreadid)
{
if (m_nwrite || m_nwriteWaiters)
{
++m_nreadWaiters;
while (m_nwrite || m_nwriteWaiters)
m_rcond.wait(lck); // calls lck.unlock() inherently, lck.lock() is called after notified.
--m_nreadWaiters;
}
}
++m_nread;
}

void RWLock::ReadUnlock()
{
std::unique_lock<std::mutex> lck(m_mtx);
--m_nread;
if (m_nwriteWaiters)
m_wcond.notify_one();
}

void RWLock::WriteLock()
{
std::unique_lock<std::mutex> lck(m_mtx);
// ==时为独占写状态,避免重复加锁等待
if (std::this_thread::get_id() != this->m_writethreadid)
{
if (m_nread || m_nwrite)
{
++m_nwriteWaiters;
while (m_nread || m_nwrite)
m_wcond.wait(lck);
--m_nwriteWaiters;
}
m_writethreadid = std::this_thread::get_id();
}
++m_nwrite; // 嵌套写时会大于1
}

void RWLock::WriteUnlock()
{
std::unique_lock<std::mutex> lck(m_mtx);
if (std::this_thread::get_id() != this->m_writethreadid)
throw std::runtime_error("WriteLock/WriteUnlock mismatch");

--m_nwrite;
if (0 == m_nwrite) // 变为0才真正是没有线程在写了
{
m_writethreadid = NULL_THEAD;
if (m_nwriteWaiters) // write-preference
m_wcond.notify_one();
else if (m_nreadWaiters)
m_rcond.notify_all();
}
}
nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!