Win VC++ Lock-free компонент

Здраствуйте ув. комьюнити,

Разрабатываю сабж, к сожалению опыта в написание лок-фри компонентов нет, а то что прочитал как то не уверенно засело в моей голове. Так что помогите плиз кто в теме.

Судя по msdn, доступ к volotile переменной(начиная с компилятора VC++ 2005) является как бы мемори барьером как с точки зрения кпу та и сточки зрения компилятора. Правилильно ли я понимаю, если у меня есть два потока ридер и врайтер, то если я напишу что то вроде:

Reader Thread:
void someClass::renderSomething(){
    if(sync){
        renderer->renderSomething(data->getSomething());
    }
}

Writer Thread:
void someClass::changeSomething(){
    data->changeSomething();
    sуnc=1;
}

(Преполагая что sуnc volotile переменая, а data глобальная переменая или мембер класа) то таким образом сам доступ к volotile переменой в порядке описаном выше, гарантирует видимость изменений, сделаных в врайтер потоке, ридер потоку?

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

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

Если у вас есть 2+ потока работающие с одной и той же переменной и эта работа посложнее, чем в примере выше, то должна быть синхронизация. Как минимум, не «sync = 1», а «InterlockedExchange(&sync, 1)» и т.д.

Варианты посложнее будут уже не lock-free.

На .net есть такая магическая штука — ReaderWriterLock: UpgradeToWriterLock, DowngradeFromWriterLock а также Asquire[смотря что] и Release[смотря что] (если я правильно понимаю проблему)

msdn.microsoft.com/...k(v=vs.71).aspx

Вроде, подобные блокировки — это обертка над неуправляемым кодом, который реализован в ядре (так, как и mutex), так что по-идее, этот инструментарий есть и без фреймворка.

В общем, пока лок на чтение не будет снят — запись не произведется. И наоборот — пока залочено на запись — чтение будет ожидаться.

С его помощью, ты гарантируешь в текущий момет времени либо только чтение, либо только запись.
ReaderWriterLock Class

Defines a lock that supports single writers and multiple readers. Как раз для синхронизации чтения-записи между несколькими конкурентными потоками.

В принципе, он так и работает, как у вас пример. Только вот мне очень кажется, что нужно в потоке врайтера делать sуnc=0 перед data->changeSomething()

Native API версия Reader/Writer Lock API попроще .NET’овской, более ограниченная в возможностях, без низлежащих kernel объектов, но за то и с определенно с меньшим overhead’ом.

Судя по msdn, доступ к volotile переменной(начиная с компилятора VC++ 2005) является как бы мемори барьером как с точки зрения кпу та и сточки зрения компилятора.

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

В IA-32 архитектуре чтение и запись 32-битового слова по выравненным границам адресов на размер этого слова являются атомарными.

Правилильно ли я понимаю, если у меня есть два потока ридер и врайтер, то если я напишу что то вроде:

Нет. Для lock-free в обоих потоках нужно делать Compare-And-Exchange блокирующую атомарную операцию.

Как пример:

msdn.microsoft.com/...0(v=vs.85).aspx

Смотреть InterlockedCompareExchange(). Пример: stackoverflow.com/...ocked-variables .

помойму вы недопоняли вопроса, мне не нужны атомарные операции, меня вобще не интересует значение волотиль переменой, я хочу использовать ее как мемори барьер.
msdn.microsoft.com/...d(v=vs.80).aspx

Если посмотрите на пример, то увидете что CriticalData не является волотильной и Interlocke* функции к ней не применяются

Мда уж изврат ещё тот.

Если внимательно прочитать написанное по той ссылке то да приведённый тобой код коррктен. Но обрати внимание на то что сработает оно как барьер лиш один раз и «назад пути нет». Елси надо посылать это сообщение регулярно таки без InterlockedCompareExchange() никак.

ЗЫ обрати внимание что такое использование volatile Майкрософт специфик.
ЗЫЫ а зачем ты пишеш лок-фри компонент? цель какая?

Ну наверное, я зря сделал, что осветил проблему без ее контекста. Итак я хочу сделать чета вроде RCU.
— Есть один поток(назовем ридер), который создает графический елемент(назовем компонент) и соовтествено в нем крутится диспатчер виндовых ивентов и соответсвено он рендерерит этот самый компонент.
— Существует некий объект данных(назваем датасорс) который рендерится компонентом.
— В любой момент при нажатии кнопки(это только для примера) создается поток(назовем врайтер) который может либо добавить данные в датасорс, либо сделать абсолютно новый датасорс(грубо говоря удалить все и записать новое), но не удалить оттуда данные. Данные записываемые врайтером не статические и меняются в зависимоти от каких то внешних раздражителей(неважно). Тоесть допусти кнопка 1 нажата — создался врайтер, заснул, нажата кнопка 2, врайтер проснулся запихал данные, заснул и опять нажата кнопка 2, врайтер проснулся запихал данные и т.д.
Таких врайтеров может быть несколько, но чтоб не усложнять пусть будет пока что один.
Итак данные в датасорсе могут быть использованны для отрисовки на екран в первом потоке, и в тот же самый момент(паралельно) данные могут писаться в датасорс вторым потоком. В бусте к примеру есть замечательная библиотека для работы с мутексами, но мне не хотелось бы использовать мутексы в потоке который отрисовывает UI, поэтому я так сказать и кам ап виз айдиа чтобы создать мультиверсионый датасорс и перед отрисовкой брать текущую версию данных и работать только с ней, в тоже самое время второй поток может свободно создатать новую версию данных и заполнить ее, эта новая версия данных подхватится ридером в следущий раз при чтении.

Надеюсь ясно расписано.

Датасорс — это модель, которая описывает свойства сцены, типа? А ридер ее читает, и на канвас чего-то помещает, в зависимости от модели? Я вот когда одну игрушку на XNA делал, у меня было именно так, и без всяких потоков ненужных: за один тик модель пересчитывала свои свойства. А потом сразу включался визуализатор, который хавал модель, стирал весь канвас, и рисовал на нем графику, в зависимости от свойств в модели. И визуализация на XNA (он же managed direct x, он же просто обвертка над неуправляемым directx) шла асинхронно (у нас не было задержек, это обеспечивал DirectX). И за счет того, что мы сначала изменяет свойства в модели, а уже ПОСЛЕ их растеризуем — блокировок никаких не нужно, и графика рывками из-за непонятно каких блокировок не дергается, и модель и визуализатор (так и хочется написать — растеризатор :) - в одном потоке, из — за двойного буфера мы не ощущаем, что перерисовываем всю сцену с нуля. Поставить если таймер тиков, скажем, на 1мс, а по тику менять свойства в модели, все плавно и хорошо. Хотя, конечно, смотря насколько быстро пересчитывается сама модель за один тик. В идеале — для этой задачи можно обойтись без блокировок, если достичь очень быстрый пересчет модели. Опасно использовать блокировки при визуализации сцены, ИМХО
Есть же готовое решение, которое применяется в игровой индустрии: шаблон MVC (и его производные): диспетчер ивентов крутится в контроллере, в зависимости от ивентов меняются свойства в модели (или подставляется другая модель, и ее соответствующий визуализатор), состояние модели (ее свойства, доступные для чтения визуализатором) меняется по тикам таймера, синхронно с ним — она и визуализируется. Естественно, мгновенно визуализироваться она не будет, это процесс — асинхронный (вызвали метод перерисовать канвас, он мгновенно сработал, а сама картинка появится может через 10, или 50 мс), но это вас волновать не должно, т.к. в directx, как я писал, хоть я по нему и не скажу, что спец (может, это только в XNA предустановленная асинхронная визуализация канвы, а на самом деле есть свойство, делающее ее синхронной) это было прозрачно и незаметно.

Например, мы хотим сделать очень плавное перемещение чего-то, по кнопкам 8,2,4,6. В модели вводим координаты X,Y, и свойство (типа enum) олицетворяющее целевой перемещаемый объект. Вот у нас в непрерывном цикле КОНТРОЛЛЕР-МОДЕЛЬ-ВИЗУАЛИЗАТОР в таком порядке вызова. Нажали на 8 — контроллер дернул модель — изменил X. Нажали на 4 — контроллер дернул модель — изменил Y. В модели мы уже там и забиваем, например, диапазон допустимых координат (т.е. в ней мы моделируем физические процессы, которые происходят в мире сцены). Таймер тикнул — контроллер попытался изменить состояние модели — модель пересчиталась, визуализатор стер канву, посмотрел что за тип у целевого объекта, в зависимости от типа получил соотв. ссылку на его изображение, прочитал из модели X и Y, и по этим координатам разместил на канве. После того, как он прошелся по всем свойствам модели — дает команду перерисовать канву — и дальше в контроллер. Канва перерисуется асинхронно. Вы можете использовать хоть битмап для визуализации, хоть в 3Dmax нарисовать картинку,ее конвертнуть в соотв.векторный формат, поддержка которого есть в DirectX — цикл входа в контроллер, выходящий из визуализатора займет столько же времени, если, конечно, мешей не так много (т.к. асинхронная растеризация) По-моему, это-элегантное решение.

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

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

Если отказываются от mutex, то делают это по разным причинам, но в данном случае я не увидел ни одной. Конкуренция потоков минимальна. Обычно от мьютексов избавляются, чтобы ускорить доступ к shared объектам и загрузить все используемые процессоры по максимуму.

Вот тут доступно описаны методы:

www.1024cores.net/...free-algorithms

помойму вы недопоняли вопроса, мне не нужны атомарные операции, меня вобще не интересует значение волотиль переменой, я хочу использовать ее как мемори барьер.
Для того, чтобы использовать её как барьер (хотя это и не будет memory barrier’ом) операции с этой переменной должны быть атомарные.
Если посмотрите на пример, то увидете что CriticalData не является волотильной и Interlocke* функции к ней не применяются
Пример расчитан на то, чтобы выполнится один раз.

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

Підписатись на коментарі