来自多个线程的共享锁会使寻找独占锁的单个线程饿死

Posted

技术标签:

【中文标题】来自多个线程的共享锁会使寻找独占锁的单个线程饿死【英文标题】: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++ 标准感到惊讶。 【参考方案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);
 );

【讨论】:

以上是关于来自多个线程的共享锁会使寻找独占锁的单个线程饿死的主要内容,如果未能解决你的问题,请参考以下文章

线程同步机制和三个线程不安全例子

Java多线程-线程同步简述

深入理解AQS(二)- 共享模式

使用读写锁实现线程同步

CAS基础

Java的锁:公平锁,非公平锁,可重入锁,自旋锁,独占锁(写锁) / 共享锁(读锁) / 互斥锁...