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

Я думаю, что это то, что должен делать boost::shared_mutex, но я не понимаю, как его использовать, и не нашел наглядного примера.

У кого-нибудь есть простой пример, который я мог бы использовать для начала?

118
kevin42 13 Июн 2009 в 06:38

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
}
104
1800 INFORMATION 7 Ноя 2012 в 22:47

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 за один раз, даже если он не обновлен (что мне показалось неудобным, когда я столкнулся с ним). Итак, если все ваши читатели - условные писатели, вам нужно найти другое решение.

172
dalle 19 Мар 2013 в 09:12

Начиная с 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;
49
Yochai Timmer 18 Авг 2019 в 05:10

Чтобы добавить еще немного эмпирической информации, я исследовал всю проблему обновляемых блокировок и Пример boost shared_mutex (несколько чтений / одна запись)? - хороший ответ, добавляющий важную информацию о том, что только один поток может иметь upgrade_lock, даже если он не обновлен, что важно, так как это означает, что вы не можете перейти с общей блокировки на уникальную, не сняв предварительно общую блокировку. (Это обсуждалось в другом месте, но самая интересная ветка находится здесь http: / /thread.gmane.org/gmane.comp.lib.boost.devel/214394)

Однако я обнаружил важное (недокументированное) различие между потоком, ожидающим обновления до блокировки (т. Е. Нужно ждать, пока все читатели освободят разделяемую блокировку), и блокировкой писателя, ожидающей того же самого (т. Е. Unique_lock).

  1. Поток, который ожидает unique_lock на shared_mutex, блокирует любые входящие новые читатели, они должны ждать запроса писателей. Это гарантирует, что читатели не умрут писателей голодом (однако я считаю, что писатели могут голодать читателей).

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

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

17
Community 23 Май 2017 в 12:02

Используйте семафор со счетчиком, равным количеству читателей. Пусть каждый читатель сделает один отсчет семафора для чтения, чтобы все они могли читать одновременно. Затем позвольте писателю взять ВСЕ счетчики семафоров перед записью. Это заставляет писатель ждать завершения всех чтений, а затем блокировать чтение во время записи.

2
R Virzi 20 Ноя 2010 в 07:31

Отличный отклик Джима Морриса, я наткнулся на это, и мне потребовалось время, чтобы понять. Вот простой код, который показывает, что после отправки «запроса» на повышение 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();
}
2
Community 23 Май 2017 в 12:26