Простая реализация блокировки чтения / записи без голодания

#c #multithreading #locking #mutex

#c #многопоточность #блокировка #мьютекс

Вопрос:

Я создал блокировку чтения / записи и тестировал ее, не сталкиваясь с какими-либо проблемами. Это было сделано, чтобы избежать голодания писателя, но я считаю, что это работает и против голодания читателя. Я видел альтернативы в Интернете, но мне было интересно, является ли это надежной реализацией.

Если вы используете обычный общий мьютекс, новые действия чтения все равно могут быть поставлены в очередь, что предотвратит обработку действий записи, пока присутствует какое-либо действие чтения. Это приведет к голоданию. Я использовал второй мьютекс, который будет заблокирован процессом записи и предотвратит постановку в очередь любых новых процессов чтения. Спасибо!

 class unique_priority_mutex
{
public:

    void lock_shared(void)
    {
        // If there is a unique operation running, wait for it to finish.
        if( this->_is_blocked ){
            // Use a shared lock to let all shared actions through as soon as the unique action finishes.
            std::shared_lock<std::shared_mutex> l(this->_unique_mutex);
        }

        // Allow for multiple shared actions, but no unique actions.
        this->_shared_mutex.lock_shared();
    }

    void unlock_shared(void)
    {
        this->_shared_mutex.unlock_shared();
    }

    void lock(void)
    {
        // Avoid other unique actions and avoid new shared actions from being queued.
        this->_unique_mutex.lock();

        // Redirect shared actions to the unique lock.
        this->_is_blocked = true;

        // Perform the unique lock.
        this->_shared_mutex.lock();
    }

    void unlock(void)
    {
        this->_shared_mutex.unlock();
        this->_is_blocked = false;
        this->_unique_mutex.unlock();
    }

    std::shared_mutex _shared_mutex;
    std::shared_mutex _unique_mutex;
    std::atomic<bool> _is_blocked = false;
};
 

Комментарии:

1. Это больше похоже на тему для обзора кода , чем на переполнение стека, поскольку вы, по сути, опубликовали то, что, по вашему утверждению, является рабочим кодом, и просите общего отзыва. Если в ходе тестирования этого кода вы обнаружите, что он работает не так, как ожидалось, тогда это будет уместным вопросом, который можно задать здесь, при условии, что вы подкрепите его методологией тестирования, выводами и актуальным вопросом.

2. Ваш тест _is_blocked в lock_shared() конкурирует с вашим назначением его в lock() . Этот промах помешал бы ему выполнять любые std::shared_lock<std::shared_mutex> l(this->_unique_mutex); средства; и, по-видимому, понижает ваше утверждение до » редко голодает «.

3. @mevets Спасибо за ответ. Некоторые запросы на чтение действительно могут передавать запрос на запись, поэтому порядок обработки не гарантируется, но если я прав, как только эти дополнительные запросы на чтение будут обработаны, процесс записи может сделать свое дело.

Ответ №1:

Если я правильно понимаю ваш код, то это эквивалентно:

 1    my_shared_lock(Lock *p) {
2        if (p->_is_blocked) {
3             exclusive_lock(amp;p->x);
4        }
5        shared_lock(amp;p->s);
6    }
7    
8    my_exclusive_lock(Lock *p) {
9        exclusive_lock(amp;p->x);
10       p->_is_blocked = 1;
11       shared_lock(amp;p->s);
12   } 
 

Если у вас выполняются два потока, и это отображает их прогресс:

 T1:   1  2  4  5
T2: 8 9     10 11
 

Ключ в том, что тест в строке 2 может выполняться в одном потоке между выполнением строк 9 и 10 в другом; поэтому строка 3 пропускается.
Тогда T1 думает, что имеет my_shared_lock , и T2 думает, что имеет my_exclusive_lock одновременно, что хуже, чем голодание.

Комментарии:

1. Я не думаю, что это то же самое, что я использую два общих мьютекса, которые используют общую блокировку в первой функции и обычную блокировку во второй. Кроме того, я считаю, что если в вашем коде есть гонка к строкам 5 и 11, только один поток заблокирует этот мьютекс, и, следовательно, только одному будет разрешено продолжить. Верно?