У меня есть многопоточное приложение, которое должно часто читать некоторые данные, и иногда эти данные обновляются. Прямо сейчас мьютекс сохраняет доступ к этим данным безопасным, но это дорого, потому что я хотел бы, чтобы несколько потоков могли читать одновременно, и блокировать их только тогда, когда требуется обновление (поток обновления может ждать завершения других потоков) .
Я думаю, что это то, что должен делать boost::shared_mutex
, но я не понимаю, как его использовать, и не нашел наглядного примера.
У кого-нибудь есть простой пример, который я мог бы использовать для начала?
6 ответов
Похоже, вы бы сделали что-то вроде этого:
boost::shared_mutex _access;
void reader()
{
// get shared access
boost::shared_lock<boost::shared_mutex> lock(_access);
// now we have shared access
}
void writer()
{
// get upgradable access
boost::upgrade_lock<boost::shared_mutex> lock(_access);
// get exclusive access
boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
// now we have exclusive access
}
1800 ИНФОРМАЦИЯ более или менее верна, но есть несколько проблем, которые я хотел бы исправить.
boost::shared_mutex _access;
void reader()
{
boost::shared_lock< boost::shared_mutex > lock(_access);
// do work here, without anyone having exclusive access
}
void conditional_writer()
{
boost::upgrade_lock< boost::shared_mutex > lock(_access);
// do work here, without anyone having exclusive access
if (something) {
boost::upgrade_to_unique_lock< boost::shared_mutex > uniqueLock(lock);
// do work here, but now you have exclusive access
}
// do more work here, without anyone having exclusive access
}
void unconditional_writer()
{
boost::unique_lock< boost::shared_mutex > lock(_access);
// do work here, with exclusive access
}
Также обратите внимание: в отличие от shared_lock, только один поток может получить upgrade_lock за один раз, даже если он не обновлен (что мне показалось неудобным, когда я столкнулся с ним). Итак, если все ваши читатели - условные писатели, вам нужно найти другое решение.
Начиная с C ++ 17 (VS2015) вы можете использовать стандарт для блокировок чтения-записи:
#include <shared_mutex>
typedef std::shared_mutex Lock;
typedef std::unique_lock< Lock > WriteLock;
typedef std::shared_lock< Lock > ReadLock;
Lock myLock;
void ReadFunction()
{
ReadLock r_lock(myLock);
//Do reader stuff
}
void WriteFunction()
{
WriteLock w_lock(myLock);
//Do writer stuff
}
Для более старой версии вы можете использовать boost с тем же синтаксисом:
#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>
typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > WriteLock;
typedef boost::shared_lock< Lock > ReadLock;
Чтобы добавить еще немного эмпирической информации, я исследовал всю проблему обновляемых блокировок и Пример boost shared_mutex (несколько чтений / одна запись)? - хороший ответ, добавляющий важную информацию о том, что только один поток может иметь upgrade_lock, даже если он не обновлен, что важно, так как это означает, что вы не можете перейти с общей блокировки на уникальную, не сняв предварительно общую блокировку. (Это обсуждалось в другом месте, но самая интересная ветка находится здесь http: / /thread.gmane.org/gmane.comp.lib.boost.devel/214394)
Однако я обнаружил важное (недокументированное) различие между потоком, ожидающим обновления до блокировки (т. Е. Нужно ждать, пока все читатели освободят разделяемую блокировку), и блокировкой писателя, ожидающей того же самого (т. Е. Unique_lock).
Поток, который ожидает unique_lock на shared_mutex, блокирует любые входящие новые читатели, они должны ждать запроса писателей. Это гарантирует, что читатели не умрут писателей голодом (однако я считаю, что писатели могут голодать читателей).
Поток, который ожидает обновления upgradeable_lock, позволяет другим потокам получить разделяемую блокировку, поэтому этот поток может не хватать, если читатели будут очень часто.
Это важный вопрос для рассмотрения и, вероятно, должен быть задокументирован.
Используйте семафор со счетчиком, равным количеству читателей. Пусть каждый читатель сделает один отсчет семафора для чтения, чтобы все они могли читать одновременно. Затем позвольте писателю взять ВСЕ счетчики семафоров перед записью. Это заставляет писатель ждать завершения всех чтений, а затем блокировать чтение во время записи.
Отличный отклик Джима Морриса, я наткнулся на это, и мне потребовалось время, чтобы понять. Вот простой код, который показывает, что после отправки «запроса» на повышение unique_lock (версия 1.54) все запросы shared_lock блокируются. Это очень интересно, поскольку мне кажется, что выбор между unique_lock и upgradeable_lock позволяет нам, хотим ли мы иметь приоритет записи или нет.
Также (1) в сообщении Джима Морриса, кажется, противоречит этому: Увеличьте shared_lock. Предпочитаете читать?
#include <iostream>
#include <boost/thread.hpp>
using namespace std;
typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > UniqueLock;
typedef boost::shared_lock< Lock > SharedLock;
Lock tempLock;
void main2() {
cout << "10" << endl;
UniqueLock lock2(tempLock); // (2) queue for a unique lock
cout << "11" << endl;
boost::this_thread::sleep(boost::posix_time::seconds(1));
lock2.unlock();
}
void main() {
cout << "1" << endl;
SharedLock lock1(tempLock); // (1) aquire a shared lock
cout << "2" << endl;
boost::thread tempThread(main2);
cout << "3" << endl;
boost::this_thread::sleep(boost::posix_time::seconds(3));
cout << "4" << endl;
SharedLock lock3(tempLock); // (3) try getting antoher shared lock, deadlock here
cout << "5" << endl;
lock1.unlock();
lock3.unlock();
}
Похожие вопросы
Связанные вопросы
Новые вопросы
c++
C ++ - это язык программирования общего назначения. Первоначально он был разработан как расширение C и имеет аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде (который должен быть) скомпилирован с помощью компилятора C ++. Используйте тег для конкретной версии для вопросов, связанных с конкретной версией стандарта [C ++ 11], [C ++ 14], [C ++ 17], [C ++ 20] или [C ++ 23] и т. Д. .