Код демонстрирует распространенную проблему, а не создает ее. Причем здесь RAII? Проблема владения ресурсом здесь не затрагивалась, поэтому и нарушить этот принцип никак не могли :) Да вы не беспокойтесь насчет будущего KasperskyOS - не вы же один такой уникальный специалист по микроядерным архитектурам :)
В Rust-е наоборот общие данные "живут" внутри мьютекса. Но то что вы описываете скорее можно назвать мутатором. На мой взгляд, такой поход более опасный чем лямбда т.к. если сохранить где-нибудь мутатор исходный объект останется залоченным. С лямбдами такие риски меньше. Но если нравится такой подход можно посмотреть в сторону Boost Synchronized Value или Folly Synchronized - там это реализовано, в конце статьи писал об этом.
Предлагаю Оккама оставить в покое и посчитать на пальцах сущности до и после: были mutex, lock guard и condition variable + защищаемые данные, стало SharedState + защищаемые данные, которые могут быть отдельной структурой/классом или просто строкой, например. Т.е. мы уменьшили общее количество сущностей, которыми вынуждены были манипулировать и получили одну новую абстракцию, которая объединяет общие данные со средствами их защиты. А правила всегда будут чем бы вы не пользовались :)
Любой класс с двумя шаблонами N и K может сгенерировать N * K вариантов, если видите в этом проблему, тогда конечно, видимо, лучше не пользоваться шаблонными классами :)
Гонками обычно называют ситуации когда два потока "на перегонки" одновременно меняют общие данные и в итоге приводят их в несогласованное состояние. При правильном использовании мьютексов или как здесь предлагается SharedState вместо них, гонки как раз исключаются.
Писать код "не думая" не думал даже рекомендовать - вы, видимо, как-то превратно трактуете прочитанное :)
А где вы увидели мьютексы в публичном API - они наоборот спрятаны внутри SharedState, который тоже совсем необязательно светить в публичном API, это скорее предполагается как часть внутренней реализации.
Подход решает проблему доступа к общим данным из разных потоков, что может пригодиться для распараллеливания. Хотя, безусловно, если есть возможность распараллелить без использования общих данных, то и данный подход не пригодится.
Ну а почему, собственно, вопрос дурацкий? Ошибки в многопоточном коде допускают очень многие люди. Если вы давно уже нет, поздравляю от всей души, но позволю усомниться такому самоуверенному заявлению :)
P.S. Раз статья вам не пригодилась вежливость требует возвратить вам лучи известной субстанции от благодарного автора :)
Суть вашей идеи именно в том, что там лок, но доступ к локу вы не даете, вы заставляете пользователя оформлять работу с данными в виде коллбэка.
Именно это и должен понимать потенциальный пользователь выбирая подобный подход.
Совершенно верно.
Если пользователь хочет лочить вручную то просто использует мьютексы напрямую.
Так же как с памятью: можно выделять-освобождать вручную, а можно через смарт-поинтер.
Выбор за разработчиком.
Мы здесь вынуждены обсуждать коряво написанную статью, в которой любимый вами подход был описан неполно и однобоко.
Во-первых, нас никто не вынуждает.
А во-вторых, это ваши субъективные оценки, поэтому как автор не смогу прокомментировать объективно :) но как мнение неравнодушного читателя приму к сведению (сюда же относим и перлы про бедных разработчиков на "лисипеде" :) ).
Пришлось мне, т.к. вы ничего показать не соизволили.
Так не успел я :) только идею озвучил, а вы уже код сразу выкатили в следующем посте :)
Это не решение. И мне кажется, что от слова совсем.
То, что у вас всегда внутри SharedState живет condition_variable, -- это еще один недостаток вашего подхода. Т.к. нарушается принцип не платить за то, что не используется.
А почему вы заранее решили что это не будет использоваться? Да еще сразу же в недостатки записали :)
Пользуясь библиотекой без исходного кода или с не полной документацией вы тоже не всегда знаете сколько "платите" за использование класса, что он там создает внутри.
Поэтому, как вы сказали, знать может и желательно, но никто не обязан предоставлять подробную информацию о том какие классы используются внутри реализации.
А в каком контексте там на этот документ ссылаются? Может это вообще не имеет никакого отношения к обсуждаемому здесь вопросу?
Ядро системы это тоже более фундаментальная вещь чем "стандартная библиотека", только вот что нам это дает не ясно.
В "ванильном мире" как вы выразились существуют все те же проблемы связанные с доступом из разных тредов к общим данным, поэтому мютексы везде нужны. И в практической работе вы скорее столкнетесь с ними, нежели будете лезть в дебри модели памяти.
Я просто привел аргументы в пользу своей точки зрения.
Я вижу "залочить данные для чтения ограничив длительность лока вот этим скоупом". Принципиально здесь "залочить".
Вы видите потому что я вам сказал что они лочатся :)
Я то как раз предлагал перестать мыслить в рамках лочить-разлочить.
Но, конечно, ни в коем случае никому не навязываю свое видение :)
Для вас, как для переизобретателя подхода, может быть важна "концептуальная составляющая" и вы акцентируете внимание на этой составляющей.
Да, я считаю что от того какие абстракции мы используем зависит качество кода и концептуальная составляющая важна безотносительно к SharedState.
Для меня, как для стороннего наблюдателя, это не так важно. Весь ваш фокус в том, что вы сами создаете lock_guard и удаляете его, заставляя пользователя оформить работу с залоченными данными в лямбде. В этом суть.
Вы правильно уловили суть реализации SharedState, но на мой взгляд сама идея важнее и совсем не принципиально что там за кулисами лочится или не лочится, главное что SharedState выполняет свой контракт публичного API.
А то, что вы на эту суть еще несколько уровней концептуальных смыслов пытаетесь навесить -- это ваши личные проблемы.
Ну не нужно горячиться :) Какие личные проблемы? Вы о чем вообще? Вы что психоаналитик? :)
Я ничего не навешивал, а пытался донести до вас свою мысль, если вы не поняли, держите себя, пожалуйста, в руках и не переходите на личности :)
Ну раз непонятно, значит ваш SharedState для таких сценариев не подойдет.
Зачем же так категорично? :) Может и подойдет.
Если вы чего-то не понимаете или с чем-то не сталкивались, то это не значит, что дизайн кривой.
Да вы успокойтесь, мы же не какой-то конкретный дизайн обсуждаем.
У меня есть устойчивое подозрение, что вы просто не осознаете, зачем нужен std::lock и почему он был добавлен.
Напрасно подозреваете, там все очевидно :)
Я ничего не предлагал. Просто пытаюсь выяснить что следовало бы сделать в вашем подходе.
Ну вы же привели кусок кода? Так вот вы все правильно поняли это и есть одно из решений.
Так что это не столько достоинство вашего подхода, сколько последствия его особенностей.
В моем понимании это все-такие достоинство, потому что ожидание определенного состояния данных это достаточно распространенный кейз, неразрывно связанный с их защитой в многопоточной среде.
Вон в Java прямо в базовый Object добавили wait/notify/notifyAll :)
Кстати говоря, ваш SharedState всегда сопровождается condition_variable (event)?
Да.
Ну да, ну да. Это не лок. Выглядит как лок, работает как лок, но не лок.
Хорошо давайте посмотрим на это так - вы смотрите код и видите "залочить мютекс". Какие данные он защищает? Когда его можно разлочить?
Или вы видите в коде "читаю данные". И смотрите на небольшой блок кода в котором они читаются.
Технически и тут и тут лок данных, но концептуально разница очевидна на мой взгляд.
Одной синхронной операцией модифицировать сразу несколько независимых друг от друга SharedState.
Это я понял, мне непонятно зачем может понадобиться одновременно лочить независимые SharedState? Цель то какая? Выглядит как какой-то костыль при кривом дизайне системы.
Может нужен просто еще один SharedState в котором будут собираться данные из остальных?
Либо вариант, который вы уже предложили.
Либо внутри блока доступа к одному SharedState открыть остальные на чтение или запись.
Как я понимаю, ваш SharedState дает пользователю две полезные вещи
Еще можно дожидаться определенного состояния данных и нотифицировать об этом.
Помимо этого мы не "лочим" данные, а явно указываем что мы собираемся с ними делать: читать, модифицировать, ждать или изменить и оповестить об этом.
Мы четко высказываем свои намерения в коде через код, это полезно для поддержки и изучения существующей кодовой базы другими людьми.
Это скорее концептуальные преимущества, а не технические.
Но пользователь должен сам решать как он будет работать с данными в режиме read-calculate-modify, когда блоки кода разделены (со всеми вытекающими), либо все сделать в modify.
Не понятно. Вы что-то такое подразумеваете
Да, только внутри DataB нужно, наверное, поместить хотя бы еще один SharedState иначе непонятно зачем такой огород городить :) Может вы объясните какой юзкейз мы пытаемся покрыть в данном случае? Тогда что-нибудь поизящнее попробуем выдумать :)
Но посмотрите, например, на Mutex из Rust-а: там объединение mutex-а и защищаемого им объекта T происходит на уровне типа Mutex.
Спасибо, хороший пример. Действительно здравая идея объединить mutex и защищаемые данные. Только еще condition variable не прикрутили :)
У тупых mutex-ов, при всех их недостатков, такой проблемы нет. Если вы mutex захватили, то результаты ваших вычислений устареть не могут, т.к. исходные данные никто не может модифицировать.
Да, но и в SharedState можно достигнуть ровно такого же поведения, если получить доступ на запись и произвести вычисления внутри этого блока, т.к. пока один поток находится внутри блока доступа данные измениться не могут. Тут вопрос в том насколько сложные будут эти вычисления и сколько мьютекс или SharedState пробудут в залоченном состоянии. Не будет ли перекрестного вызова изнутри блока к этому же SharedState.
Отдельный вопрос -- это как быть с вашим подходом в случае, если требуется синхронно модифицировать несколько разных StaredState объектов.
Вложенные SharedState.
Если о синхронизации доступа к разделяемым данным внутри самих тасков, то возвращаемся к тому, с чего начали -- это нужно свести к минимуму, в идеале -- вообще избежать.
Да, о синхронизации доступа к разделяемым данным, когда пересечения избежать не удается (возможно в силу не зависящих от нас причин).
Я имел в виду в том смысле базовый, что существует в том или ином виде в любом языке программирования где существуют потоки, без базовых примитивов синхронизации далеко не уедешь.
Приведенная ссылка относится к стандартной библиотеке C++, есть ли нечто подобное в других ЯП не уверен (и насколько эта абстракция полезна в многопоточном программировании тоже).
Код демонстрирует распространенную проблему, а не создает ее. Причем здесь RAII? Проблема владения ресурсом здесь не затрагивалась, поэтому и нарушить этот принцип никак не могли :)
Да вы не беспокойтесь насчет будущего KasperskyOS - не вы же один такой уникальный специалист по микроядерным архитектурам :)
Ценю ваш сарказм :) ответил выше.
В Rust-е наоборот общие данные "живут" внутри мьютекса. Но то что вы описываете скорее можно назвать мутатором. На мой взгляд, такой поход более опасный чем лямбда т.к. если сохранить где-нибудь мутатор исходный объект останется залоченным. С лямбдами такие риски меньше. Но если нравится такой подход можно посмотреть в сторону Boost Synchronized Value или Folly Synchronized - там это реализовано, в конце статьи писал об этом.
Предлагаю Оккама оставить в покое и посчитать на пальцах сущности до и после: были mutex, lock guard и condition variable + защищаемые данные, стало SharedState + защищаемые данные, которые могут быть отдельной структурой/классом или просто строкой, например. Т.е. мы уменьшили общее количество сущностей, которыми вынуждены были манипулировать и получили одну новую абстракцию, которая объединяет общие данные со средствами их защиты. А правила всегда будут чем бы вы не пользовались :)
В статье писал почему так - просто для наглядности, чтобы сразу было видно сигнатуры. В боевом коде, безусловно, лучше сделать как у вас.
Любой класс с двумя шаблонами N и K может сгенерировать N * K вариантов, если видите в этом проблему, тогда конечно, видимо, лучше не пользоваться шаблонными классами :)
Гонками обычно называют ситуации когда два потока "на перегонки" одновременно меняют общие данные и в итоге приводят их в несогласованное состояние. При правильном использовании мьютексов или как здесь предлагается SharedState вместо них, гонки как раз исключаются.
Писать код "не думая" не думал даже рекомендовать - вы, видимо, как-то превратно трактуете прочитанное :)
А где вы увидели мьютексы в публичном API - они наоборот спрятаны внутри SharedState, который тоже совсем необязательно светить в публичном API, это скорее предполагается как часть внутренней реализации.
Насчет "чертовщины", пожалуйста, в церковь :)
Подход решает проблему доступа к общим данным из разных потоков, что может пригодиться для распараллеливания. Хотя, безусловно, если есть возможность распараллелить без использования общих данных, то и данный подход не пригодится.
Ну это скорее вопросы к реализации Streams, которые могут повлиять на ваше решение использовать его или нет.
Ну а почему, собственно, вопрос дурацкий? Ошибки в многопоточном коде допускают очень многие люди. Если вы давно уже нет, поздравляю от всей души, но позволю усомниться такому самоуверенному заявлению :)
P.S. Раз статья вам не пригодилась вежливость требует возвратить вам лучи известной субстанции от благодарного автора :)
Согласен, но в статье речь о ситуациях когда без мьютексов обойтись не получается.
Увольте пожалуйста от такой "камасутры" :)
Совершенно верно.
Если пользователь хочет лочить вручную то просто использует мьютексы напрямую.
Так же как с памятью: можно выделять-освобождать вручную, а можно через смарт-поинтер.
Выбор за разработчиком.
Во-первых, нас никто не вынуждает.
А во-вторых, это ваши субъективные оценки, поэтому как автор не смогу прокомментировать объективно :) но как мнение неравнодушного читателя приму к сведению (сюда же относим и перлы про бедных разработчиков на "лисипеде" :) ).
Так не успел я :) только идею озвучил, а вы уже код сразу выкатили в следующем посте :)
А... ну раз кажется тогда, конечно :)
А почему вы заранее решили что это не будет использоваться? Да еще сразу же в недостатки записали :)
Пользуясь библиотекой без исходного кода или с не полной документацией вы тоже не всегда знаете сколько "платите" за использование класса, что он там создает внутри.
Поэтому, как вы сказали, знать может и желательно, но никто не обязан предоставлять подробную информацию о том какие классы используются внутри реализации.
А в каком контексте там на этот документ ссылаются? Может это вообще не имеет никакого отношения к обсуждаемому здесь вопросу?
Ядро системы это тоже более фундаментальная вещь чем "стандартная библиотека", только вот что нам это дает не ясно.
В "ванильном мире" как вы выразились существуют все те же проблемы связанные с доступом из разных тредов к общим данным, поэтому мютексы везде нужны. И в практической работе вы скорее столкнетесь с ними, нежели будете лезть в дебри модели памяти.
Я просто привел аргументы в пользу своей точки зрения.
Вы видите потому что я вам сказал что они лочатся :)
Я то как раз предлагал перестать мыслить в рамках лочить-разлочить.
Но, конечно, ни в коем случае никому не навязываю свое видение :)
Да, я считаю что от того какие абстракции мы используем зависит качество кода и концептуальная составляющая важна безотносительно к SharedState.
Вы правильно уловили суть реализации SharedState, но на мой взгляд сама идея важнее и совсем не принципиально что там за кулисами лочится или не лочится, главное что SharedState выполняет свой контракт публичного API.
Ну не нужно горячиться :) Какие личные проблемы? Вы о чем вообще? Вы что психоаналитик? :)
Я ничего не навешивал, а пытался донести до вас свою мысль, если вы не поняли, держите себя, пожалуйста, в руках и не переходите на личности :)
Зачем же так категорично? :) Может и подойдет.
Да вы успокойтесь, мы же не какой-то конкретный дизайн обсуждаем.
Напрасно подозреваете, там все очевидно :)
Ну вы же привели кусок кода? Так вот вы все правильно поняли это и есть одно из решений.
В моем понимании это все-такие достоинство, потому что ожидание определенного состояния данных это достаточно распространенный кейз, неразрывно связанный с их защитой в многопоточной среде.
Вон в Java прямо в базовый Object добавили wait/notify/notifyAll :)
Да.
Хорошо давайте посмотрим на это так - вы смотрите код и видите "залочить мютекс". Какие данные он защищает? Когда его можно разлочить?
Или вы видите в коде "читаю данные". И смотрите на небольшой блок кода в котором они читаются.
Технически и тут и тут лок данных, но концептуально разница очевидна на мой взгляд.
Это я понял, мне непонятно зачем может понадобиться одновременно лочить независимые SharedState? Цель то какая? Выглядит как какой-то костыль при кривом дизайне системы.
Может нужен просто еще один SharedState в котором будут собираться данные из остальных?
Либо вариант, который вы уже предложили.
Либо внутри блока доступа к одному SharedState открыть остальные на чтение или запись.
Еще можно дожидаться определенного состояния данных и нотифицировать об этом.
Помимо этого мы не "лочим" данные, а явно указываем что мы собираемся с ними делать: читать, модифицировать, ждать или изменить и оповестить об этом.
Мы четко высказываем свои намерения в коде через код, это полезно для поддержки и изучения существующей кодовой базы другими людьми.
Это скорее концептуальные преимущества, а не технические.
Но пользователь должен сам решать как он будет работать с данными в режиме read-calculate-modify, когда блоки кода разделены (со всеми вытекающими), либо все сделать в modify.
Да, только внутри DataB нужно, наверное, поместить хотя бы еще один SharedState иначе непонятно зачем такой огород городить :)
Может вы объясните какой юзкейз мы пытаемся покрыть в данном случае? Тогда что-нибудь поизящнее попробуем выдумать :)
Я бы не стал утверждать что это "обычно", это разве что в Windows так :)
Спасибо, хороший пример. Действительно здравая идея объединить mutex и защищаемые данные. Только еще condition variable не прикрутили :)
Да, но и в SharedState можно достигнуть ровно такого же поведения, если получить доступ на запись и произвести вычисления внутри этого блока, т.к. пока один поток находится внутри блока доступа данные измениться не могут. Тут вопрос в том насколько сложные будут эти вычисления и сколько мьютекс или SharedState пробудут в залоченном состоянии. Не будет ли перекрестного вызова изнутри блока к этому же SharedState.
Вложенные SharedState.
Да, о синхронизации доступа к разделяемым данным, когда пересечения избежать не удается (возможно в силу не зависящих от нас причин).
Я, но это была шутка :) ваша мысль мне понятна.
Я имел в виду в том смысле базовый, что существует в том или ином виде в любом языке программирования где существуют потоки, без базовых примитивов синхронизации далеко не уедешь.
Приведенная ссылка относится к стандартной библиотеке C++, есть ли нечто подобное в других ЯП не уверен (и насколько эта абстракция полезна в многопоточном программировании тоже).
Походу "не завезли" :) но думаю макросом можно решить вопрос :)