Многопоточность чтения/записи в C++11

Я пытаюсь реализовать решение для читателей-писателей на С++ с помощью std::thread.

Я создаю несколько потоков чтения, которые работают в бесконечном цикле, делая паузу на некоторое время между каждым доступом для чтения. Я попытался воссоздать алгоритм, представленный в книге Таненбаума об операционных системах:

    rc_mtx.lock(); // lock for incrementing readcount
    read_count += 1;
    if (read_count == 1) // if this is the first reader
        db_mtx.lock(); // then make a lock on the database
    rc_mtx.unlock();

    cell_value = data_base[cell_number]; // read data from database
    rc_mtx.lock();
    read_count -= 1; // when finished 'sign this reader off'
    if (read_count == 0) // if this was the last one
        db_mtx.unlock(); // release the lock on the database mutex
    rc_mtx.unlock();

Конечно, проблема в том, что поток, который может удовлетворять условию быть последним читателем (и, следовательно, хочет выполнить разблокировку), никогда не получал db_mtx. Я попытался открыть еще один «материнский» поток, чтобы читатели могли позаботиться о получении и освобождении мьютекса, но во время этого процесса я теряюсь. Если есть элегантный способ решить эту проблему (поток может попытаться освободить мьютекс, который никогда не был получен) элегантным способом, я был бы рад услышать!


person kimsay    schedule 06.06.2016    source источник
comment
Вы правы в том, что разблокировка мьютекса ограничена потоком выполнения, который его заблокировал, иначе он не определен. Я призываю вас переработать это с read_count как std::atomic, что заблокирует автора, если только это не 0   -  person kmdreko    schedule 07.06.2016
comment
Какого фактического поведения вы пытаетесь достичь? Что претендует на db_mtx, и с какой стати вы перебираете два (!) мьютекса, чтобы загрузить одно значение, которое никогда не используете?   -  person Useless    schedule 07.06.2016
comment
Если вы не можете использовать boost::shared_mutex или shared_timed_mutex C++14, вы можете использовать этот, в котором большая часть реализации указана в документе: open-std.org/jtc1/sc22/wg21/docs/papers/2007/   -  person Howard Hinnant    schedule 07.06.2016
comment
@ vu1p3n0x, значит, read_count будет вести себя как семафор?   -  person kimsay    schedule 07.06.2016


Ответы (1)


Вы можете использовать условную переменную для приостановки операций записи, если выполняется чтение, вместо использования отдельной блокировки.

// --- read code
rw_mtx.lock();    // will block if there is a write in progress
read_count += 1;  // announce intention to read
rw_mtx.unlock();
cell_value = data_base[cell_number];
rw_mtx.lock();
read_count -= 1;  // announce intention to read
if (read_count == 0) rw_write_q.notify_one();
rw_mtx.unlock();

// --- write code
std::unique_lock<std::mutex> rw_lock(rw_mtx);
write_count += 1;
rw_write_q.wait(rw_lock, []{return read_count == 0;});
data_base[cell_number] = cell_value;
write_count -= 1;
if (write_count > 0) rw_write_q.notify_one();

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

В С++ 14 вы можете использовать shared_timed_mutex вместо mutex, чтобы получить доступ к нескольким читателям/одному писателю.

// --- read code
std::shared_lock<std::shared_timed_mutex> read_lock(rw_mtx);
cell_value = data_base[cell_number];

// --- write code
std::unique_lock<std::shared_timed_mutex> write_lock(rw_mtx);
data_base[cell_number] = cell_value;

Скорее всего, в следующем стандарте C++ (вероятно, C++17) будет простая реализация shared_mutex.

person jxh    schedule 06.06.2016
comment
Благодарность! Я не знал такого рода мьютексов. Отвечает ли конструктор unique_lock/shared_lock за получение rw_mutex или мне нужно вызывать функцию вроде: lock() / unlock() ? - person kimsay; 07.06.2016
comment
да. Помощники *_lock выполняют блокировку/разблокировку RAII за вас. Вы можете сделать read_lock.unlock(), если хотите вручную изменить состояние блокировки. - person jxh; 07.06.2016