【问题标题】:Read Write lock implementation in C++C++中的读写锁实现
【发布时间】:2016-06-05 22:57:36
【问题描述】:

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

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();
    }
}

代码似乎运行良好,但我有一些概念性问题。

第一季度。我在http://en.cppreference.com/w/cpp/thread/shared_mutex/lock 上看到了使用unique_lock 和shared_lock 的建议,但我不明白为什么,因为shared_mutex 已经支持lock 和lock_shared 方法?

第二季度。此代码是否有可能导致写入饥饿?如果是,那我怎样才能避免饥饿?

第三季度。有没有其他的锁类我可以尝试实现读写锁?

【问题讨论】:

标签: c++ multithreading concurrency locking mutex


【解决方案1】:

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 可能会引起您的兴趣。但逻辑有点不同。

也许,您还可以通过开箱即用的思考找到解决方案:也许有一个合适的无锁数据结构可以通过稍微改变方法来促进读取器和写入器的共存?

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 在每个成员函数中,您在堆栈shared_lock(mylock) 上创建所需锁类型的实例。不需要释放它,它在函数退出时超出范围时被释放。这即使在异常情况下也有效。希望这更像是正确的英语。这是漫长的一天:(
    【解决方案3】:

    第一季度。我在http://en.cppreference.com/w/cpp/thread/shared_mutex/lock 上看到了使用unique_lock 和shared_lock 的建议,但我不明白为什么,因为shared_mutex 已经支持lock 和lock_shared 方法?

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

    第二季度。此代码是否有可能导致写入饥饿?如果是,那么我该如何避免饥饿?

    是的,当然。如果你这样做:

    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 接近于零。这是另一个线程获取锁的窗口。因为窗口太小了,可能拿不到。

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

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

    第三季度。有没有其他的锁类我可以尝试实现读写锁?

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

    或者,boost 有自己的锁。

    【讨论】:

      猜你喜欢
      • 2015-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-28
      • 2010-09-19
      • 1970-01-01
      • 2012-08-15
      相关资源
      最近更新 更多