【问题标题】:Shared locks from multiple threads can starve a single thread looking for an exclusive lock来自多个线程的共享锁会使寻找独占锁的单个线程饿死
【发布时间】:2020-11-16 17:46:59
【问题描述】:

假设我有几个线程从共享内存中读取。所有这些线程都在重复获取共享互斥锁上的共享锁来访问此内存。

假设我有一个线程写入该共享内存并获取独占锁。

如果有足够多的读取线程,可能永远不会有一个时间点,一个写入线程能够获取排他锁,因为共享锁在任何时间点都由多个线程持续持有。

解决这个问题最简单最直接的模式是什么?我正在寻找一种简单直接的方法来解决这个问题,最好使用 STL。

C++ 中的示例实现也会很有帮助。

【问题讨论】:

  • 这能回答你的问题吗? Reader/Writer Locks in C++
  • 赋予写入者优先权的锁。一旦线程开始等待写锁,就不会再发出读锁了。释放所有读锁后,将授予写锁。
  • @MartinYork,stl 中有什么可以做到这一点吗?链接的帖子有一个 C++17 的实现,我相信它仍然会饿死写线程,我宁愿使用现有的预先测试的东西而不是实现我自己的读写锁。
  • @J... 不是真的。线程中有几个不正确的答案,它只关注使用什么样的互斥锁和锁。我已经在这里使用了共享锁,问题是线程饥饿,因为有许多读取线程和一个写入线程。具体来说,如果存在的话,我正在寻找一个简单直接的 stl 预构建解决方案。
  • 令我惊讶的是,该标准没有定义任何方式来指定解决std::shared_mutex 冲突的“策略”,并且它不需要将新读者带入在等待的作家后面排队(即上面@MartinYork 描述的内容。)哦,好吧!这不是我第一次对 C++ 标准感到惊讶。

标签: c++ multithreading locking mutex


【解决方案1】:

这应该可行。

它并不复杂,如果您有多个作家,则不提供任何订单保证。但它应该可以工作。

#include <condition_variable>
#include <mutex>

class RWLock
{
    std::mutex                  lock;
    std::condition_variable     writeWaiters;
    std::condition_variable     readWaiters;

    int                         waitingWriters;
    int                         currentWriters;
    int                         currentReaders;

    public:
        RWLock()
            : waitingWriters(0)
            , currentWriters(0)
            , currentReaders(0)
        {}

    private:
        friend class RGuard;
        void readLock()
        {
            std::unique_lock<std::mutex>     guard(lock);
            readWaiters.wait(guard, [&]{return waitingWriters == 0;});
            ++currentReaders;
        }
        void readUnLock()
        {
            std::unique_lock<std::mutex>     guard(lock);
            --currentReaders;
            if (currentReaders == 0) {
                writeWaiters.notify_one();
            }
        }
    private:
        friend class WGuard;
        void writeLock()
        {
            std::unique_lock<std::mutex>     guard(lock);
            ++waitingWriters;

            writeWaiters.wait(guard, [&]{return (currentReaders != 0 || currentWriters != 0);});
            ++currentWriters;
        }
        void writeUnLock()
        {
            std::unique_lock<std::mutex>     guard(lock);
            --waitingWriters;
            --currentWriters;
            if (waitingWriters != 0) {
                writeWaiters.notify_one();
            }
            else {
                readWaiters.notify_all();
            }
        }
}
class RGuard
{
    RWLock&     lock;
    public:
        RGuard(RWLock& lock)
            : lock(lock)
        {
            lock.readLock();
        }
        ~RGuard()
        {
            lock.readUnLock();
        }
};
class WGuard
{
    RWLock&     lock;
    public:
        WGuard(RWLock& lock)
            : lock(lock)
        {
            lock.writeLock();
        }
        ~WGuard()
        {
            lock.writeUnLock();
        }
};

int main()
{
    RWLock  lock;
    RGuard  guardR(lock);
    WGuard  guardW(lock);
}

【讨论】:

    【解决方案2】:

    我的首选解决方案是CsLibGuarded,您可以选择您的费用。

    我使用了 lr_guarded 以便写入修改一个副本,而读取在另一侧继续,然后当写入完成时,所有新读取都转到修改后的一侧,依此类推。在所有读者离开后,写入也可以修改另一端。

    (未经测试的代码)

    using MapType = std::map<std::string, std::shared_ptr<ComplicatedObject>>;
    lr_guarded<MapType> map;
    
    void MyCache::insert(std::string key, std::shared_ptr<ComplicatedObject> element) {
     m_cache.modify(
     [&key, &element]
     (MapType & map) {
     map.emplace(key, element);
     });
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-25
      • 2010-12-25
      • 2020-08-29
      • 2018-06-16
      • 1970-01-01
      • 2021-02-15
      相关资源
      最近更新 更多