C++中读取写锁定实现

Read Write lock implementation in C++

本文关键字:锁定 实现 写锁 读取 C++      更新时间:2023-10-16

我正在尝试使用读/写锁定C++使用shared_mutex

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock >  WriteLock;
typedef boost::shared_lock< Lock >  ReadLock;
class Test {
    Lock lock;
    WriteLock writeLock;
    ReadLock readLock;
    Test() : writeLock(lock), readLock(lock) {}
    readFn1() {
        readLock.lock();
        /*
             Some Code
        */
        readLock.unlock();
    }
    readFn2() {
        readLock.lock();
        /*
             Some Code
        */
        readLock.unlock();
    }
    writeFn1() {
        writeLock.lock();
        /*
             Some Code
        */
        writeLock.unlock();
    }
    writeFn2() {
        writeLock.lock();
        /*
             Some Code
        */
        writeLock.unlock();
    }
}

代码似乎工作正常,但我有一些概念问题。

问题 1.我已经看到了在 http://en.cppreference.com/w/cpp/thread/shared_mutex/lock 上使用unique_lock和shared_lock的建议,但我不明白为什么shared_mutex已经支持锁定和lock_shared方法?

问题 2.此代码是否可能导致写入不足?如果是,那么我怎样才能避免饥饿?

问题 3.有没有其他锁定类可以尝试实现读写锁定?

Q1:使用互斥包装器

建议使用包装器对象而不是直接管理互斥锁,以避免代码中断且互斥锁未释放的不幸情况,从而使其永久锁定。

这就是RAII的原则。

但这仅在您的 ReadLock 或 WriteLock 是使用它的函数的本地时才有效。

例:

readFn1() {
    boost::unique_lock< Lock > rl(lock);  
    /*
         Some Code 
         ==> imagine exception is thrown
    */
    rl.unlock();   // this is never reached if exception thrown 
}  // fortunately local object are destroyed automatically in case 
   // an excpetion makes you leave the function prematurely      

在您的代码中,如果其中一个函数被中断,这将不起作用,因为您的 ReadLock WriteLock 对象是 Test 的成员,而不是设置锁的函数的本地。

Q2:写入饥饿

目前还不完全清楚您将如何调用读者和作者,但是是的,存在风险:

  • 只要读取器处于活动状态,编写器就会被unique_lock阻止,等待互斥锁在独占模式下可用。
  • 但是,只要 wrtier 在等待,新读者就可以获得对共享锁的访问权限,从而导致unique_lock进一步延迟。

如果你想避免饥饿,你必须确保等待的作家确实有机会设定他们的unique_lock。 例如,在您的阅读器中有一些代码来检查编写器是否在设置锁之前等待。

Q3 其他锁定类

不太确定您要查找什么,但我的印象是condition_variable您可能会感兴趣。但逻辑有点不同。

也许,你也可以通过开箱即用来找到解决方案:也许有一种合适的无锁数据结构,可以通过稍微改变方法来促进读者和作家的共存?

锁的类型是可以的,但不是将它们作为成员函数创建,而是在成员函数中locktype lock(mymutex) .这样,即使在例外的情况下,它们也会在销毁时释放。

问题 1.我已经看到了在 http://en.cppreference.com/w/cpp/thread/shared_mutex/lock 上使用unique_lock和shared_lock的建议,但我不明白为什么shared_mutex已经支持锁定和lock_shared方法?

可能是因为unique_lock自 c++11 以来就已经存在了,但shared_lock 正在加入 c++17。此外,[可能]unique_lock可以更有效率。这是[创作者]http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html shared_lock的原始理由,我尊重这一点。

问题 2.此代码是否可能导致写入不足?如果是,那么我怎样才能避免饥饿?

是的,绝对。如果您这样做:

while (1)
    writeFn1();

您最终可以得到以下时间线:

T1: writeLock.lock()
T2: writeLock.unlock()
T3: writeLock.lock()
T4: writeLock.unlock()
T5: writeLock.lock()
T6: writeLock.unlock()
...

T2-T1的差异是任意的,基于正在完成的工作量。但是,T3-T2接近于零。这是另一个线程获取锁的窗口。因为窗口太小了,它可能不会得到它。

为了解决这个问题,最简单的方法是插入一个小睡眠(例如 nanosleep ) 在 T2T3 之间。您可以通过将其添加到writeFn1底部来执行此操作。

其他方法可能涉及为锁创建队列。如果线程无法获取锁,它会将自身添加到队列中,队列中的第一个线程会在释放锁时获得锁。在 linux 内核中,这是为"排队旋转锁"实现

问题 3.有没有其他锁定类可以尝试实现读写锁定?

虽然不是一个类,但您可以使用pthread_mutex_lockpthread_mutex_unlock。这些实现递归锁。您可以添加自己的代码来实现等效的 boost::scoped_lock 。您的类可以控制语义。

或者,boost有自己的锁。