Естественно, разрешение блокировать только себя — не панацея от дедлоков. Выгода — в локальности ошибки: чтобы понять в чём ошибка достаточно посмотреть на исходный текст эксклюзивных методов объектов классов A и B. Эксклюзивных методов у них 4 штуки — надо критически просмотреть текст 4 процедур. А если разрешить лочиться на ком угодно и где угодно, то ошибку искать придётся неизвестно где — по всему тексту программы, а не в 4 процедурах. С учётом модульных систем (когда загрузился чей-то посторонний модуль и что-то чужое залочил) найти ошибку будет очень трудоёмко.
Можно провести следующую аналогию. Забудем про потоки и поговорим про обычное ООП. Пусть мы решили спрятать данные внутри объектов и давать к ним доступ только через методы. Сочиним два класса A и B. Пусть в классе А есть метод, который вызывает метод класса В, а тот вызывает этот же метод класса А -> stack otherflow. Чтобы понять в чём ошибка достаточно посмотреть на исходный текст этих методов классов A и B, а не изучать текст всей программы, что было бы будь поля объектов доступны для непосредственного редактирования (не инкапсулированы).
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Лочатся только блоки кода помеченные директивой {EXCLUSIVE}. Внутри одной процедуры может быть несколько таких блоков.
получается, что это просто синтаксический сахар, ничем принципиально не отличающийся от сишарпного lock
Сложнее в случае, когда надо блокировать нечто "по цепочке", т.е. "не отпускать одно не захватив следующее, а захватив следующее, отпускать предыдущее".
TYPE
Sem* = OBJECT(* Binary Semaphore *)VAR taken: BOOLEAN
PROCEDURE P*; (*enter semaphore*)BEGIN{EXCLUSIVE}
AWAIT(~taken); taken := TRUE
END P;
PROCEDURE V*; (*leave semaphore*)BEGIN{EXCLUSIVE}
taken := FALSE
END V;
PROCEDURE & Init;
BEGIN taken := FALSE
END Init;
END Sem;
Более изощрённая задача — одновременно держать захваченным не 1-2 звена цепочки, а больше.
Здравствуйте, Дарней, Вы писали:
Д>получается, что это просто синтаксический сахар, ничем принципиально не отличающийся от сишарпного lock
Есть одна мелочь (но приятно): По завершении {EXCLUSIVE} блока посылается PulseAll, а по завершении lock — не посылается.
Но есть и принципиальная разница:
В C# кто угодно посторонний может залочить любой другой объект lock (alien) {...}, а в Active Oberon {EXCLUSIVE} — лочит только себя (только this). Это аналогично тому, как если бы в одной системе программирования был бы разрешён непосредственный доступ к полям всех объектов, а в другой системе программирования разрешался бы доступ только к своим собственным полям, а к полям чужих объектов — только через их методы. Думаю, это принципиальная разница: принципиальная локальность возможных ошибок в одной системе и принципиальная нелокальность возможных ошибок в другой системе программирования.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Думаю, это принципиальная разница: принципиальная локальность возможных ошибок в одной системе и принципиальная нелокальность возможных ошибок в другой системе программирования.
Небольшое разумное самоограничение — и никакой принципиальной нелокальности не будет, а ваши волосы станут мягкими и шелковистыми
Здравствуйте, Дарней, Вы писали:
Д>Здравствуйте, Сергей Губанов, Вы писали:
СГ>>Думаю, это принципиальная разница: принципиальная локальность возможных ошибок в одной системе и принципиальная нелокальность возможных ошибок в другой системе программирования.
Д>Небольшое разумное самоограничение — и никакой принципиальной нелокальности не будет, а ваши волосы станут мягкими и шелковистыми
Разумное самоограничение ВСЕХ программистов в проекте и все ок. Вот только иногда проще заставить программистов работать со средством разработки принципиально запрещающем что-то делать, нежели добится разумного самоограничения.
Как говорится о коде, который не протестирован с уверенностью можно утверждать только одно он не работает. Надеюсь аналогия понятна.
Хотя бесусловно обсуждаемый момент может быть не достаточно важной причиной для смены средства разработки и т.д.
Здравствуйте, SteMage, Вы писали:
SM>Разумное самоограничение ВСЕХ программистов в проекте и все ок. Вот только иногда проще заставить программистов работать со средством разработки принципиально запрещающем что-то делать, нежели добится разумного самоограничения.
В данном случае сделать глобальный поиск по исходникам и раздать звиздюли по его результатам — совершенно несложная задача
SM>Как говорится о коде, который не протестирован с уверенностью можно утверждать только одно он не работает. Надеюсь аналогия понятна.
аналогия понятна, но не имеет смысла. Наличие "плохого" кода в данном случае проверяется очень просто.
SM>Хотя бесусловно обсуждаемый момент может быть не достаточно важной причиной для смены средства разработки и т.д.
скорее — не может быть достаточно важной причиной. Тем более что надо не только менять средство разработки, но еще и переписывать весь код.
SM>Но эта особенность ИМХО заслуживает внимания.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Здесь lock () {...} или BEGIN {EXCLUSIVE} ... END бессильны — нужны семафоры. СГ>В Active Oberon семафоры изготавливают вручную:
... и потом используют как блокировку внешних объектов.
В общем, как ни пытались отвертеться от опасных приёмов... сами же написали инструментарий для них.
С этой точки зрения нет разницы, является ли примититв синхронизации "семафор" встроенным или рукодельным.
Но вот для сопровождения софта — желательно не давать повод изобретать велосипеды: есть риск ошибок в реализации.
Вот, например, твой семафор. Это двоичный нереентерабельный семафор без политики планирования и без проверок корректного использования. Отсюда:
— риск зависания на двойном захвате: sem.p(); sem.p();
— несбалансированная отдача не отслеживается и может привести в дальнейшем к двойному захвату
— если несколько активностей ждут один семафор, то одна из них может ждать вечно, хотя семафор будет многократно отпущен (и захвачен другими)
Во что разрастётся код семафора, свободный от этих проблем. Вряд ли он будет такой компактный и очевидный.
Потом мы захотим семафор со счётчиком.
Его, конечно, можно сделать на двоичных семафорах; или скопипастим код двоичного, подставив AWAIT(count>0) и count:=count+/-1...
Короче говоря, по результатам таких упражнений, получим библиотеку семафоров — хорошо отлаженную, хорошо специфицированную, хорошо документированную... повторно используемую и рекомендуемую к применению вместо новых велосипедов.
A>Спрашивается, зачем у каждого объекта sync block, если он в подавляющем большинстве случаев не используется, а используем мы для синхронизации другой, специально созданный для этого объект, для которого можно было бы иметь специальный класс. И только этот класса должен иметь sync block. У остальных классов от sync block'а можно было бы избавиться. А sync block между прочим 4 байта требует.
sync block используется не только для блокировки но и например для дефолтного GetHashCode итд
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Сергей Губанов, Вы писали:
СГ>В Active Oberon семафоры изготавливают вручную:
Кстати говоря, статья — отличная иллюстрация механизма синхронизации "Conditional Variable".
Если этот механизм существует как примитив (на уровне языка {напр. Оберон} или библиотеки {напр. pthread}), то многое становится легче.
В виндоузе такого примитива нет, его приходится переизобретать. С++ники могут воспользоваться кроссплатформенными boost/thread или ACE.
Здравствуйте, Severn, Вы писали:
S>Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>> Будем переделывать d? S>Зачем? d синхронизирует свои данные — по сути это некая дополнителная обработка между обращениями к a1 и a2.
[злой скип пожрал]
Здесь у тебя получается слишком много блокировок. Вернее, на каждый вызов MethForA1A2 происходит 3 захвата/освобождения объекта синхронизации.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>>Будем переделывать d?
СГ>Да, переделаем...
Правильно. И фактически, получим объект, который занимается только раскидыванием блокировок. Собственно, примерно так же поступают и в "традиционных" языках.
[...]
СГ>...но это не особо сложно.
Недостаток здесь один — необходимость модификации класса d.
СГ>Сложнее в случае, когда надо блокировать нечто "по цепочке", т.е. "не отпускать одно не захватив следующее, а захватив следующее, отпускать предыдущее".
[...] СГ>Здесь lock () {...} или BEGIN {EXCLUSIVE} ... END бессильны — нужны семафоры.
угу
СГ>В Active Oberon семафоры изготавливают вручную:
[...]
СГ>Более изощрённая задача — одновременно держать захваченным не 1-2 звена цепочки, а больше.
Ну вот. Собственно, этого я и ждал. А потом с помощью семафоров можно эмулировать всё многобразие объектов синхронизации...
Дело в том, что это опровергает необходимость так уж внимательно слушать певцов ООП. Какой смысл утверждать, что семафоры (сиречь, отчуждаемые объекты синхронизации) — это плохо, если на практике может оказаться, что без них — никуда?
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>Здесь у тебя получается слишком много блокировок. Вернее, на каждый вызов MethForA1A2 происходит 3 захвата/освобождения объекта синхронизации.
Точнее по одному захвату трех разных объектов. Что в этом плохого?
Если мы поместим MethForA2 внутри D, то количество блокировок уменьшится, но одновременно увеличится эффективный размер критических секций. Т.е. клент для MethForA2 будет ждать все время операции MethForA1A2, хотя ему до этого никакого дела нет.
По сути к этому:
СГ>Лочиться только на самом себе логически следует из объединения ООП и многопоточности:
СГ>1) Все данные инкапсулированы внутри объектов.
СГ>2) Доступ к любым данным осуществляется только с помощью методов объекта.
СГ>3) Для синхронизированного (эксклюзивного) доступа к данным используются эксклюзивные методы (т.е такие методы, блоки кода в которых залочены на this).
стоит добавить пункт про распределение обязанностей. Если обязанности (данные/методы) распределены между объектами оптимальным образом, то в чем проблема в использовании lock(this)?
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Здравствуйте, ie, Вы писали:
ie>> это должна быть коллекция "под конкретную задачу"
СГ>Естественно.
Так вот круг задач следовало определить в условии. Вот у меня бы рука не поднялась сделать такую реализацию для Queue общего типа (а ведь в условии не сказано обратного).
И еще вопрос. А если человек не будет использовать для синхронизации this, а создаст специальный объект для синхронизации. Это как-то повлияет на ваше решение при оценке данной задачи?
Здравствуйте, ie, Вы писали:
ie>Так вот круг задач следовало определить в условии. Вот у меня бы рука не поднялась сделать такую реализацию для Queue общего типа (а ведь в условии не сказано обратного).
Дык ведь она и не общего типа, а типа: EmployeeTest.IQueue.
ie>И еще вопрос. А если человек не будет использовать для синхронизации this, а создаст специальный объект для синхронизации. Это как-то повлияет на ваше решение при оценке данной задачи?
Вы такие вопросы задаёте... да если б кто решил хоть как-нибудь...
По секреты скажу: ещё ни один испытуемый эту задачу решать не брался. Ознакомившись со всем списком предложенных задач, все кидаются решать задачу про множество, думая что она проще.
Задача о множестве
Напишите реализацию методов IsExist, Include, Exclude.
Время: 10 минут
namespace EmployeeTest
{
public interface ISet
{
void Include (object element);
void Exclude (object element);
bool IsExist (object element);
}
public sealed class MySet: ISet
{
private sealed class List
{
public object head;
public List tail;
public List (object head, List tail)
{
this.head = head; this.tail = tail;
}
}
private List list;
public bool IsExist (object element)
{
// Помещен ли указанный элемент в это множество?
}
public void Include (object element)
{
// Если указанного элемента в этом множестве нет, поместить его.
}
public void Exclude (object element)
{
// Исключить указанный элемент из этого множества, если он в нем есть.
}
}
}
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>Какой смысл утверждать, что семафоры (сиречь, отчуждаемые объекты синхронизации) — это плохо...
Да ладно Вам... я утверждал немного другое. Плохо — это когда разрешено синхронизироваться на чём угодно, а не только на том, что специально для этого и было задумано, во что был вложен именно этот смысл. Например, в C# lock работает для всяких разных expression, а .Net-овский Monitor.Enter(object) работает для любых object, что может привести к трудно обнаруживаемым ошибкам, о которых уже сообщалось здесь
Здравствуйте, Кодт, Вы писали:
К>В общем, как ни пытались отвертеться от опасных приёмов... сами же написали инструментарий для них.
....... К>Почему сразу нельзя это сделать?
Да я не против такого инструментария, а против того, что в .Net вообще и в C# в частности разрешено лочится на ЛЮБОМ (чужом) объекте когда и где вздумается. Продолжение здесь
Здравствуйте, GlebZ, Вы писали:
GZ>Сергей, предлагаю вам в качестве домашнего задания решить данную задачу вообще без объектов синхронизации. Максимум, InterlockedExchange, чтобы вам полегче было.
Имхо, Сергей прав. В данной постановке без аналога критической секции задачу решить нельзя.
Если убрать условие, что Dequeue должен ждать, то тогда да — можно. Точнее тогда будет рационально, так как очередь будет блокироваться каждый раз на очень маленькое время, и можно обоснованно применить аналог spin-lock'а из InterlockedIncrement/Decrement + Sleep(0).
Здравствуйте, Сергей Губанов, Вы писали:
ie>>Так вот круг задач следовало определить в условии. Вот у меня бы рука не поднялась сделать такую реализацию для Queue общего типа (а ведь в условии не сказано обратного).
СГ>Дык ведь она и не общего типа, а типа: EmployeeTest.IQueue.
И что? Мне ли вам рассказывать, что такое спецификация требований. Так вот тип EmployeeTest.IQueue никак спецификацией требований не является и никакие выводы о блокирующем Dequeue сделать нельзя. Ну да ладно, как я понял любое решение будет для вас приемлемым, что собственно и правильно.
ie>>И еще вопрос. А если человек не будет использовать для синхронизации this, а создаст специальный объект для синхронизации. Это как-то повлияет на ваше решение при оценке данной задачи?
СГ>Вы такие вопросы задаёте... да если б кто решил хоть как-нибудь...
СГ>По секреты скажу: ещё ни один испытуемый эту задачу решать не брался. Ознакомившись со всем списком предложенных задач, все кидаются решать задачу про множество, думая что она проще.
Для человека, писавшего на занятиях в универе добавление/удаление в/из списка, но не писавшего ни разу многопоточные приложения, эта задача действительно проще. К нам приходят на собеседование и многие даже не знают, что такое lock.
Здравствуйте, Кодт, Вы писали:
СГ>>В Active Oberon семафоры изготавливают вручную:
К>Кстати говоря, статья — отличная иллюстрация механизма синхронизации "Conditional Variable". К>Если этот механизм существует как примитив (на уровне языка {напр. Оберон} или библиотеки {напр. pthread}), то многое становится легче.
К>В виндоузе такого примитива нет, его приходится переизобретать. С++ники могут воспользоваться кроссплатформенными boost/thread или ACE.
Добавлю следующее.
Программистами придумано несколько (разных) способов синхронизации одновременно исполняющихся активностей, но, оказывается, их все можно друг через друга выразить. Возникает вопрос, какой способ синхронизации объявить более примитивным (базовым, элементарным, фундаментальным), чем другой? В языке Active Oberon фундаментальным обозван примитив AWAIT(condition) в связке с BEGIN{EXCLUSIVE} ... END блоком, который лочится на текущий объект (this) или модуль (в зависимости от того встретился ли он в процедуре-методе или в обычной процедуре-модуля). AWAIT(condition) выбран в качестве фундаментального, поскольку, не будь он фундаментальным, то скорее всего он был бы не очень эффективен. Например, самая неэффективная (просто тупая) реализация инструкции AWAIT(condition) есть пустой цикл
WHILE ~condition DO END;
останавливающийся только когда condition кто-то из другого потока присвоит значение TRUE.
В Windows/.Net инструкция AWAIT(condition) на уровне системы не реализована, поэтому сторонние, т.е. библиотечные реализации не очень эффективны как хотелось бы. Короче, программы на языке Active Oberon/Zonnon под Windows/.Net будут работать не так шустро как могли бы.
Владимир Лось работая в системе QNX написал библиотеку эмулирующую Active-Oberon-истые активные объекты на C++ с помощью примитивов синхронизации описанных в POSIX. На быстродействие AWAIT(condition) не жалуется. Говорит, что портировать эту C++ (POSIX) библиотеку под Windows не собирается, ибо смысл пропадёт — сильно медленнее работать будет. Вот его первая статья на эту тему: http://qnxclub.net/modules.php?name=Content&pa=showpage&pid=5
Владимир Лось. Активные объекты в С++.
Предлагается библиотека libao* . Сделана попытка реализации активных объектов в духе разработок ETHZ (Active Oberon, Zonnon). Даётся описание принципов построения активных объектов и краткое введение в предлагаемую библиотеку. Рекомендуется сначала прочитать статью...
Сейчас он "докручивает" там кое что, обещал в скором времени опубликовать ещё...
Ну, и тут нельзя не сказать про операционную систему Aos Bluebottle( гы-гы, если кто ещё не знает) написанной на самом Active Oberon, а примитив AWAIT(condition) — реализован "прямо поверх голого железа", т.е. быстрее не бывает (ну если только аппаратно). Гуткнехт (создатель языков Active Oberon и Zonnon) во время осеннего турне (вместе с Виртом по России) обещал имплементировать язык Zonnon в Bluebottle где использование его станет сильно более эффективно нежели чем в .Net.