2013-03-21 2 views
3

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

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

Для этого у нас есть класс под названием GuardedData. Вызывающий может запросить такой объект и должен хранить его только в течение короткого времени в локальной области. Пока объект живет, он держит замок. Как только объект уничтожается, блокировка освобождается. Доступ к данным связан с механизмом блокировки без какой-либо конкретной дополнительной работы в вызывающем устройстве. Название класса напоминает звонящего настоящего охранника.

template<typename T, typename Lockable> 
class GuardedData { 
    GuardedData(T &d, Lockable &m) : data(d), guard(m) {} 
    boost::lock_guard<Lockable> guard; 
    T &data; 

    T &operator->() { return data; } 
}; 

Опять же, очень простая концепция. Оператор-> имитирует семантику итераторов STL для доступа к полезной нагрузке.

Теперь интересно:

  • Является ли этот подход хорошо известен?
  • Возможно ли, что шаблонный класс, подобный этому, уже доступен, например. в ускорительных библиотеках?

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

+1

У меня есть данные, которые можно изменить в фоновом режиме во время доступа к нему на переднем плане. Фактически, данные меняются местами при завершении расчета фона. Этот обмен не должен происходить, когда кто-то обращается к данным, что может привести к тому, что эта сторона окажется в противоречивом состоянии. Нам нужно сделать своп вместо того, чтобы просто создавать новый экземпляр, в то время как другие все еще используют старый экземпляр. Причина в том, что огромное количество данных будет постоянно находиться в памяти. – ypnos

ответ

1

Хорошо известно, я не уверен. Тем не менее, я использую аналогичный механизм в Qt, довольно часто называемый QMutexLocker. Различие (второстепенное, imho) заключается в том, что вы связываете данные вместе с мьютексом. Очень похожий механизм с тем, который вы описали, является нормой для синхронизации потоков в C#.

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

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

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

+0

Я думаю, что QMutexLocker - это то же самое, что и boost :: lock_guard. Они оба являются обертками RAII/RRID для мьютекса aquire/release. – Pete

2

В зависимости от того, как это используется, вы почти наверняка окажетесь в тупике в какой-то момент. Если вы хотите работать с двумя частями данных, вы в конечном итоге блокируете мьютекс дважды и запираетесь (если только каждый фрагмент данных имеет свой собственный мьютекс), что также приведет к тупиковой ситуации, если порядок блокировки не будет согласован - вы не можете контролировать что с этой схемой, не делая ее действительно сложной). Если вы не используете рекурсивный мьютекс, который может быть нежелательным.

Также, как ваши объекты GuardedData прошли?boost :: lock_guard не копируется - он вызывает проблемы с правами на мьютекс, то есть где &, когда он выпущен.

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

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

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

+0

Итак, вторая итерация должна была бы использовать boost :: recursive_mutex вместо lock_guard. – ypnos

+0

Объект данных может составлять несколько гигабайт, поэтому важно избегать избыточных копий. – ypnos

+0

Вам нужно копировать все данные каждый раз? Разрешить внешним клиентам управлять блокировкой, вероятно, закончится проблемами. – Pete

Смежные вопросы