Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>Знаешь, что такое Очень Плохая Вещь? ГВ>Это вот она: СГ>>C#:
System.Monitor.Enter(this);
Здравствуйте, Дарней, Вы писали:
Д> Вот придёт кому-то в голову синхронизироваться на этом же объекте, но в другом месте и в других целях — и всё, приплыли.
Ну, дуракам закон не писан. А Вы ещё спрашивали в чём разница между обычными языками (типа C#) и языками поддерживающими парадигму активных объектов (Active Oberon, Zonnon,...). Разница в том, что залочится в C# можно на любом (чужом) объекте (быть может, на ровном месте неожиданно заработав deadlock), а в Active Oberon — только на самом себе (только на this).
Лочиться только на самом себе логически следует из объединения ООП и многопоточности:
1) Все данные инкапсулированы внутри объектов.
2) Доступ к любым данным осуществляется только с помощью методов объекта.
3) Для синхронизированного (эксклюзивного) доступа к данным используются эксклюзивные методы (т.е такие методы, блоки кода в которых залочены на this).
Здравствуйте, Сергей Губанов, Вы писали: Д>> Вот придёт кому-то в голову синхронизироваться на этом же объекте, но в другом месте и в других целях — и всё, приплыли.
СГ>Ну, дуракам закон не писан. А Вы ещё спрашивали в чём разница между обычными языками (типа C#) и языками поддерживающими парадигму активных объектов (Active Oberon, Zonnon,...). Разница в том, что залочится в C# можно на любом (чужом) объекте (быть может, на ровном месте неожиданно заработав deadlock), а в Active Oberon — только на самом себе (только на this).
И это хорошо? Вот у меня есть статический метод:
public static void M()
{
lock(_syncObj)
{
...
}
}
Ну и как с этим бороться если lock будет разрешаться только на this?
Здравствуйте, ie, Вы писали:
ie>И это хорошо? Вот у меня есть статический метод:
ie>Ну и как с этим бороться если lock будет разрешаться только на this?
Здравствуйте, marat321, Вы писали:
M>Здравствуйте, ie, Вы писали:
ie>>И это хорошо? Вот у меня есть статический метод:
ie>>Ну и как с этим бороться если lock будет разрешаться только на this?
M>В абяроне есть статические методы?
Здравствуйте, ie, Вы писали:
ie>И это хорошо? Вот у меня есть статический метод:
... ie>Ну и как с этим бороться если lock будет разрешаться только на this?
Тёмная силы сторона статическими методами завладела. Объекты активные только используй ты
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Если у класса есть статические методы, значит класс используется в роли модуля. Роль this отдаётся типу класса typeof():
Здравствуйте, marat321, Вы писали:
M>В абяроне есть статические методы?
Есть, только называются они просто обычными процедурами.
Oberon — модульная система, все процедуры расположены в модулях. Модуль играет роль объекта-синглетона. Он может инкапсулировать любые данные. Доступ к этим данным осуществляется с помощью процедур этого модуля. Процедуры могут быть эксклюзивными для этого модуля. Так что, в этом смысле (эксклюзивного доступа к данным), разница между объектом и модулем отсутствует: в методах объекта лочатся на this, в процедурах модуля лочаться на сам модуль.
Пример:
PROCEDURE DoSmth;
BEGIN{EXCLUSIVE}
...
END DoSmth;
если DoSmth — это метод объекта, то {EXCLUSIVE} — обозначает лок на this,
если DoSmth — это просто обычная процедура из модуля MyModule, то {EXCLUSIVE} — обозначает лок на MyModule.
Ха-ха-ха, из-за того что в C# разрешено блокировать не только самого себя (this или typeof(Me)), но и любого другого тоже вот такое безобразие и возникает! Если бы было разрешено блокировать только самого себя (this или typeof(Me)), всё было бы чики-пуки. Разрешение блокировать чужой объект (а не лишь самого себя), это всё равно что разрешение использовать goto — та же нелокальность последствий. Хотя нет, это даже хуже чем goto — это goto-наоборот, что-то из оперы COMING FROM
Я тут у себя на работе сочинил небольшой задачник для проведения собеседований,
вот задача как раз на тему этой ветки форума:
Задача об очереди
Напишите реализацию методов Enqueue и Dequeue.
Указание
Для синхронизации потоков, где это нужно, используйте:
• lock (expression)
• System.Threading.Monitor.Wait (object obj);
• System.Threading.Monitor.PulseAll (object obj);
Время: 5 минут
namespace EmployeeTest
{
public interface IQueue
{
void Enqueue (object element);
object Dequeue ();
}
public sealed class MyQueue: IQueue
{
private sealed class Item
{
public object element;
public Item next;
public Item (object element)
{
this.element = element;
}
}
private Item head;
private Item tail;
public void Enqueue (object element)
{
// Добавить элемент в хвост очереди.
// Операция должна быть безопасна в условиях многопоточного использования.
}
public object Dequeue ()
{
// Если очередь пуста, то ждать (спать) до тех пор пока кто-то другой
// (из другого потока) добавит элемент в очередь.
// Извлечь элемент из головы очереди и вернуть его из функции.
// Операция должна быть безопасна в условиях многопоточного использования.return null;
}
}
}
не торопитесь смотреть решение (оно в следующем сообщении) попробуйте сначала решить задачу самостоятельно...
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Здравствуйте, ie, Вы писали:
ie>>Ну и как с этим бороться если lock будет разрешаться только на this?
СГ>Если у класса есть статические методы, значит класс используется в роли модуля. Роль this отдаётся типу класса typeof():
Но все-таки возвращаясь к первоначальной постановке. Допустим я хочу эксклюзивно владеть объектом. Т.е. объект сам не thread-safe, но я хочу использовать его из нескольких потоков. Тогда как?
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Решение задачи об очереди
Красота! Только вот я не могу никак в вашем решении узнать, является ли очередь пустая или нет, а при попытке взять из нее что-нибудь, виснем и возможно надолго.
Собеседование заваленно
Здравствуйте, ie, Вы писали:
ie>Но все-таки возвращаясь к первоначальной постановке. Допустим я хочу эксклюзивно владеть объектом. Т.е. объект сам не thread-safe, но я хочу использовать его из нескольких потоков. Тогда как?
Обзовём этот объект словом — data.
Тогда возвращаемся обратно к основным принципам ООП объединённого с многопоточностью:
1) Все данные инкапсулированы внутри объектов.
2) Доступ к данным осуществляется только с помощью методов объекта.
3) Для синхронизированного (эксклюзивного) доступа к данным используются эксклюзивные методы.
Процесс мышления таков:
1) data — это теперь данные, надо их инкапсулировать в какой-то объект MyData.
2) Снаружи доступ к данным осуществляется только с помощью методов объекта MyData.
3) Синхронизированный доступ осуществляется эксклюзивными методами объекта MyData.
Резюме:
Написать thread-safe объект-обёртку MyData над объектом data и работать через него.
Здравствуйте, ie, Вы писали:
ie>Красота! Только вот я не могу никак в вашем решении узнать, является ли очередь пустая или нет, а при попытке взять из нее что-нибудь, виснем и возможно надолго.
Это не решение "виновато", а такое было условие задачи. Условие задачи намеренно сделано очень простым, чтобы испытуемый успел решить эту задачу (и ещё ряд других) во время собеседования не слишком при этом утомив и того кто его экзаменует.
Если бы я сформулировал её так:
public interface IQueue
{
void Enqueue (object element);
object Dequeue ();
bool IsAvailable {get;} // не пуста ли очередь?bool BlockingMode {get; set;} // снять/установить/проверить блокирующий режим.
// В случае если BlockingMode = false, то Dequeue должна выдавать исключение когда очередь пуста
// В случае если BlockingMode = true, то Dequeue должна блокировать поток до тех пор пока очередь пуста
}
то мне пришлось бы дать испытуемому чуток побольше времени, но ничего принципиально нового бы о нём я бы не узнал. Ведь если он решит первую задачу, то решит и вторую (аналогичную), так чего время зря тянуть?
Сергей Губанов wrote: > 1) *Все* данные инкапсулированы внутри объектов. > 2) Доступ к данным осуществляется *только* с помощью методов объекта. > 3) Для синхронизированного (эксклюзивного) доступа к данным используются > *эксклюзивные методы*.
Вы только что описали аппартаментную (appartament) потоковую модель COM.
Рассказать какие с ней проблемы возникают?
СГ> bool IsAvailable {get;} // не пуста ли очередь?
СГ>
Кстати, проверять пуста очередь или не пуста перед выполнением Dequeue — не очень осмысленное занятие, ведь в промежуток времени между вызовами IsAvailable и Dequeue очередь может успеть опустошить кто-то другой...
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Ха-ха-ха, из-за того что в C# разрешено блокировать не только самого себя (this или typeof(Me)), но и любого другого тоже вот такое безобразие и возникает! Если бы было разрешено блокировать только самого себя (this или typeof(Me)), всё было бы чики-пуки. Разрешение блокировать чужой объект (а не лишь самого себя), это всё равно что разрешение использовать goto — та же нелокальность последствий. Хотя нет, это даже хуже чем goto — это goto-наоборот, что-то из оперы COMING FROM
Здравствуйте, Cyberax, Вы писали:
C>Сергей Губанов wrote: >> 1) *Все* данные инкапсулированы внутри объектов. >> 2) Доступ к данным осуществляется *только* с помощью методов объекта. >> 3) Для синхронизированного (эксклюзивного) доступа к данным используются >> *эксклюзивные методы*. C>Вы только что описали аппартаментную (appartament) потоковую модель COM. C>Рассказать какие с ней проблемы возникают?
СГ>Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>>Знаешь, что такое Очень Плохая Вещь? ГВ>>Это вот она: СГ>>>C#: СГ>
СГ> System.Monitor.Enter(this);
СГ>
СГ>Здравствуйте, Дарней, Вы писали:
Д>> Вот придёт кому-то в голову синхронизироваться на этом же объекте, но в другом месте и в других целях — и всё, приплыли.
А это, кстати, интересный вопрос. Вот в .NET у каждого объекта сделали sync block, чтобы каждому объекту можно было сделать lock(this). А потом выяснилось, что это очень и очень плохо делать lock(this). Начали рекомендовать практику введения отдельного синхронизирующего объекта, т.е.
class MySyncClass
{
object sync_obj = new object();
public void foo()
{
lock (sync_obj)
{
// modify me
}
}
}
Спрашивается, зачем у каждого объекта sync block, если он в подавляющем большинстве случаев не используется, а используем мы для синхронизации другой, специально созданный для этого объект, для которого можно было бы иметь специальный класс. И только этот класса должен иметь sync block. У остальных классов от sync block'а можно было бы избавиться. А sync block между прочим 4 байта требует.
УжасЪ
Сергей, предлагаю вам в качестве домашнего задания решить данную задачу вообще без объектов синхронизации. Максимум, InterlockedExchange, чтобы вам полегче было.
Здравствуйте, minorlogic, Вы писали:
M>А в Dequeue () разве мы не поймали дедлок ?
M>Как кто то может что то добавить , если мы залочили все выполнение ? Или я чет в механизмах шарповских не понимаю
Это не шарповские механизмы, и даже не дотнетные, это называется Монитор.
Шарповский lock (obj) {...} — это синтаксический сахар над:
У монитора, кроме Enter(obj) и Exit(obj) есть еще Wait(obj) и PulseAll(obj), которые можно вызывать только находясь внутри эксклюзивного блока кода. Wait — усыпляет поток до тех пор пока кто-то другой не вызовет PulseAll. Уснувший по Wait поток считается (временно) покинувшим эксклюзивный блок, так что эксклюзивный блок становится вновь доступен для проникновения в него другого потока, поработав внутри эксклюзивного блока другой поток, уходя, должен вызвать PulseAll для того чтобы разбудить уснувшие по Wait другие потоки. Итак, на вход в эксклюзивный блок есть две очереди. Одна очередь образуется на барьере Enter, а другая очередь — это уже вошедшие ранее в эксклюзивный блок потоки, но уснувшие внутри него по Wait.
public object Dequeue ()
{
// перед этой командой потоки ставятся в очередь: внутрь вход по одному!lock (this)
{
// Проникнув внутрь эксклюзивного блока, поток проверяет очередь на непустоту,
// если она пуста - засыпает. Как только он засыпает по Wait, то объект this становится
// вновь доступен для других потоков. Раз так, то какой-то другой поток может вызвать
// метод Enqueue и сделать очередь непустой. В конце метода Enqueue зовётся PulseAll.
// Сработавший PulseAll будит уснувшие на этом объекте потоки. Разбуженные потоки вновь
// впускаются внутрь эксклюзивного блока (по одному),
// из которого они были временно выведены на время своего снаwhile (this.head == null) System.Threading.Monitor.Wait(this);
object r = this.head.element;
this.head = this.head.next;
if (this.head == null) this.tail = null;
System.Threading.Monitor.PulseAll(this);
return r;
}
}
Итого, для синхронизации, в язык введено два дополнительных служебных слова EXCLUSIVE и AWAIT — с помощью них реализуются все механизмы синхронизации:
Synchonization Examples: http://bluebottle.ethz.ch/languagereport/node8.html
Вот аналог того буфера:
MODULE Buffers;
CONST
BufLen = 256;
TYPE(* Buffer- First-in first-out buffer *)
Buffer* = OBJECT
VAR
data: ARRAY BufLen OF INTEGER;
in, out: LONGINT;
(* Put - insert element into the buffer *)PROCEDURE Put* (i: INTEGER);
BEGIN{EXCLUSIVE}
AWAIT ((in + 1) MOD BufLen # out); (*AWAIT ~full *)
data[in] := i;
in := (in + 1) MOD BufLen
END Put;
(* Get - get element from the buffer *)PROCEDURE Get* (VAR i: INTEGER);
BEGIN{EXCLUSIVE}
AWAIT (in # out); (*AWAIT ~empty *)
i := data[out];
out := (out + 1) MOD BufLen
END Get;
PROCEDURE & Init;
BEGIN
in := 0; out := 0;
END Init;
END Buffer;
END Buffers.
Здравствуйте, GlebZ, Вы писали:
GZ>Сергей, предлагаю вам в качестве домашнего задания решить данную задачу вообще без объектов синхронизации. Максимум, InterlockedExchange, чтобы вам полегче было.
На InterlockedExchange и, тем более, вообще без объектов синхронизации эту задачу решить нельзя. Какую-то другую — можно, эту — нет.
СГ>> bool IsAvailable {get;} // не пуста ли очередь?
СГ>>
СГ>Кстати, проверять пуста очередь или не пуста перед выполнением Dequeue — не очень осмысленное занятие, ведь в промежуток времени между вызовами IsAvailable и Dequeue очередь может успеть опустошить кто-то другой...
В том то и беда. Что вызов Dequeue может привести к бесконечному ожиданию, что на самом деле не допустимо. Решение разработчиков стандартной (System.Collection.Queue) очереди мне кажется более логичным. Но как я помню из ваших предыдущих постов, бросать исключение плохо, поэтому даже не знаю получится ли у вас красивое разрешение этой ситуации.
Здравствуйте, ie, Вы писали:
ie>В том то и беда. Что вызов Dequeue может привести к бесконечному ожиданию, что на самом деле не допустимо.
В некоторых задачах очень даже допустимо, даже необходимо по логике работы. Например в моей программе есть такое место, когда распознаватель речи обратившись за очередной порцией данных отправляется в спячку если эта порция данных ещё не была к этому времени получена. Как только она получается, он автоматически будится просто от того, что в очередь произведена запись.
ie>Решение разработчиков стандартной (System.Collection.Queue) очереди мне кажется более логичным. Но как я помню из ваших предыдущих постов, бросать исключение плохо, поэтому даже не знаю получится ли у вас красивое разрешение этой ситуации.
Не блокирующий вызов не кидающий исключений в случае пустой очереди? Да элементарно:
bool Dequeue (out object obj);
if (queue.Dequeue(out x))
{
...
}
else
{
... очередь оказалась пустой
}
Kluev wrote: > C>Вы только что описали аппартаментную (appartament) потоковую модель COM. > C>Рассказать какие с ней проблемы возникают? > Какие?
1. Межпоточный маршаллинг — с языковой поддержкой он, конечно, будет
прозрачным и более эффективным цикла сообщений в Винде. Но все же...
3. Слишком грубая структуризация — несколько потоков не могут
одновременно использовать ресурсы одного аппартамента, даже если они не
связаны друг с другом.
4. Остается возможность дедлоков, несмотря на то, что код фактически
однопоточный.
Из плюсов: очень эффективно в некоторых случаях, так как не нужны
блокировки ну и писать однопоточные приложения значительно проще.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>3) Для синхронизированного (эксклюзивного) доступа к данным используются эксклюзивные методы (т.е такие методы, блоки кода в которых залочены на this).
и всё-таки, я так и не понял толком — лочатся все методы объекта или только специальным образом помеченные?
и второй вопрос — зачем писать в явном виде вот это, вместо простого lock() {}?
Здравствуйте, Сергей Губанов, Вы писали:
ie>>В том то и беда. Что вызов Dequeue может привести к бесконечному ожиданию, что на самом деле не допустимо.
СГ> В некоторых задачах очень даже допустимо, даже необходимо по логике работы. Например в моей программе есть такое место, когда распознаватель речи обратившись за очередной порцией данных отправляется в спячку если эта порция данных ещё не была к этому времени получена. Как только она получается, он автоматически будится просто от того, что в очередь произведена запись.
У меня тоже такие задачи регулярны, но отслеживать ситуацию, когда клиент просто забил и перестал слать какие-либо данные отслеживаться должна непременно. Но вот делать такую коллекцию, как предлагаете вы (спящую до появления данных), по меньшей мере не логично. Либо это должна быть коллекция "под конкретную задачу", но ни как не общего назначения.
ie>>Решение разработчиков стандартной (System.Collection.Queue) очереди мне кажется более логичным. Но как я помню из ваших предыдущих постов, бросать исключение плохо, поэтому даже не знаю получится ли у вас красивое разрешение этой ситуации.
СГ>Не блокирующий вызов не кидающий исключений в случае пустой очереди? Да элементарно: СГ>
СГ>bool Dequeue (out object obj);
СГ>
Я бы в таком случае принял решение создания 2х методов.
Здравствуйте, Сергей Губанов, Вы писали:
Д>> Вот придёт кому-то в голову синхронизироваться на этом же объекте, но в другом месте и в других целях — и всё, приплыли.
СГ>Ну, дуракам закон не писан. А Вы ещё спрашивали в чём разница между обычными языками (типа C#) и языками поддерживающими парадигму активных объектов (Active Oberon, Zonnon,...). Разница в том, что залочится в C# можно на любом (чужом) объекте (быть может, на ровном месте неожиданно заработав deadlock), а в Active Oberon — только на самом себе (только на this).
Сергей, а как в Обероне решается вот такая задача:
TYPE ClassA = CLASS
ptrB: POINTER TO ClassB;
END;
TYPE ClassB = CLASS
ptrA: POINTER TO ClassA;
END;
FUNCTION Construct(): POINTER TO ClassA;
BEGIN
ptrA := NEW ClassA;
ptrA.ptrB := NEW ClassB;
ptrA.ptrB.ptrA := ptrA;
Construct := ptrA;
END;
PROCEDURE ClassA.foo;
BEGIN{exclusive}
.....
this.ptrB.foo();
.....
END;
PROCEDURE ClassB.foo;
BEGIN{exclusive}
.....
END;
PROCEDURE ClassB.bar;
BEGIN{exclusive}
.....
this.ptrA.bar();
.....
END;
PROCEDURE ClassA.bar;
BEGIN{exclusive}
.....
END;
(Если чего напутал в синтаксисе — извини).
И теперь предположим, что два потока одновременно влезли в эту пару — один через ClassA.foo(), другой через ClassB.bar(). Всё, дедлок. Хотя объекты блокировали только себя.
По-хорошему, тут нужно всё переписывать: разбивать ClassA.foo и ClassB.bar, чтобы передача управления за пределы критической секции разблокировала объект. Естественно, не нарушая его инвариант...
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Лочиться только на самом себе логически следует из объединения ООП и многопоточности: СГ>1) Все данные инкапсулированы внутри объектов. СГ>2) Доступ к любым данным осуществляется только с помощью методов объекта. СГ>3) Для синхронизированного (эксклюзивного) доступа к данным используются эксклюзивные методы (т.е такие методы, блоки кода в которых залочены на this).
А как лочить несколько объектов за раз? То есть, например, вот такая ситуация:
Имеем два объекта (a1 и a2). И два объекта-пользователя (b и c). Объекту b нужно лочить a1 и a2, а объекту c — только a2. Например, b перекачивает какие-то данные из a1 в a2, а c — только обновляет a2.
Если мы можем опираться только на самоблокировку объекта, тогда получается, что нужно организовать какой-то промежуточный d, у которого будет два exclusive-метода: meth_for_a1_a2 и meth_for_a2. Хорошо, мы перенаправим вызовы b и c на некий d. А потом у нас появится третий пользователь, например e, которому нужно будет лочить только a1. Будем переделывать d?
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
В очередной раз дискуссия переходит в режим противостояния. И вот почему.
Ты предлагаешь серебряные пули. А серебряных пуль-то, на самом деле, не существует. Есть много видов обычных пуль, которыми нужно стрелять по соответствующим целям.
У самоблокировок есть своя ниша. У блокировок сторонних объектов — другая ниша.
Скажем, в СУБД предмет блокировок — не сами активности сервера, и не транзакции, а участки БД, т.е. пассивные объекты. Делать таблицы активностями просто смысла нет.
Кстати, джентельмены! Чтобы не доводить до флейма: когда приводите контрпримеры — старайтесь не просто "доказать от противного", но и
— очертить круг целей
— показать, какие пули предназначены для этих целей
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ> Будем переделывать d?
Зачем? d синхронизирует свои данные — по сути это некая дополнителная обработка между обращениями к a1 и a2.
public class A1
{
public Smth Smth
{
get { return new Smth("Smth from A1"); }
}
public void DoA1(A2 a2)
{
Smth s = a2.Smth;
lock (this)
{
// некие операции с иполоьзованием this и s
}
}
}
public class A2
{
public Smth Smth
{
get { return new Smth("Smth from A2"); }
}
public void DoA2(A1 a1)
{
Smth s = a1.Smth;
lock (this)
{
// некие операции с иполоьзованием this и s
}
}
public void MethForA2()
{
lock (this)
{
// некие операции с иполоьзованием this
}
}
}
public class D
{
public void MethForA1A2(A1 a1, A2 a2)
{
// синхронизирует все операцию целиком, а не данные a1 и a2
// если additionalProcessing нет, то просто переносим все либо в A1 либо в A2lock (this)
{
a1.DoA1(a2);
additionalProcessing(a1,a2);
a2.DoA2(a1);
}
}
private void additionalProcessing(A1 a1, A2 a2)
{ }
}
Здравствуйте, ie, Вы писали:
ie>Кидающий исключение: СГ>>
СГ>>object Dequeue();
СГ>>
ie>И не кидающий его: СГ>>
СГ>>bool TryDequeue(out object obj);
СГ>>
Это имхо неверно — ислпьзовать два разных подхода. Никогда не знаешь, что от метода ожидать — придется и if'ы лепить и try.
Лучше уж сделать
object Dequeue();
НЕ кидающий исключение и при случае возвращающий null. Дополнительный if все равно придется ставить, также как и в bool TryDequeue(out object obj), а писанины меньше
Естественно, разрешение блокировать только себя — не панацея от дедлоков. Выгода — в локальности ошибки: чтобы понять в чём ошибка достаточно посмотреть на исходный текст эксклюзивных методов объектов классов 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.
Здравствуйте, Сергей Губанов, Вы писали:
ГВ>>Какой смысл утверждать, что семафоры (сиречь, отчуждаемые объекты синхронизации) — это плохо...
СГ>Да ладно Вам... я утверждал немного другое. Плохо — это когда разрешено синхронизироваться на чём угодно, а не только на том, что специально для этого и было задумано, во что был вложен именно этот смысл.
Не вижу противоречия. Семафоры как раз и задуманы для того, чтобы на них можно было синхронизироваться. Строго говоря — это вообще неделимая синхронизационная сущность. По сути, exclusive-секция, это не больше, чем управление семафором на входе/выходе и обход ожидающих conditinal expression.
СГ>Например, в C# lock работает для всяких разных expression, а .Net-овский Monitor.Enter(object) работает для любых object, что может привести к трудно обнаруживаемым ошибкам, о которых уже сообщалось здесь
Ну, скажем так. К труднообнаружимым ошибкам (ТОО) обычно приводит хаотичный процесс разработки. Сами по себе особености того или иного языка влияют на возникновение ошибок весьма косвенно.
Как раз для того, чтобы избежать ТОО в случае многопоточного програмирования можно (и нужно, на мой взгляд) выделить планирование захвата ресурсов в отдельные процедуры, возможно — взаимосинхронизированные (рассуждение с a1...d). В случае с "оберонским" подходом к реализации — выделить независимый объект, у которого все методы будут exclusive. В общем, такое разделение ничем не отличается от подхода, когда мы выделяем набор семафоров, а работу с ними помещаем в отдельные процедуры или классы. Есть только один недостаток — иной раз может потребоваться несколько таких объектов, например, по соображениям, связанным с наличием разных модулей и т.п. И вот тут-то будет не очень хорошо, поскольку в случае отчуждённых семафоров достаточно следовать политике захвата ресурсов, а в случае "оберон-подхода" придётся всякий раз модифицировать сам управляющий объект (LSP-violation? похоже, но не уверен).
Ну, скажем, для приведёного примера (он слегка надуман, ну ладно) в случае традиционного языка можно было бы поступить так:
Semaphore s_a1;
Semaphore s_a2;
Semaphore s_a1_a2;
SomeClass a1;
SomeClass a2;
// Далее, исполняем политику, в которой сказано, что при работе с a1 и a2 сначала следует залочить s_a1_a2void func_a1a2()
{
Guard g(s_a1_a2);
Guard g1(s_a1);
Guard g1(s_a2);
// Много-много чего-то.
}
void func_a1()
{
Guard g1(s_a1);
// Здесь будет что-то про a1
}
Теперь, если нам захочется добавить какой-нибудь a3, с которым нужно работать вместе с a1 и a2, то можно поступить так:
И наконец, упростим себе задачу отслеживания политики — сделаем так, чтобы блокировка одного семафора гарантированно означала блокировку ряда других семафоров. Сформируем комплексные управляющие объекты:
class MultiSemaphore : public LockableObject
{
Semaphore s_my_;
/* Список адресов остальных семафоров */ others_sems_;
public:
MultiSemaphore(Semaphore *p1, ...)
{
/*читаем в цикле через va_arg*/ other_sems_.add(other_ptr);
}
//void Lock()
{
s_my_.Lock();
/* Крутим цикл по списку others_sems_, от начала в конец */
others_sems_[i]->Lock();
}
void Unlock()
{
// NB! Действуем строго в обратном порядке по отношению к Lock()!
/* Крутим цикл по списку others_sems_, из конца в начало */
others_sems_[i]->Unlock();
s_my_.Unlock();
}
}
Тогда задача отслеживания политики блокировок несколько упрощается:
Semaphore s_a3;
MultiSemaphore s_other_a3(&s_a3, &s_a1_a2, 0);
void func func_a123()
{
Guard g(&s_other_a3);
// Всё! В этой точке мы имеем корректно заблокированый контекст.
}
Дальше решение можно усложнить — внедрить, например, в MultiSemaphore алгоритмы предсказания блокировок и т.п. В общем — можно будет поиметь славный fun и приход.
При этом, что важно, нам не понадобилось менять уже имеющийся код, как это произойдёт в случае с "диспетчерским" объектом. Случаи патологической глупости и попыток захвата s_a1 поперёд s_a1_a2 я не рассматриваю. Также, впрочем, не буду апеллировать к возможности сделать семафор средствами самого Оберона, поскольку таким образом решение задачи на Обероне сводится к "классике".
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Severn, Вы писали:
ГВ>>Здесь у тебя получается слишком много блокировок. Вернее, на каждый вызов MethForA1A2 происходит 3 захвата/освобождения объекта синхронизации. S>Точнее по одному захвату трех разных объектов. Что в этом плохого?
Да, верно. Притом захваты подчинённых объектов (a1 и a2) выполняются при каждом вызове их методов. Я, в общем-то, когда выдумывал пример, то предполагал, что захват агрегата a1+a2 нужен для какой-то, достаточно объёмной работы. Если нужно всего-то по одному разу вызвать методы a1 и a2, да ещё и с промежуточным храненим данных, то здесь, ИМХО, дополнительно блокировать весьт агрегат не нужно.
А если производится большой обмен данными (скажем — десяток вызовов a1 и столько же — a2), то можно здесь хорошо завалить производительность из-за постоянных lock/unlock на каждый вызов.
S>Если мы поместим MethForA2 внутри D, то количество блокировок уменьшится, но одновременно увеличится эффективный размер критических секций. Т.е. клент для MethForA2 будет ждать все время операции MethForA1A2, хотя ему до этого никакого дела нет.
Верно. Поэтому в таких методах нужно бороться с непредсказуемыми побочными эффектами. Например — не вызывать MessageBox. Насчёт увеличения длины критической секции — согласен, но такова плата за повышение общей производительности благодаря отказу от лишних блокировок/разблокировок.
S>По сути к этому: S>
СГ>>Лочиться только на самом себе логически следует из объединения ООП и многопоточности:
СГ>>1) Все данные инкапсулированы внутри объектов.
СГ>>2) Доступ к любым данным осуществляется только с помощью методов объекта.
СГ>>3) Для синхронизированного (эксклюзивного) доступа к данным используются эксклюзивные методы (т.е такие методы, блоки кода в которых залочены на this).
S>стоит добавить пункт про распределение обязанностей. Если обязанности (данные/методы) распределены между объектами оптимальным образом, то в чем проблема в использовании lock(this)?
Да-да. Вопрос как раз в критериях "оптимальности".
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>...поскольку таким образом решение задачи на Обероне сводится к "классике".
Не буду возражать. Я не против классики.
Кстати, вот мой вариант решения Вашей задачи про MultiSemaphore, но через Монитор.
Заранее извиняюсь, что не на Active Oberon, а на C#. Но на Active Oberon этот текст перепишется почти 1:1 заменяя:
lock (this) и Monitor.PulseAll(this) ---> BEGIN{EXCLUSIVE} ... END
while (this.IsBusy(indexies)) System.Threading.Monitor.Wait(this); ---> AWAIT(this.IsBusy(indexies))
ну, и лишаясь синтаксического сахара навроде params и foreach
namespace Test
{
public sealed class MultiSemaphore
{
private readonly bool[] busyFlags;
public MultiSemaphore (int count)
{
this.busyFlags = new bool[count];
}
private bool IsBusy (params int[] indexies)
{
foreach (int i in indexies) if (this.busyFlags[i]) return true;
return false;
}
public void Enter (params int[] indexies)
{
lock (this)
{
while (this.IsBusy(indexies)) System.Threading.Monitor.Wait(this);
foreach (int i in indexies) this.busyFlags[i] = true; // заняли
System.Threading.Monitor.PulseAll(this);
}
}
public void Exit (params int[] indexies)
{
lock (this)
{
foreach (int i in indexies) this.busyFlags[i] = false; // освободили
System.Threading.Monitor.PulseAll(this);
}
}
}
}
...я намеренно здесь не написал проверок индексов на принадлежность интервалу 0..this.busyFlags.Length-1, это конечно надо сделать в реальном коде, а этот код демонстрационный
Использование:
MultiSemaphore sem = new MultiSemaphore(10000);
// ...
sem.Enter(26, 38, 17, 237, 266, 1001);
// работа с объектами номер 26, 38, 17, 237, 266 и 1001
sem.Exit(26, 38, 17, 237, 266, 1001);
Здравствуйте, Сергей Губанов, Вы писали:
ГВ>>...поскольку таким образом решение задачи на Обероне сводится к "классике". СГ>Не буду возражать. Я не против классики.
А как это вяжется с рассуждениями о недостатках традиционного подхода?
Разница в том, что залочится в C# можно на любом (чужом) объекте (быть может, на ровном месте неожиданно заработав deadlock), а в Active Oberon — только на самом себе (только на this).
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>А как это вяжется с рассуждениями о недостатках традиционного подхода? ГВ>
ГВ>Разница в том, что залочится в C# можно на любом (чужом) объекте (быть может, на ровном месте неожиданно заработав deadlock), а в Active Oberon — только на самом себе (только на this).
В моём примере MultiSemaphore разьве лок не на this делается?
На this.
Язык C# (в частности) и .Net (вообще) разрешают кому угодно другому напрямую залочится на объект MultiSemaphore ms следующим локом:
lock (ms)
{
...
}
не обращая внимания на (в обход) специально предусмотренного ms.Enter() и ms.Exit()?
Здравствуйте, Сергей Губанов, Вы писали:
ГВ>>А как это вяжется с рассуждениями о недостатках традиционного подхода? СГ>В моём примере MultiSemaphore разьве лок не на this делается? СГ>На this.
Угу. Но наличие методов Enter/Exit как раз и лишает блокировку локальности.
СГ>Язык C# (в частности) и .Net (вообще) разрешают кому угодно другому напрямую залочится на объект MultiSemaphore ms следующим локом:
СГ>lock (ms) СГ>{ СГ> ... СГ>}
СГ>не обращая внимания на (в обход) специально предусмотренного ms.Enter() и ms.Exit()?
Хмм... Цитата из MSDN:
Monitor.Enter
[...]
Invoking this member is identical to using the C# lock statement.
Я чего-то не понимаю... Или претензия к тому, что один поток может залочиться на семафор "снаружи", а другой в это время позовёт его методы не выполнив lock(semaphore)?
СГ>Разрешает. И это есть дыра. СГ>Мои претензии — к этой дыре.
Так создавая объект "Семафор" на Обероне мы и создаём ту же самую "дыру". Даже не в профиль. То есть, сводим ряд задач к привычному "дырявому" базису. Семафор тоже может "залочить" кто угодно — для того он и создаётся, собственно.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Поторопился с ответом, ввело в заблуждение совпадение названий Monitor.Enter и MultiSemaphore.Enter. Действительно, не следует путать lock(ms) и ms.Enter/ms.Exit. В общем — да, можно считать подобное "дырой", хотя опять-таки, палка сия о двух концах. Ну так здесь дыра не в "нелокальности" блокировок, а в том, что объектом синхронизации может выступать всё что угодно, а не только специально предназначенные для этого сущности (семафоры, мьютексы, критические секции и т.п.).
Тогда, пожалуй, что соглашусь — риск налететь на deadlock при таком подходе повышается. Но, повторюсь, дело здесь не в "нелокальности", а в том, что, фактически, у нас количество семафоров равно количеству объектов (+ N * количество_объектов_value-типов за счёт боксинга).
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>Так создавая объект "Семафор" на Обероне мы и создаём ту же самую "дыру". Даже не в профиль. То есть, сводим ряд задач к привычному "дырявому" базису. Семафор тоже может "залочить" кто угодно — для того он и создаётся, собственно.
Тут есть маленький штрих. lock() позволяет извне залочить любой объект (ну, ассоциированный с этим объектом мьютекс, неважно). А если мьютекс является внутренней принадлежностью объекта (на уровне языка, как в Обероне, или на уровне библиотек, как в большинстве других языков), то он и только он решает, кому и как позволено лочить. Инкапсуляция, понимаш.
class Something
{
private:
mutable mutex guard_; // чтобы сделать Something монитором, нам нужно владеть мьютексом...public:
void foo()
{
mutex::scoped_lock exclusive(guard_); // ... и пользоваться им
.....
}
// если любишь в жизни риск, форматируй жёсткий диск
mutex& guard() const { return guard_; }
void lock() const { guard_.lock(); }
void unlock() const { guard_.unlock(); }
// никто не заставляет отдавать ручки наружу - только добрая или злая воля автора
}
Если мы на Обероне создали объект "мьютекс" и пользуемся им — то пользователь должен быть очень настойчивым и глупым, чтобы 1) напихать эти мьютексы во все структуры и 2) лочить их направо и налево.
Здравствуйте, Кодт, Вы писали:
ГВ>>Так создавая объект "Семафор" на Обероне мы и создаём ту же самую "дыру". Даже не в профиль. То есть, сводим ряд задач к привычному "дырявому" базису. Семафор тоже может "залочить" кто угодно — для того он и создаётся, собственно.
К>Тут есть маленький штрих. lock() позволяет извне залочить любой объект (ну, ассоциированный с этим объектом мьютекс, неважно).
Тогда, пожалуй, что соглашусь — риск налететь на deadlock при таком подходе повышается. Но, повторюсь, дело здесь не в "нелокальности", а в том, что, фактически, у нас количество семафоров равно количеству объектов (+ N * количество_объектов_value-типов за счёт боксинга).
В общем, мои позиции таковы:
1. Защита от deadlock-ов начинается с планирования синхронизаций и построения графа блокировок.
2. Метод реализации должен выбираться в соответствии с задачей и имеющимися языковыми средствами.
3. Реализация не должна разрушать план синхронизации.
Почему я согласен с СГ в той части, что в .Net есть "дыра"? Потому что очень легко нарушить граф синхронизации — зацепив любой объект. То есть, нужно вводить ограничения на использование определённых конструкций языка. В принципе, ничего страшного тут нет — решаемо.
Почему я не согласен с СГ в той части, что лучший способ управления — самоблокировка? Потому что иначе не было бы нужды делать семафоры в Обероне. Я не сомневаюсь, что немалую часть задач можно решить посредством EXCLUSIVE/AWAIT, просто где-то таких exclusive нужно два (захват/отпускание). С другой стороны, не всегда синхронизируемые процессы могут принадлежать одному объекту или классу.
Вывод: и в .Net дырка может проявиться, и апологетов ООП слушать ни к чему. В сухом остатке — свой мозг и планирование синхронизаций.
Вот так или примерно так.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
object MultiSemaphore;
var busy: array 1000 of boolean;
procedure IsBusy (s: array of integer): boolean;
var i: integer;
begin
for i := 0 to len(s)-1 do
if busy[s[i]] then return true end
end;
return false
end IsBusy;
procedure SetBusy (s: array of integer; val: boolean);
var i: integer;
begin
for i := 0 to len(s)-1 do busy[s[i]] := val end
end SetBusy;
procedure{public} Enter (s: array of integer);
begin{locked}
await ~IsBusy(s);
SetBusy(s, true)
end Enter;
procedure{public} Exit (s: array of integer);
begin{locked}
SetBusy(s, false)
end Exit;
end MultiSemaphore.
Здравствуйте, Дарней, Вы писали:
Д>Здравствуйте, SteMage, Вы писали:
SM>>Разумное самоограничение ВСЕХ программистов в проекте и все ок. Вот только иногда проще заставить программистов работать со средством разработки принципиально запрещающем что-то делать, нежели добится разумного самоограничения.
Д>В данном случае сделать глобальный поиск по исходникам и раздать звиздюли по его результатам — совершенно несложная задача
А если код протестирован временем и его нельзя трогать? Знаете менеджеры иногда готовы увольнять программистов за рефакторинг. Да как будем боротся с наведенными эффектами? А если так пишет единственный разработчик разбирающийся в проекте? Как то вы предлагаете простое организационное решение, которое в жизни очень часто не работает.
SM>>Хотя бесусловно обсуждаемый момент может быть не достаточно важной причиной для смены средства разработки и т.д.
Д>скорее — не может быть достаточно важной причиной. Тем более что надо не только менять средство разработки, но еще и переписывать весь код.
Может при прочих равных. Вы ведь не против типизации? Или все таки против?
Здравствуйте, SteMage, Вы писали:
SM>А если код протестирован временем и его нельзя трогать? Знаете менеджеры иногда готовы увольнять программистов за рефакторинг.
Если катастрофически нет времени в данный момент — да, они правы. А в остальных случаях увольнять надо таких менеджеров.
SM>Да как будем боротся с наведенными эффектами?
Какие еще наведенные эффекты могут быть от замены lock(this) на lock(syncObject)?
SM>А если так пишет единственный разработчик разбирающийся в проекте?
Уволить всех, влючая этого разработчика. Или валить к чертовой бабушке из такого проекта. Или оба варианта вместе.
SM>Как то вы предлагаете простое организационное решение, которое в жизни очень часто не работает.
Вот это конкретное решение, в этом конкретном случае? Ты проверял?
Д>>скорее — не может быть достаточно важной причиной. Тем более что надо не только менять средство разработки, но еще и переписывать весь код.
SM>Может при прочих равных. Вы ведь не против типизации? Или все таки против?
А что — типизация? Почти в любом языке есть конструкции, которые не имеют прямого аналога в другом языке, и вообще реализуются с трудом. К тому же язык — это не только язык, но еще и платформа, на которой он работает. Причем платформа — это не только и не столько ось, сколько набор библиотек и инструментов, созданных для этого языка.
Ты что, серьезно думаешь, что типизация поможет тебе портировать прогу с (к примеру) C# на оберон? Переписать полностью, и скорее всего перепроектировать — единственный возможный вариант. То есть, на самом деле, невозможный.
Здравствуйте, Дарней, Вы писали:
Д>Здравствуйте, SteMage, Вы писали:
Д>Какие еще наведенные эффекты могут быть от замены lock(this) на lock(syncObject)?
Чужой исходный код или отдали что-то на аутсорс, а исходников нет, а делать надо.
Д>Вот это конкретное решение, в этом конкретном случае? Ты проверял?
Вот конкретное решение я много раз проверял в жизни. Оно не работает во многих случаях. Для того чтобы оно работало должен быть довольно жестко организованный процесс разработки, а это много чего значит. Далеко не всегда это так, ваш совет увольнятся с работы (и идти например на в два раза меньшую зарплату) больше похож на утопию. Вы ВЕРИТЕ в организационные методы. Я принципиально в них НЕ ВЕРЮ.
Д>>>скорее — не может быть достаточно важной причиной. Тем более что надо не только менять средство разработки, но еще и переписывать весь код.
SM>>Может при прочих равных. Вы ведь не против типизации? Или все таки против?
А где вы видели утверждение о том, что Я предлагаю все переписать на Обероне? Например почему не начать новый проект на Обероне, если он содержит необходимые нам библиотеки? Я лишь утверждаю, что при прочих равных это может быть решаюшим доводом в пользу выбора платформы. Кто сказал, что издержки на обучения будут выше издержек на правку багов или наладки нормального процесса разработки?
Сравнение с типизацией не случайно. Типизация позволяет избежать ряда ошибок. Обсуждаемая особенность позволяет избежать ряда ошибок, при этом не создавая дополнительных проблем.
Да кстати я так и не услышал ответа на свой вопрос по поводу типизации.
Здравствуйте, SteMage, Вы писали:
Д>>Какие еще наведенные эффекты могут быть от замены lock(this) на lock(syncObject)?
SM>Чужой исходный код или отдали что-то на аутсорс, а исходников нет, а делать надо.
А что вообще ты собрался делать, если нет исходников?
И все-таки, какие вообще наведенные эффекты могут быть от такой замены в принципе?
Д>>Вот это конкретное решение, в этом конкретном случае? Ты проверял?
SM>Вот конкретное решение я много раз проверял в жизни. Оно не работает во многих случаях. Для того чтобы оно работало должен быть довольно жестко организованный процесс разработки, а это много чего значит. Далеко не всегда это так, ваш совет увольнятся с работы (и идти например на в два раза меньшую зарплату) больше похож на утопию. Вы ВЕРИТЕ в организационные методы. Я принципиально в них НЕ ВЕРЮ.
Я не верю в организационные методы, я просто их иногда использую. А еще я не верю в незыблемые принципы, потому что это уже из области религии. Одни и те же методы могут и работать, и не работать, в зависимости от обстоятельств.
Какие проблемы могут быть вот в данном конкретном случае? И вообще, почему обязательно в два раза меньшую зарплату? У меня обычно бывает наоборот
SM>А где вы видели утверждение о том, что Я предлагаю все переписать на Обероне? Например почему не начать новый проект на Обероне, если он содержит необходимые нам библиотеки? Я лишь утверждаю, что при прочих равных это может быть решаюшим доводом в пользу выбора платформы.
"прочих равных" здесь нет и быть не может. Единственной причиной для выбора оберона могут быть религиозные соображения, потому что по всем реальным характеристикам он сливает другим технологиям. Для embedded есть Symbian. Для параллельных систем — Erlang. И так далее.
SM>Сравнение с типизацией не случайно. Типизация позволяет избежать ряда ошибок. Обсуждаемая особенность позволяет избежать ряда ошибок, при этом не создавая дополнительных проблем.
А какой "ряд ошибок" может быть по причинам, которые мы сейчас рассматриваем?
SM>Да кстати я так и не услышал ответа на свой вопрос по поводу типизации.
Ты хочешь об этом поговорить? Если действительно очень хочешь, то я отвечу — сильная статическая типизация рулит в большинстве случаев.
Здравствуйте, Дарней, Вы писали:
Д>Единственной причиной для выбора оберона могут быть религиозные соображения, потому что по всем реальным характеристикам он сливает другим технологиям.
Извините, конечно, что вмешиваюсь в ваш спор, но, вроде, Ваш оппонент ничего про Oberon и не утверждал. Это во-первых, так что Ваш выпад против Oberon неуместен и непонятен. Во-вторых, коль скоро этот выпад Вами всё-таки был сделан, то со своей стороны, замечу, что Вы сказали неправду. По реальным характеристикам, например, Blackbox находится где-то на уровне технологий .Net & Java. На разных оберонах написан ряд операционных систем, в том числе операционных систем реального времени, в том числе для встраиваемых аппаратных систем. Кроме того, ведущие академические проекты осуществлялись и осуществляются именно на оберонах. Обероны успешно используются и в ряде коммерческих организаций. А на Modula (предшественнике Oberon) программируются космические спутники.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Извините, конечно, что вмешиваюсь в ваш спор, но, вроде, Ваш оппонент ничего про Oberon и не утверждал. Это во-первых, так что Ваш выпад против Oberon неуместен и непонятен.
Поскольку в данной ветке мы обсуждали фичу именно оберона, то меня очень удивляет твое утверждение, что мое заявление о нем здесь неуместно.
СГ> Во-вторых, коль скоро этот выпад Вами всё-таки был сделан, то со своей стороны, замечу, что Вы сказали неправду. По реальным характеристикам, например, Blackbox находится где-то на уровне технологий .Net & Java.
Список этих реальных характеристик — в студию! Желательно в виде таблицы с плюсиками и минусиками. Только реальных фич, а не типа "в комментарии можно вставлять картинки"
СГ>На разных оберонах написан ряд операционных систем, в том числе операционных систем реального времени, в том числе для встраиваемых аппаратных систем. Кроме того, ведущие академические проекты осуществлялись и осуществляются именно на оберонах. Обероны успешно используются и в ряде коммерческих организаций. А на Modula (предшественнике Oberon) программируются космические спутники.
да, как же много зависит от правильной постановки высказывания
Мне даже лень снова посылать тебя в googlewars... может быть, проще согласиться? (в глубоком раздумье)
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>Ну, скажем так. К труднообнаружимым ошибкам (ТОО) обычно приводит хаотичный процесс разработки. Сами по себе особености того или иного языка влияют на возникновение ошибок весьма косвенно.
Думается, между языком и процессом разработки есть связь.
IMHO, язык создается в расчете на определенный характер процесса разработки, и именно поэтому в Си так много "лазеек" (loopholes), а в Обероне — мало.
Но существует одно качество, которое нельзя купить, — это надежность. Цена надежности — погоня за крайней простотой. Это цена, которую очень богатому труднее всего заплатить.
Здравствуйте, Дарней, Вы писали:
Д>Здравствуйте, SteMage, Вы писали:
Д>>>Какие еще наведенные эффекты могут быть от замены lock(this) на lock(syncObject)?
SM>>Чужой исходный код или отдали что-то на аутсорс, а исходников нет, а делать надо.
Д>А что вообще ты собрался делать, если нет исходников? Д>И все-таки, какие вообще наведенные эффекты могут быть от такой замены в принципе?
Скажите а сборки в .NET уже отменили? Рассмотрим ситуацию у нас есть пакет основной, который делает другая организация. И есть два пакета, которые делаются на его основе. Мы делаем один пакет, а другая фирма делает другой пакет. Теперь рассмотрим ситуацию, что у нас что-то не работает. Количество возможных причин почему у нас не работает, при использовании активных объектов падает или увеличивается?
Я полагаю, что падает, поскольку исключена потенциально опасная конструкция. Я не прав?
SM>>Вот конкретное решение я много раз проверял в жизни. Оно не работает во многих случаях. Для того чтобы оно работало должен быть довольно жестко организованный процесс разработки, а это много чего значит. Далеко не всегда это так, ваш совет увольнятся с работы (и идти например на в два раза меньшую зарплату) больше похож на утопию. Вы ВЕРИТЕ в организационные методы. Я принципиально в них НЕ ВЕРЮ.
Д>Я не верю в организационные методы, я просто их иногда использую. А еще я не верю в незыблемые принципы, потому что это уже из области религии. Одни и те же методы могут и работать, и не работать, в зависимости от обстоятельств. Д>Какие проблемы могут быть вот в данном конкретном случае? И вообще, почему обязательно в два раза меньшую зарплату? У меня обычно бывает наоборот
К сожалению то, какие организационные методы использовать обычно решают не программисты, надеюсь не надо объяснять как сложно перестраивать уже существующий процесс разработки? У вас дети жена есть? Это пока вы свободный человек и у вас есть достаточно большие сбережения вы можете себе позволить менять работу тогда, когда вам удобно. А вот в остальных случаях все не так хорошо. И рецепт валить очень часто не работает.
SM>>А где вы видели утверждение о том, что Я предлагаю все переписать на Обероне? Например почему не начать новый проект на Обероне, если он содержит необходимые нам библиотеки? Я лишь утверждаю, что при прочих равных это может быть решаюшим доводом в пользу выбора платформы.
Д>"прочих равных" здесь нет и быть не может. Единственной причиной для выбора оберона могут быть религиозные соображения, потому что по всем реальным характеристикам он сливает другим технологиям. Для embedded есть Symbian. Для параллельных систем — Erlang. И так далее.
Ну походу случай клинический. Скажите мы собираемся выпустить новый процессор что делать? И сколько это стоит? Неужели вы не видите какя выстраивается пирамида с Java и .Net? И где в этой пирамиде вершина?
Тему пирамиды я готов с вами обсудить.
А вот если вдруг IBM возьмет и напишет систему использующую активные объекты, что тогда? Не я понимаю, что это очень очень мало вероятно, но все таки.
Д>Ты хочешь об этом поговорить? Если действительно очень хочешь, то я отвечу — сильная статическая типизация рулит в большинстве случаев.
Нет я проверяю вменяемость того с кем разговариваю. Если человек не признает типизацию, то ИМХО он не вменяемый.
SteMage wrote: > А вот если вдруг IBM возьмет и напишет систему использующую активные > объекты, что тогда? Не я понимаю, что это очень очень мало вероятно, но > все таки.
Да ничерта не будет. Немного другой набор примитивов синхронизации
ничего не меняет.
Вот в POSIX есть condition'ы, а в Win32 их нет. И что, мир от этого рушится?
Здравствуйте, SteMage, Вы писали:
SM>Скажите а сборки в .NET уже отменили? Рассмотрим ситуацию у нас есть пакет основной, который делает другая организация. И есть два пакета, которые делаются на его основе. Мы делаем один пакет, а другая фирма делает другой пакет. Теперь рассмотрим ситуацию, что у нас что-то не работает. Количество возможных причин почему у нас не работает, при использовании активных объектов падает или увеличивается?
SM>Я полагаю, что падает, поскольку исключена потенциально опасная конструкция. Я не прав?
я вообще не понимаю, что ты хочешь сказать. Потенциально опасная конструкция исключена у кого — "у нас" или "у них"? Это — во первых.
А во вторых — насколько опасна эта конструкция? Тебя послушать, так по сравнению с этим даже проход по памяти меркнет. А на самом деле, нужно иметь очень специфический генокод, чтобы напороться на такую проблему.
SM>К сожалению то, какие организационные методы использовать обычно решают не программисты, надеюсь не надо объяснять как сложно перестраивать уже существующий процесс разработки?
Руководитель группы не может решать, как ему вести работу в своей группе? Ну тогда из такой фирмы надо бежать, как от огня.
SM>У вас дети жена есть? Это пока вы свободный человек и у вас есть достаточно большие сбережения вы можете себе позволить менять работу тогда, когда вам удобно. А вот в остальных случаях все не так хорошо. И рецепт валить очень часто не работает.
А переход на оберон эту проблему должен решить, я так понимаю?
SM>Ну походу случай клинический. Скажите мы собираемся выпустить новый процессор что делать? И сколько это стоит? Неужели вы не видите какя выстраивается пирамида с Java и .Net? И где в этой пирамиде вершина?
ничего не понимаю. Какая еще прирамида?
SM>Тему пирамиды я готов с вами обсудить.
главное, не предлагай купить акции АО "МММ"
SM>А вот если вдруг IBM возьмет и напишет систему использующую активные объекты, что тогда? Не я понимаю, что это очень очень мало вероятно, но все таки.
Ericsson уже написал. Причем давно. Только вот обероном там даже и не пахнет. Там функциональщиной пахнет, а это уже совсем другой оборот
SM>Нет я проверяю вменяемость того с кем разговариваю. Если человек не признает типизацию, то ИМХО он не вменяемый.
Здравствуйте, Дарней, Вы писали:
Д>Список этих реальных характеристик — в студию! Желательно в виде таблицы с плюсиками и минусиками. Только реальных фич, а не типа "в комментарии можно вставлять картинки"