【问题标题】:Reader Writer Lock supporting low priority writers支持低优先级写入器的读取器写入器锁
【发布时间】:2011-02-22 16:48:04
【问题描述】:

我正在尝试寻找(或实施)支持低优先级写入器的读取器/写入器锁,但在研究任何现有解决方案时均未成功。

我所说的低优先级写入者的意思是:“将让位”给新来的读者或普通写入者。

如果有源源不断的读取器肯定会导致饥饿,但这可以通过定时锁变体(“尝试定时低优先级写入器锁”,然后在超时时切换到普通锁)或通过更改来解决读取器的发布方式(可能会定期停止读取一小段时间)。

如果有任何文献描述了这些内容,我还没有找到。

如果有利用常规锁的已知(正确!)解决方案,我将不胜感激。

【问题讨论】:

  • 我很想知道现有的实现,但如果没有,我能想到的最简单的方法是为低优先级查询创建一个单独的队列,定期合并到主队列。它似乎与您提到的定时锁没有太大区别,只是即使队列为空也会有延迟 - 如果需要,解决此问题的一种方法是添加“空队列”事件。

标签: c++ concurrency mutex readerwriterlock


【解决方案1】:

我不知道 100% 喜欢你的提议,但有一些现有的接口很接近:

许多现有的读/写锁定 API 都有一个“尝试锁定”接口,例如 UN*X 系统上的pthread_rwlock_trywrlock()。这些是免等待的,只有在没有人拥有它或已经等待它的情况下才会获取锁。

您通常会使用这种旋转来锁定,和/或人为地延迟尝试代码(回退)。 IE。有这样的代码:

for (count = 0; count < MAX_SPINS && (locked = trywrlock(lock)); count++);
if (locked) {
    delay(some_backoff);
    wrlock(lock);         /* or try spinning loop above again ? */
}

很遗憾,这并不是您所要求的;它会延迟锁定,但低优先级写入器将在 CPU 上旋转和/或由于(可能不必要的)退避而以延迟的方式获取它。

Solaris 内核有一个接口rw_tryupgrade(9f),可用于测试当前线程是否是锁上的唯一读取器,没有写入器等待,如果是,则将锁升级为独占/写入,即您将编写代码喜欢:

if (!rw_tryenter(lock, RW_WRITER)) {
    rw_enter(lock, RW_READER);    /* this might wait for a writer */
    if (!rw_tryupgrade(lock)) {   /* this fails if >1 reader / writer waiting */
        /* do what you must to if you couldn't get in immediately */
    }
}

这有点接近您的要求,但仍然不完全相同 - 如果失败,您必须放弃 readlock,可能后退(等待),重新获取 readlock,尝试升级。该过程再次相当于旋转。

此外,许多 UNIX 系统实际上至少按照调度优先级的顺序执行等待者唤醒;因此,在尝试普通的等待wrlock() 调用之前,您必须使线程的优先级最低(如果必要的话,人为地);根据调度程序的工作方式,在您的线程等待时想要相同的写锁的其他人将在此之前获得它。虽然在不一定保证的多处理器/核心系统上......

最后,SymbianOS(Symbian^3 版本)有一个RRWlock 类,可以使读者优先于写者,这样如果有读者等待/进来,它就会故意让写者饿死。同样,不完全准确您想要的行为,因为它会影响给定锁上的所有作者,而不仅仅是特定的。

恐怕您必须编写自己的优先级锁,并带有两个写入器唤醒队列。

【讨论】:

  • 感谢这个非常详细的回复。你的一些想法(有延迟的自旋锁)是我最初的想法,虽然我不知道你的很多例子。
  • 请参阅home.roadrunner.com/~hinnant/mutexes/locking.html,了解一些在 C++ 中实现读/写锁的开源代码。不幸的是,这个链接也没有解决原始问题。但它确实解决了与此答案中提出的问题非常相似的问题。此实现中的读/写优先级是“公平”。
【解决方案2】:

这里你看到的是读者和作者之间的优先级,这样低优先级的作者总是给高优先级的读者第一次机会。由于它的慷慨,这显然可能导致低优先级写入者的饥饿。

这可以在两个不同的层次上实现: 1.从应用方面: 这是一种常用的方法,因为互斥锁通常没有偏见。 在线程争夺锁定之前,应用程序逻辑应该自己决定哪个线程具有更高的优先级并允许该线程去锁定。 这通常成为特定于应用程序的:

--> 任务执行者方法: 执行者线程仅根据优先级执行可用任务。 它解决了执行者级别的基于优先级的执行,但同样的问题在该级别之上弹出。这可以作为基于 FIFO 的长互斥锁实现。这也需要解决饥饿问题。

  1. 编写一个偏向锁定机制,它可以理解优先级并允许更高优先级的线程锁定。这也需要保证“不挨饿”。 可以实现一个临时版本的互斥锁。

【讨论】:

    【解决方案3】:

    在我的脑海中,你会想要这样的东西:

    class PriorityRWLock
    { 
      int                rwcount;
      mutex              mtx;
      condition_variable cv;
      priority_queue<writer_info> writers;
    };
    

    ... 和 PriorityRWLock::acquire_write_lock() 看起来像:

    lock_guard raii(mtx);
    
    do {
    if ( rwcount==0 ) // == no read or write locks
    {
      if ( writers.top().thread == thread::self() )
      {  rwcount = -1; writers.pop_front(); break; } // == exclusive write access
      else 
      { 
         // wake up the waiting thread(s) and sleep
         writers.push( writer_info(thread::self(),priority) ); 
         cv.notify_all();
         cv.wait(mtx); 
      }
    }
    else
    { cv.wait(mtx); }  // sleep
    } while ( true );
    

    ...或类似的东西。

    它不会过于高效。您确实更愿意将 rwcount 存储在 atomic_int 或类似的文件中,但对 condition_variable 的需求排除了这一点。

    由于可能需要间歇地等待(),因此定时锁定会很棘手。 try_write_lock() 应该是可行的。

    【讨论】:

    • 糟糕...忘记了最重要的 writers.push(...) 在顶部。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多