Здравствуйте, 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), а писанины меньше