Требуется найти "красивое" решение для следующей задачи.
Есть четыре потока: один "управляющий", и три "вычислительных". Задача "управляющего потока" раздавать задачи "вычислительным потокам", а задача "вычислительных" выполнять эти задачи. При этом "вычислительные потоки" должны работать по следующему алгоритму:
Стоит на паузе в ожидании задачи
Получает очередную задачу
Получает команду на старт
Выполняет вычисления
Становится на паузу в ожидании следующей задачи (возврат к первому пункту списка)
Кроме того:
Все три потока должны начинать работу "одновременно" (это ограничение само появляется из следующего требования)
Каждый поток должен дожидаться пока остальные два потока не выполнят работу
Управляющий поток должен дожидаться пока все вычислительные потоки не встанут на паузу и только после этого раздавать им задачи
Каждый поток должен быть создан только один раз
Производительность решения очень важна!
Пытаясь решить эту задачу я написал следующий тестовый пример:
И всё вроде работает, но есть две вещи которые меня не устраивают:
Наличие большого количества экземпляров AutoResetEvent
Необходимость выполнять проверку состояния потока через ThreadState
Не устраивают они меня потому, что пока нужно только три "вычислительных" потока с этим можно жить, а представьте во что это превратиться если их будет пару десятков.
Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?
Здравствуйте, Cynic, Вы писали:
C>Требуется найти "красивое" решение для следующей задачи.
Ну, на вскидку:
1. Заводим 2 объекта синхронизации. Один — системы ReaderWriterLock, другой — системы AutoResetEvent
2. Управлающий поток крутится в цикле:
while (true)
{
rwlock.AcquireWriterLock();
раздать_задания():
rwlock.ReleaseWriterLock();
event.WaitOne()
}
3. Вычислительные потоки крутятся в цикле:
while (true)
{
rwlock.AcquireReaderLock();
сделать_работу();
event.Set();
rwlock.ReleaseReaderLock()
}
Здравствуйте, Pzz, Вы писали:
Pzz>Ну, на вскидку:
Рабочий поток будет вечно круги нарезать.
Рабочий поток может начать работать до того как получит задание.
итп
Можно обойтись одним барьером:
Barrier barrier = new Barrier(3 + 1);//3 рабочих потока плюс 1 управляющий
...
while (true)
{
раздать_задания();
barrier.SignalAndWait();//запускает рабочие потоки
barrier.SignalAndWait();//ждет, когда рабочие потоки закончат работу
}
...
while (true)
{
barrier.SignalAndWait();//ждёт задание
сделать_работу();
barrier.SignalAndWait();//запускает получение нового задания
}
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, Cynic, Вы писали:
C>Не устраивают они меня потому, что пока нужно только три "вычислительных" потока с этим можно жить, а представьте во что это превратиться если их будет пару десятков. C>Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?
Здравствуйте, mapnik, Вы писали:
M>Отличный совет! А где же совет учить Windows Longhorn, ObjectSpaces и WinFS. И что там еще у MS инновационного?
С разморозкой
Здравствуйте, Sinix, Вы писали:
S>Таски были "инновационными" в 2007м, если чо.
Вот это новость!
Пока космические корабли с софтом на java бороздят просторы вселенной и Марса как минимум 10 лет, MS внедряет инновационные "таски" и async/await. Видимо с помощью "программистов шарепоинт".
Здравствуйте, mapnik, Вы писали:
M>Пока космические корабли с софтом на java бороздят просторы вселенной и Марса как минимум 10 лет, MS внедряет инновационные "таски" и async/await. Видимо с помощью "программистов шарепоинт".
У btn1 появился конкурент
Это тематический раздел. Для посраться и показать себя красивого есть КСВ и мусорные разделы. Если не можете общаться без набросов — проходите мимо пожалуйста.
Здравствуйте, Sinix, Вы писали:
S>Это тематический раздел. Для посраться и показать себя красивого есть КСВ и мусорные разделы. Если не можете общаться без набросов — проходите мимо пожалуйста.
Да вы что? И что теперь в тематических разделах на вопросы по синхронизации потоков модно отвечать — "а иди как ты учи TPL", особенно когда человек пишет "Производительность решения очень важна!".
Я бы за такой ответ сразу банил, если совсем уже грубо
Здравствуйте, Cynic, Вы писали:
C>Требуется найти "красивое" решение для следующей задачи. C>Есть четыре потока: один "управляющий", и три "вычислительных". Задача "управляющего потока" раздавать задачи "вычислительным потокам", а задача "вычислительных" выполнять эти задачи. При этом "вычислительные потоки" должны работать по следующему алгоритму: C>
C> Стоит на паузе в ожидании задачи C> Получает очередную задачу C> Получает команду на старт C> Выполняет вычисления C> Становится на паузу в ожидании следующей задачи (возврат к первому пункту списка) C>C>Кроме того: C>
C> Все три потока должны начинать работу "одновременно" (это ограничение само появляется из следующего требования) C> Каждый поток должен дожидаться пока остальные два потока не выполнят работу C> Управляющий поток должен дожидаться пока все вычислительные потоки не встанут на паузу и только после этого раздавать им задачи C> Каждый поток должен быть создан только один раз C> Производительность решения очень важна! C>
Если .net >= 4.0, то:
1) Я бы посмотрел на блокирующую очередь для ожидания новой
задачи;
2) Для синхронизации ("дожидания") выполнения потоков барьер уже посоветовали выше.
M>Да вы что? И что теперь в тематических разделах на вопросы по синхронизации потоков модно отвечать — "а иди как ты учи TPL", особенно когда человек пишет "Производительность решения очень важна!".
Я ещё раз попытаюсь, ок?
1. Вы не знаете про таски вообще ничего. Иначе не писали бы про производительность, инновационность тасков и про мёртвую технологию. Все три утверждения — мимо.
2. Вам топик интересен именно на предмет посраться. Вот это
что там еще у MS инновационного?
...
космические корабли с софтом на java бороздят просторы вселенной и Марса как минимум 10 лет, MS внедряет инновационные "таски" и async/await. Видимо с помощью "программистов шарепоинт"
не я писал.
3. TPL тут именно что правильное решение. Изучить недолго, всяко быстрее, чем изобрести свой велосипед с похожими характеристиками.
Если есть аргументированные возражения — велкам. Если нет — я пас.
Здравствуйте, Cynic, Вы писали: C>Требуется найти "красивое" решение для следующей задачи. C>Есть четыре потока: один "управляющий", и три "вычислительных". Задача "управляющего потока" раздавать задачи "вычислительным потокам", а задача "вычислительных" выполнять эти задачи. При этом "вычислительные потоки" должны работать по следующему алгоритму:
Таски наше всё. Пишется за 5 минут, работает с первого раза. UPD: НЕ работает с первого раза, см ниже.
код
enum State
{
Idle, GetResult, Process
}
static volatile State state;
static async Task<int> GetNextResult(int i)
{
Debug.Assert(state == State.GetResult, "Fail");
Console.WriteLine("Geting result #{0}", i);
var delayMs = (i % 5) * 200;
await Task.Delay(TimeSpan.FromMilliseconds(delayMs));
Debug.Assert(state == State.GetResult, "Fail");
Console.WriteLine("Result #{0} ready, moving on", i);
return i;
}
static async Task<int> Process(int worker, int i)
{
Debug.Assert(state == State.Process, "Fail");
Console.WriteLine("Worker{0}: processing result #{1}", worker, i);
var delayMs = (i % 7) * 150;
await Task.Delay(TimeSpan.FromMilliseconds(delayMs));
Debug.Assert(state == State.Process, "Fail");
Console.WriteLine("Worker{0}: result #{1} processed", worker, i);
return i;
}
static async Task Run(CancellationToken ct)
{
int num = 0;
int taskCount = 3;
while (!ct.IsCancellationRequested)
{
state = State.GetResult;
var tasks = Enumerable.Range(0, taskCount).Select(i => GetNextResult(num + i));
await Task.WhenAll(tasks);
state = State.Process;
tasks = Enumerable.Range(0, taskCount).Select(i => Process(i, num + i));
await Task.WhenAll(tasks);
state = State.Idle;
num += taskCount;
}
}
static void Main()
{
Console.WriteLine("Starting, any key to exit");
var cts = new CancellationTokenSource();
var task = Run(cts.Token);
Console.ReadKey();
cts.Cancel();
task.Wait();
}
Это на случай, если получение-обработку надо разделить. Если не надо, всё тоже несложно — producer-consumer и ConcurrentQueue.
UPD: Позорище и ещё раз позорище
Вот это
while (!ct.IsCancellationRequested)
{
state = State.GetResult;
var tasks = Enumerable.Range(0, taskCount).Select(i => GetNextResult(num + i)); // (1)
await Task.WhenAll(tasks);
state = State.Process;
tasks = Enumerable.Range(0, taskCount).Select(i => Process(i, num + i)); // (2)
await Task.WhenAll(tasks);
state = State.Idle;
num += taskCount;
}
надо заменить на
while (!ct.IsCancellationRequested)
{
state = State.GetResult;
var tasks = Enumerable.Range(0, taskCount).Select(i => GetNextResult(num + i)).ToArray(); // (1)
await Task.WhenAll(tasks);
state = State.Process;
var tasks2 = tasks.Select((t, i) => Process(i, t.Result)).ToArray(); // (2)
await Task.WhenAll(tasks2);
num += taskCount;
state = State.Idle;
}
С (2) всё понятно — надо обрабатывать результат, а не "Enumerable.Range(0, taskCount)".
С (1) всё чуть сложнее. Кто сообразит, почему там нужен .ToArray() (подчеркнул) — тому пряник.
UPD2:
Ну и правильный вариант использования "Task.WhenAll()", чтобы закрыть тему.
while (!ct.IsCancellationRequested)
{
state = State.GetResult;
var resultTasks = Enumerable.Range(0, taskCount).Select(i => GetNextResult(num + i));
var results = await Task.WhenAll(resultTasks);
state = State.Process;
var processingTasks = results.Select((result, ix) => Process(ix, result)).ToArray();
await Task.WhenAll(processingTasks);
num += taskCount;
state = State.Idle;
}
C>Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?
Есть, можно сказать, уже классическая статья Joe Albahary Threading in C#. Там множество наглядных примеров, в том числе и на аналогичные задачи, если я правильно помню.
Здравствуйте, Cynic, Вы писали:
C>Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?
1) Самый правильный способ — забить вообще про игры с потоками и просто использовать TPL. Потому что очень похоже что ты изобретаешь плохую и тормозную его версию.
2) Если уж хочется руками, то выкинуть примитивы ОС — они медленные, могут дать провал перформанса на 1-2 порядка. Вместо этого использовать lock, ReaderWriterLockSlim и Monitor.
3) Использовать параллельные коллекции типа ConcurrentQueue — большая часть операций с ними реализована lock free, это может еще до 10-20% прибытку в перформансе дать.
4) А дальше чисто алгоритмыческие фишки типа work stealing или локализации областей совместно изменяемых данных, но про них без подробного описания твоей задачи говорить бессмысленно.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали: AVK>2) Если уж хочется руками, то выкинуть примитивы ОС — они медленные, могут дать провал перформанса на 1-2 порядка. Вместо этого использовать lock, ReaderWriterLockSlim и Monitor.
Я конечно извиняюсь, скорее всего я туплю, но как можно использовать например ReaderWriterLockSlim для моей задачи
Мне нужно, чтобы например три рабочих потока взяли себе по задаче выполнили их и при этом каждый из них дождался пока закончат остальные, потом все снова взяли себе по задаче и т.д. lock() (ну и Monitor) я ещё как то смог прикрутить к этой задаче, а вот про ReaderWriterLockSlim идей чего то нету. Можно примерчик
Здравствуйте, nikov, Вы писали:
N>Советую забыть про потоки и учить таски и TPL.
По поводу TPL: Правильно ли я понимаю, что TPL реализует паттерн поставщик-потребитель, т.е. создаёт один или несколько рабочих потоков и скармливает им задачи. При это сами потоки не создаются и уничтожаются каждый раз для новой задачи? Вопрос такой возник, потому что важна производительность решения.
Здравствуйте, Cynic, Вы писали:
C>По поводу TPL: Правильно ли я понимаю, что TPL реализует паттерн поставщик-потребитель, т.е. создаёт один или несколько рабочих потоков и скармливает им задачи. При этом сами потоки не создаются и уничтожаются каждый раз для новой задачи? Вопрос такой возник, потому что важна производительность решения.
Не совсем так. Скорее, таски — это довольно толковая реализация пары work item + шедулер.
Шадулер может быть любой, по умолчанию используется крайне эффективный пул задач. Плюс с тасками и await очень удобно писать код с non-blocking wait. Вот тут