Fj_
|
Carpal Tunnel
|
|
|
|
Рег.: 12.09.2004
|
Сообщений: 8795
|
|
Рейтинг: 3287
|
|
Вопрос про synchronisation/concurrency (в .NET)
09.05.2008 00:15
|
|
|
Было: тред, который делает много всего, в частности иногда достает из очереди команды (делегаты или интерфейсы, не суть) и исполняет их.
Потом концепция изменилась: команды должны быть просто функциями, которые блокируются пока мастер-тред не разрешит исполнить одну (и ровно одну!), при этом мастер-тред блокируется, пока она не исполнится.
То есть нужно что-то вроде такого:
code: public class SingleTaskMutex : IDisposable
{
public bool Grant();
public IDisposable Acquire();
}
void MasterThread()
{
using (stMutex = new SingleTaskMutex())
{
while (true)
{
// do something
if (stMutex.Grant())
{
// do something more if a command was executed
}
}
}
}
string SomeSlaveFunction(string value)
{
using (master.stMutex.Acquire())
{
// do something
}
}
Ну и как это сделать, чтобы получилось надежно? Я имею в виду, что обычные примитивы для синхронизации _очень_ надежны, во время их выполнения может случиться что угодно, может кончиться память, может прилететь ThreadAbortException (или, если там где-то делается Wait(), ThreadInterrupt) - но они гарантированно оставят программу в корректном состоянии. Хочется чего-то приблизительно такого же по уровню.
Основная проблема в том, чтобы разбудить мастера, когда раб закончит выполняться. Я сделал это при помощи двух AutoResetEvent'ов и одного инта, считающего количество рабов. Если мастер видит, что есть рабы, хотящие выполняться, он дергает первый эвент (запуская очередного раба) и тут же блокируется на втором, который разблокируеся в Dispose у хелпера, полученного рабом.
Но мне это дичайше не нравится, потому что Acquire (в конструкторе хелпера) тогда выглядит так:
code:
Interlocked.Increment(ref owner.waitingSlavesCount);
try
{
owner.slaveAllowedIn.WaitOne();
}
finally
{
Interlocked.Decrement(ref owner.waitingSlavesCount);
}
и меня очень нервирует, что если во время WaitOne() прилетит Interrupt, то будет некоторое время, в течение которого раб уже смирился с мыслью, что поработать ему не дадут, но еще не успел сообщить об этом мастеру. Который теоретически может успеть дернуть slaveAllowedIn (на который теперь никто не повесится) и заблокироваться навсегда.
Я понимаю, что это крайне маловероятно, но все же стандартные примитивы очень устойчивы и я хочу сравнимой устойчивости.
Как это вообще делать правильно? Как-то нужно узнать, что разрешение было получено. Авторезетэвент такой возможности не дает, даже нельзя посмотреть на его текущее состояние, Monitor.Wait(object, int) казалось бы должен вести себя правильно, если передать 0 в качестве таймаута, но нет, не работает.
Что-то я в задумчивости. Вроде бы не такая уж эзотерическая задача...
|
The data is the error (c)IIS FTP Server. |
|
DeeMon
|
|
|
|
|
Рег.: 28.03.2004
|
Сообщений: 1746
|
Из: Siam gulf
|
Рейтинг: 3029
|
|
Re: Вопрос про synchronisation/concurrency (в .NET)
[re: Fj_]
09.05.2008 09:00
|
|
|
А нельзя выполнять их в мастер-треде, раз он все равно должен блокироваться?
В WinAPI была хорошая функция SignalObjectAndWait. В .NET есть ее аналог? Кажется, она бы тут пригодилась.
Редактировал DeeMon (09.05.2008 09:14)
|
|
Fj_
|
Carpal Tunnel
|
|
|
|
Рег.: 12.09.2004
|
Сообщений: 8795
|
|
Рейтинг: 3287
|
|
Re: Вопрос про synchronisation/concurrency (в .NET)
[re: DeeMon]
09.05.2008 17:34
|
|
|
Не, в принципе можно, конечно. Опять засовывать в очередь вейтхендл и лямбду, которая аккуратно засовывает в локальную переменную возвращаемое значение... И, кажется, работать оно будет с математической точностью, то есть в принципе не будет мест, куда можно вставить выбрасывание эксепшена с фатальным результатом. И действительно просто получится. Тем удивительней, что с требованием о блокировке и исполнении в контексте родительского треда получается фигня.
Задача-то тривиальная. Есть распределитель ресурсов, распределяющий ресурс ровно по одной штуке в руки и, вдобавок, сам им пользующийся. Как это сделать? Ну, действительно очень просто формулируется же. Однако я вчера часа четыре трахал себе моск и ничего удовлетворяющего мои запросы не придумал.
SignalObjectAndWait есть. Но оно бесполезно, повторюсь, основная проблема в том, что мы не знаем, сумеет ли тот, кому мы сигналим, отсигналить нам в ответ, да и есть ли он вообще. Wait как бы нужно делать только в том случае, если мы как-то получили подтверждение, что сигнал получен и адресат обязуется нас потом разбудить. Не знаю, у меня вообще всякие подозрения начинают возникать, что, может быть, оно в текущей формулировке не решается в принципе (потому что раб должен каким-то образом уметь не только подтверждать готовность, но и _подтверждать неготовность_, иначе как мастер, дружелюбно открывший дверь, решит, что пора ее закрыть?).
Очень загадочно!
|
The data is the error (c)IIS FTP Server. |
|
scout
|
newbie
|
|
|
|
Рег.: 18.02.2005
|
Сообщений: 45
|
|
Рейтинг: 8
|
|
Re: Вопрос про synchronisation/concurrency (в .NET)
[re: Fj_]
09.05.2008 20:37
|
|
|
В ответ на:
Задача-то тривиальная. Есть распределитель ресурсов, распределяющий ресурс ровно по одной штуке в руки и, вдобавок, сам им пользующийся. Как это сделать? Ну, действительно очень просто формулируется же. Однако я вчера часа четыре трахал себе моск и ничего удовлетворяющего мои запросы не придумал.
Из всего написанного выше не очень понятна логика этого распределителя. Как именно он должен распределять этот ресурс? Если например, им одновременно захотят воспользоваться несколько клиентов плюс сам распределитель, то в каком порядке он должен быть им предоставлен? Что должно произойти, если этот ресурс в данный момент отдан кому-то другому. Все клиенты должны заблокироваться или получить ответ, что ресурс занят, и продолжить делать что-нибудь другое? В каких потоках позволяется использовать ресурс - в клиентских или только в потоке распределителя или и там и там? Решение этой задачи через сигналы никогда не даст гарантированную устойчивость к сбоям, т.к. распределитель получается очень сильно зависим от клиента (так как клиент должен послать ему специальный сигнал - а до этого времени поток распределителя заблокирован). Чтобы таких блокировок не было может быть стоит построить общение клиента на принципе запроса - клиент спрашивает у распределителя ресурс, а тот ему либо его отдает, либо говорит, что ресурс занят. У клиента при этом должна быть возможность, либо подписаться на сообщение о том, что ресурс освободился (если ресурсом клиент должен пользоваться в своем потоке), либо встать в очередь на ресурс (если клиентская функция должна выполняться в потоке распределителя).
|
|
Fj_
|
Carpal Tunnel
|
|
|
|
Рег.: 12.09.2004
|
Сообщений: 8795
|
|
Рейтинг: 3287
|
|
Re: Вопрос про synchronisation/concurrency (в .NET)
[re: scout]
09.05.2008 22:35
|
|
|
Так. Чтобы было понятней, показываю приблизительный вариант (псевдо)кода с делегатами
code:
string SlaveMethod(string param)
{
string result;
AutoResetEvent completed = new AutoResetEvent(false);
lock(master.CommandQueue)
{
master.CommandQueue.Enqueue(new Command(completed, () =>
{
// here goes all processing...
result = SomeFunction(param);
// and some more processing.
};
}
completed.WaitOne();
return result;
}
void MasterThread
{
while (!shutDownRequested)
{
// do various tasks
// ...
Command cmd = null;
lock (CommandQueue)
{
if (CommandQueue.Count > 0)
cmd = CommandQueue.Dequeue();
}
if (null != cmd)
{
cmd.Execute();
// do some more shit after command execution
//...
// resume slave thread execution.
cmd.Completed.Set();
}
// And, possibly, do even more shit.
}
}
Вот такую штуку можно компилить (добавив необходимые классеги и, возможно, обработку эксепшенов вокруг cmd.Execute()), и она будет работать с абсолютной надежностью. Что бы ни случилось, она сработает корректно, по крайней мере с точки зрения мастера.
Вообще тут есть легкое читерство: даже если раб где-то в своем completed.WaitOne(); вылетит по эксепшену, мастер продолжит выполнять его функцию до победного конца, так что если у нее могут быть прикольные сайд-эффекты, клиенту нужно Приложить Усилия.
Так вот, надеюсь, теперь понятно, какой именно семантики хочется?
Собственно, вопрос - как ее решать, если переформулировать в терминах ресурсов: у мастера есть ресурс, он его все время держит локнутым, но иногда разлочивает и тут же залочивает обратно, в общем практически Monitor.Wait(). Но! Надо как-то сделать так, чтобы пока ресурс был разлочен, его мог бы обработать ровно один клиент. И вот тут дичайшие проблемы откуда-то берутся, Monitor.Wait() это ж фактически Monitor.Exit(obj), Thread.Sleep(0), Monitor.Enter(obj), и вот надо как-то так сделать, чтобы именно мастер успешно выполнил Monitor.Enter(), после того, как раб освободит ресурс. А не какой-нибудь другой раб, ожидающий ресурса.
Очень это странно все. Пока я писал это, несколько раз останавливался, потому что казалось, что нашел простое и изящное решение. Но нет!
|
The data is the error (c)IIS FTP Server. |
|
scout
|
newbie
|
|
|
|
Рег.: 18.02.2005
|
Сообщений: 45
|
|
Рейтинг: 8
|
|
Re: Вопрос про synchronisation/concurrency (в .NET)
[re: Fj_]
10.05.2008 00:09
|
|
|
Можно сделать как-то так:
code: namespace TestMasterSlave
{
class Resource
{ }
delegate void Action(Resource resource);
delegate void ActionResult(bool isSuccess);
class ClientAction
{
public Action Action;
public ActionResult ActionResult;
}
class Master
{
readonly Queue<ClientAction> clientActions = new Queue<ClientAction>();
readonly object lockObject = new object();
readonly Resource resource = new Resource();
public void RegisterResourceAction(ClientAction clientAction)
{
lock(lockObject)
{
this.clientActions.Enqueue(clientAction);
}
}
void MainThread()
{
while(true)
{
ClientAction currentAction = null;
lock(lockObject)
{
if (clientActions.Count > 0)
currentAction = clientActions.Dequeue();
}
//Предоставляем ресурс кому надо
if (currentAction != null)
{
currentAction.Action(resource);
//Уведомляем клиента, что мы воспользовались его обработчиком
//Передаем статус результата (возможные ошибки и т.д.)
currentAction.ActionResult(true);
}
//Теперь сами можем использовать ресурс
}
}
}
class Slave
{
public void SlaveFunction(Master master)
{
AutoResetEvent completed = new AutoResetEvent(false);
ClientAction clientAction = new ClientAction();
clientAction.Action = delegate(Resource resource)
{
//Действия с ресурсом
};
clientAction.ActionResult = delegate(bool status)
{
completed.Set();
};
master.RegisterResourceAction(clientAction);
completed.WaitOne();
}
}
}
|
|
Fj_
|
Carpal Tunnel
|
|
|
|
Рег.: 12.09.2004
|
Сообщений: 8795
|
|
Рейтинг: 3287
|
|
Re: Вопрос про synchronisation/concurrency (в .NET)
[re: scout]
10.05.2008 00:20
|
|
|
Лол, а теперь найди десять отличий между своим кодом и моим. Да, так можно сделать. Я в самом первом посте это написал.
Но у меня теперь уже принципиальный интерес: можно ли сделать это без делегатов, чтобы код рабов исполнялся в рабском же треде?
|
The data is the error (c)IIS FTP Server. |
|
scout
|
newbie
|
|
|
|
Рег.: 18.02.2005
|
Сообщений: 45
|
|
Рейтинг: 8
|
|
Re: Вопрос про synchronisation/concurrency (в .NET)
[re: Fj_]
10.05.2008 01:16
|
|
|
code: namespace TestMasterSlave
{
class Resource
{ }
class ResourceUsage
{
public readonly DateTime StartUse = DateTime.Now;
public DateTime? FinishUse = null;
public bool StartUseResource = false;
public bool FinishUseResource = false;
}
delegate ResourceUsage ResourceAvailable(Resource resource);
class Master
{
readonly Queue<ResourceAvailable> clientRequests = new Queue<ResourceAvailable>();
readonly object lockObject = new object();
readonly Resource resource = new Resource();
public void RegisterClientRequest(ResourceAvailable clientRequest)
{
lock (lockObject)
{
this.clientRequests.Enqueue(clientRequest);
}
}
void MainThread()
{
ResourceUsage currentResourceUsage = null;
while(true)
{
if (currentResourceUsage != null)
{
if (currentResourceUsage.FinishUseResource == false)
{
//проверяем, что возможно ресурс используется неоправданно долго и т.п.
if (DateTime.Now - currentResourceUsage.StartUse > TimeSpan.FromDays(365))
{
//отнимаем ресурс у текущего клиента(нужно подумать как)
}
Thread.Sleep(TimeSpan.FromSeconds(1));
continue;
}
else
{
//Сейчас никто не использует ресурс
currentResourceUsage = null;
}
}
//Ресурс сейчас никто не использует - его может использовать сам мастер
ResourceAvailable resourceAvailable = null;
lock(lockObject)
{
if (clientRequests.Count > 0)
resourceAvailable = clientRequests.Dequeue();
}
if(resourceAvailable!=null)
{
//Говорим очередному клиенту, что ресурс ему доступен
currentResourceUsage = resourceAvailable(resource);
}
}
}
}
class Slave
{
public void SlaveFunction(Master master)
{
AutoResetEvent mayBeginUseResource = new AutoResetEvent(false);
ResourceUsage resourceUsage = new ResourceUsage();
Resource resource = null;
master.RegisterClientRequest(
delegate(Resource _resource)
{
resource = _resource;
mayBeginUseResource.Set();
return resourceUsage;
});
mayBeginUseResource.WaitOne();
//Начали использовать ресурс
try
{
resourceUsage.StartUseResource = true;
//use resource
}
finally
{
resourceUsage.FinishUseResource = true;
}
}
}
}
|
|
|
|