提升Shared_lock / Unique_lock,赋予作者优先权?

时间:2018-01-10 11:36:45

标签: c++ multithreading boost

我有一个使用Boost Thread锁定编写的多线程应用程序。

在这种情况下,有一个作家和多个读者。正如我现在所做的那样,作者似乎等待所有读者完成才能再次写作。

我想要的是给作者优先权,这样如果它想再写一次,它就会这样做,无论如何。读者可以解决这个问题。 例如:

现在:

Writer;
reader1;
reader2;
reader3;
reader4;

我想要的是:

Writer;
reader1;
reader2;
Writer(if ready);
reader3;
reader4;

这可能吗?我的代码在下面复制:

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > WriteLock;
typedef boost::shared_lock< Lock > ReadLock;
Lock frameLock;

cv::Mat currentFrame;
bool frameOk;

void writer()
{
    while (true)
    {
        cv::Mat frame;
        cv::Mat src = cv::imread("C:\\grace_17.0001.jpg");
        cv::resize(src, frame, cv::Size(src.cols / 4, src.rows / 4));

        int64 t0 = cv::getTickCount();


        WriteLock w_lock(frameLock);
        frame.copyTo(currentFrame);
        frameLock.unlock();

        frameOk = true; // tells read we have at least one frame

        int64 t1 = cv::getTickCount();
        double secs = (t1 - t0) / cv::getTickFrequency();
        std::cout << "wait time WRITE: " << secs * 1000 << std::endl;
    }
}

void readerTwo(int wait)
{
    while (true)
    {
        if (frameOk) // if first frame is written
        {
            static cv::Mat readframe;

            int64 t0 = cv::getTickCount();


            //gets frame
            ReadLock r_lockz(frameLock);
            currentFrame.copyTo(readframe);
            r_lockz.unlock();

            std::cout << "READ: " << std::to_string(wait)<< std::endl;

            cv::imshow(std::to_string(wait), readframe);
            cv::waitKey(1);
            std::this_thread::sleep_for(std::chrono::milliseconds(20));
        }
    }
}



void main()
{
    const int readerthreadcount = 50;

    std::vector<boost::thread*> readerthread;

    boost::thread* wThread = new boost::thread(writer);


    for (int i = 0; i<readerthreadcount; i++) {
        ostringstream id;  id << "reader" << i + 1;
        readerthread.push_back(new boost::thread(readerTwo, (i)));
    }

    wThread->join(); delete wThread;

    for (int i = 0; i<readerthreadcount; i++) {
        readerthread[i]->join(); delete readerthread[i];
    }

}

谢谢。

2 个答案:

答案 0 :(得分:0)

Writer饥饿是读/写锁的典型问题。

不幸的是,读取器/写入器锁必须按算法和每个架构进行调整。 (至少在开发出更聪明的东西之前。)

  

这可能吗?

是的,这是可能的。有条件变量。有一个计数waitingWriters。当一个写入器进入时,它获取互斥锁,递增waitingWriters,然后等待条件readerCount == 0.当读取器线程结束时,它获取互斥锁,递减readerCount,并在readerCount == 0时发出写入器条件的信号。读者线程进来,它获取互斥锁。如果waitingWriters == 0,则增加readerCount并释放互斥锁。否则等待条件waitingWriters == 0.当编写器线程完成时,它获取互斥锁。如果waitingWriters == 0,则表示读者情况。否则,它会向下一位作者发出信号。

请注意,我刚刚给你的算法:

  1. 现在优先考虑写入读取。这是另一个极端 读取可能是饥饿而不是写入。
  2. 仅使用1个互斥锁。 (不是 读者互斥&amp;作家互斥)
  3. 不适合快速读取,即读取操作比一个调度时间片短的读取。为此你想要使用自旋锁(查看Big Reader)
  4. 调整取决于许多因素,其中最重要的因素是读者与作者线程的比例以及关键部分的持续时间。

答案 1 :(得分:0)

这是我高效高效的写优先共享互斥锁。在最佳情况下,它只需要一个原子交换就可以进行锁定和解锁,这与其他需要两次原子交换的实现相反。

#pragma once
#include <cstdint>
#include <cassert>
#include <thread>
#include <new>
#include <atomic>
#include "semaphore.h"

static_assert(std::atomic<std::uint64_t>::is_always_lock_free, "std::uint64_t must be lock-free");

class alignas(std::hardware_constructive_interference_size) wprio_shared_mutex
{
public:
         wprio_shared_mutex();
         wprio_shared_mutex( wprio_shared_mutex const & ) = delete;
         ~wprio_shared_mutex();
    void lock_shared();
    void unlock_shared();
    void shared_to_write();
    void lock_writer();
    void write_to_shared();
    void unlock_writer();
    bool we_are_writer();

private:
    std::atomic<std::uint64_t> m_atomic; // bit  0 - 20: readers
                                         // bit 21 - 41: waiting readers
                                         // bit 42 - 62: waiting writers
                                         // bit      61: writer-flag
    std::thread::id            m_writerId;
    std::uint32_t              m_writerRecursionCount;
    semaphore                  m_releaseReadersSem,
                               m_releaseWriterSem;

    static unsigned const      WAITING_READERS_BASE   = 21,
                               WAITING_WRITERS_BASE   = 42,
                               WRITER_FLAG_BASE       = 63;
    static std::uint64_t const MASK21                 = 0x1FFFFFu;
    static std::uint64_t const READERS_MASK           = MASK21,
                               WAITING_READERS_MASK   = MASK21           << WAITING_READERS_BASE,
                               WAITING_WRITERS_MASK   = MASK21           << WAITING_WRITERS_BASE,
                               WRITER_FLAG_MASK       = (std::uint64_t)1 << WRITER_FLAG_BASE;
    static std::uint64_t const READER_VALUE           = (std::uint64_t)1,
                               WAITING_READERS_VALUE  = (std::uint64_t)1 << WAITING_READERS_BASE,
                               WAITING_WRITERS_VALUE  = (std::uint64_t)1 << WAITING_WRITERS_BASE;

    static bool check( std::uint64_t flags );
};

inline
bool wprio_shared_mutex::check( std::uint64_t flags )
{
    unsigned readers        = (unsigned)(flags                           & MASK21),
             waitingReaders = (unsigned)((flags >> WAITING_READERS_BASE) & MASK21),
             waitingWriters = (unsigned)((flags >> WAITING_WRITERS_BASE) & MASK21),
             writerFlag     = (unsigned)((flags >> WRITER_FLAG_BASE)     & 1);
    if( readers && (waitingReaders || writerFlag) )
        return false;
    if( waitingReaders && (readers || !writerFlag) )
        return false;
    if( waitingWriters && !(writerFlag || readers) )
        return false;
    if( writerFlag && readers )
        return false;
    return true;
}

wprio_shared_mutex::wprio_shared_mutex()
{
    m_atomic.store( 0, std::memory_order_relaxed );
}

wprio_shared_mutex::~wprio_shared_mutex()
{
    assert(m_atomic == 0);
}

void wprio_shared_mutex::lock_shared()
{
    using namespace std;
    for( uint64_t cmp = m_atomic.load( std::memory_order_relaxed ); ; )
    {
        assert(check( cmp ));
        if( (cmp & WRITER_FLAG_MASK) == 0 )
        [[likely]]
        {
            if( m_atomic.compare_exchange_weak( cmp, cmp + READER_VALUE, memory_order_acquire, memory_order_relaxed ) )
                [[likely]]
                return;
        }
        else
            if( m_atomic.compare_exchange_weak( cmp, cmp + WAITING_READERS_VALUE, memory_order_relaxed, memory_order_relaxed ) )
            [[likely]]
            {
                m_releaseReadersSem.forced_wait();
                return;
            }
    }
}

void wprio_shared_mutex::unlock_shared()
{
    using namespace std;
    for( uint64_t cmp = m_atomic.load( std::memory_order_relaxed ); ; )
    {
        assert(check( cmp ));
        assert((cmp & READERS_MASK) >= READER_VALUE);
        if( (cmp & READERS_MASK) != READER_VALUE || (cmp & WAITING_WRITERS_MASK) == 0 )
        [[likely]]
        {
            if( m_atomic.compare_exchange_weak( cmp, cmp - READER_VALUE, memory_order_relaxed, memory_order_relaxed ) )
                [[likely]]
                return;
        }
        else
        {
            assert(!(cmp & WRITER_FLAG_MASK));
            if( m_atomic.compare_exchange_weak( cmp, (cmp - READER_VALUE - WAITING_WRITERS_VALUE) | WRITER_FLAG_MASK, memory_order_relaxed, memory_order_relaxed ) )
            [[likely]]
            {
                m_releaseWriterSem.forced_release( 1 );
                return;
            }
        }
    }
}

void wprio_shared_mutex::shared_to_write()
{
    using namespace std;
    for( uint64_t cmp = m_atomic.load( std::memory_order_relaxed ); ; )
    {
        assert(check( cmp ));
        assert((cmp & READERS_MASK) >= READER_VALUE);
        if( (cmp & READERS_MASK) == READER_VALUE )
        [[likely]]
        {
            assert(!(cmp & WRITER_FLAG_MASK));
            if( m_atomic.compare_exchange_weak( cmp, (cmp - READER_VALUE) | WRITER_FLAG_MASK, memory_order_acquire, memory_order_relaxed ) )
            [[likely]]
            {
                m_writerId             = this_thread::get_id();
                m_writerRecursionCount = 0;
                return;
            }
        }
        else
        {
            assert((cmp & READERS_MASK) > READER_VALUE);
            if( m_atomic.compare_exchange_weak( cmp, cmp - READER_VALUE + WAITING_WRITERS_VALUE, memory_order_relaxed, memory_order_relaxed ) )
            [[likely]]
            {
                m_releaseWriterSem.forced_wait();
                m_writerId             = this_thread::get_id();
                m_writerRecursionCount = 0;
                return;
            }
        }
    }
}

void wprio_shared_mutex::lock_writer()
{
    using namespace std;
    uint64_t cmp = m_atomic.load( std::memory_order_acquire );
    if( (cmp & WRITER_FLAG_MASK) && m_writerId == this_thread::get_id() )
    {
        ++m_writerRecursionCount;
        return;
    }
    for( ; ; )
    {
        assert(check( cmp ));
        if( (cmp & (WRITER_FLAG_MASK | READERS_MASK)) == 0 )
        [[likely]
        {
            if( m_atomic.compare_exchange_weak( cmp, cmp | WRITER_FLAG_MASK, memory_order_acquire, memory_order_relaxed ) )
            [[likely]
            {
                m_writerId             = this_thread::get_id();
                m_writerRecursionCount = 0;
                return;
            }
        }
        else
            if( m_atomic.compare_exchange_weak( cmp, cmp + WAITING_WRITERS_VALUE, memory_order_relaxed, memory_order_relaxed ) )
            [[likely]]
            {
                m_releaseWriterSem.forced_wait();
                m_writerId             = this_thread::get_id();
                m_writerRecursionCount = 0;
                return;
            }
    }
}

void wprio_shared_mutex::unlock_writer()
{
    using namespace std;
    uint64_t cmp = m_atomic.load( std::memory_order_relaxed );
    if( (cmp & WRITER_FLAG_MASK) && m_writerRecursionCount && m_writerId == this_thread::get_id() )
    {
        --m_writerRecursionCount;
        return;
    }
    m_writerId = thread::id();
    for( ; ; )
    {
        assert(cmp & WRITER_FLAG_MASK && !(cmp & READERS_MASK));
        assert(check( cmp ));
        if( (cmp & WAITING_WRITERS_MASK) != 0 )
            [[unlikely]]
            if( m_atomic.compare_exchange_weak( cmp, cmp - WAITING_WRITERS_VALUE, memory_order_release, memory_order_relaxed ) )
            [[likely]]
            {
                m_releaseWriterSem.forced_release( 1 );
                return;
            }
            else
                continue;
        if( (cmp & WAITING_READERS_MASK) != 0 )
        [[unlikely]]
        {
            uint64_t wakeups = (cmp & WAITING_READERS_MASK) >> WAITING_READERS_BASE;
            if( m_atomic.compare_exchange_weak( cmp, (cmp & ~WRITER_FLAG_MASK) - (cmp & WAITING_READERS_MASK) + wakeups, memory_order_release, memory_order_relaxed ) )
            [[likely]]
            {
                m_releaseReadersSem.forced_release( (unsigned)wakeups );
                return;
            }
            else
                continue;
        }
        if( m_atomic.compare_exchange_weak( cmp, 0, memory_order_release, memory_order_relaxed ) )
            [[likely]]
            return;
    }
}

bool wprio_shared_mutex::we_are_writer()
{
    return (m_atomic.load( std::memory_order_relaxed ) & WRITER_FLAG_MASK) && m_writerId == std::this_thread::get_id();
}

该算法允许连续的读取器,但是一旦写入器注册进行写入,就会将其他读取器排入队列,并等待当前读取器完成。这都是通过单个64位原子值完成的!

该代码允许读者和读者递归。但是,当您多次阅读时,不应执行shared_to_write();。那你就会陷入僵局。递归的能力是天生的共享阅读功能,没有额外的开销。但是对于编写来说,还有一个额外的递归计数器以及一个thread :: id。

我不会在这里包括我的信号量类,因为它应该是不言自明的。在我的信号量类中,我有forced_wait和forced_release;这是两个函数,它们在失败时会反复进行等待或释放。

[[可能]]-和[[不太可能]]标签是C ++ 20优化提示。您可以使用早期的编译器将其删除。 we_are_writer-method检查当前线程是否具有写所有权。这可以用于用于使用assert()进行调试的目的。

shared-mutex通过alignas()指令与缓存行对齐。但是,由于对象末尾的两个信号量,整个对象本身可能大于缓存行。但是,短锁定路径的数据位于适合高速缓存行的标头中。如果对象末尾的信号量不适合同一缓存行,也不会造成伤害,因为困锁很慢。

该对象既不可复制,也不可移动,因为信号量也可能不是。这可能是因为POSIX信号量依赖于不可复制的sem_t数据类型,我可以直接将其嵌入C ++信号量数据类型中,从而使其成为非复制或可移动的。