Порядок блокировки shared_mutex - PullRequest
7 голосов
/ 11 мая 2019

У меня сложилось впечатление, что шаблон множественного чтения / записи, реализованный с помощью std::shared_mutex в c ++ 17, потенциально никогда не сможет отказаться от уникальных блокировок, если будет получено слишком много общих блокировок.

После поиска по cppreference я не уверен, что это так. В частности:

Все операции блокировки и разблокировки одного мьютекса выполняются в одном общий заказ

Например, учитывая следующие операции над shared_mutex, я полагал, что unique_lock может никогда не получить. Предполагая бесконечное количество shared_locks, и что эти блокировки приобретаются до первого shared_locks выпуска.

shared_lock
shared_lock
shared_lock

unique_lock

shared_lock
[...]
shared_lock

Дает следующие характеристики.

{ shared_lock, shared_lock, shared_lock, shared_lock, ..., shared_lock } // never releases

unique_lock

Однако, если я правильно понимаю cppreference, как только unique_lock попытается получить, последовательный shared_locks будет блокироваться, пока unique_lock не будет выпущен. Придание следующих характеристик резьбы.

{ shared_lock, shared_lock, shared_lock} // simultaneous

unique_lock

{ shared_lock, ..., shared_lock} // waits, then simultaneous

Итак, мой вопрос: std::shared_mutex продолжает упорядочивать между общими и уникальными блокировками? Предотвращение случая, когда unique_locks никогда не приобретается из-за подавляющего количества shared_locks приобретаемого.

редактировать:

Вот пример кода, который поможет понять проблему и ради потомков. На MSVC 2019 shared_mutex является безопасным, и заказ происходит по желанию. unique_lock обрабатывается до "бесконечной" суммы shared_locks.

Теперь возникает вопрос, зависит ли эта платформа?

#include <chrono>
#include <cstdio>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <vector>

using namespace std::chrono_literals;

std::shared_mutex smtx;

int main(int, char**) {

    std::vector<std::thread> threads;

    auto read_task = [&]() {
        std::shared_lock l{ smtx };
        printf("read\n");
        std::this_thread::sleep_for(1s);
    };

    auto write_task = [&]() {
        std::unique_lock l{ smtx };
        printf("write\n");
        std::this_thread::sleep_for(1s);
    };

    // Create a few reader tasks.
    threads.emplace_back(read_task);
    threads.emplace_back(read_task);
    threads.emplace_back(read_task);


    // Try to lock a unique_lock before read tasks are done.
    std::this_thread::sleep_for(1ms);
    threads.emplace_back(write_task);

    // Then, enque a gazillion read tasks.
    // Will the unique_lock be locked? [drum roll]

    // Would be while(true), 120 should be enough for demo
    for (size_t i = 0; i < 120; ++i) {
        std::this_thread::sleep_for(1ms);
        threads.emplace_back(read_task);
    }

    for (auto& t : threads) {
        t.join();
    }
}

Выходы:

read
read
read
write
read
...
read

1 Ответ

2 голосов
/ 12 мая 2019

В спецификации std shared_mutex не указывается приоритет для общих и уникальных блокировок. Также нет API для установки такого приоритета. Одним из первоначальных мотивов отсутствия спецификации приоритета является наличие алгоритма Александра Терехова, как объяснено здесь .

Вторичной мотивацией является объяснение отсутствия читателя-писателя приоритетные политики в shared_mutex. Это связано с алгоритмом зачислен на Александра Терехова, который позволяет ОС решать, какой поток следующий, чтобы получить замок, не заботясь, является ли уникальный замок или общая блокировка ищется. Это приводит к полному отсутствию читателя или писатель голодает. Это просто честно.

Стандартная спецификация не требует алгоритма Александра Терехова. Однако, по крайней мере, я надеялся, что этот алгоритм предпочтительнее из-за отсутствия спецификации или API для предпочтения читателей над писателями или наоборот.

Более подробная информация об алгоритме Александра Терехова и некотором коде, демонстрирующем его поведение в этом SO-ответе здесь .

...