Красивое решение для задачи с потоками
От: Cynic Россия  
Дата: 12.03.15 21:06
Оценка: -1
Требуется найти "красивое" решение для следующей задачи.
Есть четыре потока: один "управляющий", и три "вычислительных". Задача "управляющего потока" раздавать задачи "вычислительным потокам", а задача "вычислительных" выполнять эти задачи. При этом "вычислительные потоки" должны работать по следующему алгоритму:
Кроме того:
Пытаясь решить эту задачу я написал следующий тестовый пример:
    class Program
    {
        static AutoResetEvent mt = new AutoResetEvent(false);
        static AutoResetEvent th1 = new AutoResetEvent(false);
        static AutoResetEvent th2 = new AutoResetEvent(false);
        static AutoResetEvent th3 = new AutoResetEvent(false);

        static Thread mainThread;
        static Thread thread1;
        static Thread thread2;
        static Thread thread3;

        static void Main(string[] args)
        {
            mainThread = new Thread(MainThread);
            mainThread.IsBackground = true;

            thread1 = new Thread(Thread1);
            thread1.IsBackground = true;

            thread2 = new Thread(Thread2);
            thread2.IsBackground = true;

            thread3 = new Thread(Thread3);
            thread3.IsBackground = true;

            thread1.Start();
            thread2.Start();
            thread3.Start();
            mainThread.Start();

            Console.ReadLine();
        }

        static void MainThread()
        {
            while (true)
            {
                Console.Write("\nT ");

                if ((thread1.ThreadState & ThreadState.WaitSleepJoin) != ThreadState.WaitSleepJoin ||
                    (thread2.ThreadState & ThreadState.WaitSleepJoin) != ThreadState.WaitSleepJoin ||
                    (thread3.ThreadState & ThreadState.WaitSleepJoin) != ThreadState.WaitSleepJoin)
                {
                    mt.WaitOne();
                }

                if ((thread1.ThreadState & ThreadState.Running) == ThreadState.Running ||
                    (thread2.ThreadState & ThreadState.Running) == ThreadState.Running ||
                    (thread3.ThreadState & ThreadState.Running) == ThreadState.Running)
                {
                    th1.Set();
                    th2.Set();
                    th3.Set();
                }

                Thread.Sleep(1000);
            }
        }

        static void Thread1()
        {
            while (true)
            {
                mt.Set();
                th1.WaitOne();
                Console.Write("1");
            }
        }

        static void Thread2()
        {
            while (true)
            {
                mt.Set();
                th2.WaitOne();
                Console.Write("2");
            }
        }

        static void Thread3()
        {
            while (true)
            {
                mt.Set();
                th3.WaitOne();
                Console.Write("3");
            }
        }
    }

И всё вроде работает, но есть две вещи которые меня не устраивают:
Не устраивают они меня потому, что пока нужно только три "вычислительных" потока с этим можно жить, а представьте во что это превратиться если их будет пару десятков.
Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?
:)
Отредактировано 12.03.2015 21:52 Cynic . Предыдущая версия . Еще …
Отредактировано 12.03.2015 21:09 Cynic . Предыдущая версия .
Отредактировано 12.03.2015 21:08 Cynic . Предыдущая версия .
Отредактировано 12.03.2015 21:08 Cynic . Предыдущая версия .
Отредактировано 12.03.2015 21:07 Cynic . Предыдущая версия .
Re: Красивое решение для задачи с потоками
От: Pzz Россия https://github.com/alexpevzner
Дата: 12.03.15 21:32
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Требуется найти "красивое" решение для следующей задачи.


Ну, на вскидку:
1. Заводим 2 объекта синхронизации. Один — системы ReaderWriterLock, другой — системы AutoResetEvent
2. Управлающий поток крутится в цикле:
  while (true)
  {
    rwlock.AcquireWriterLock();
    раздать_задания():
    rwlock.ReleaseWriterLock();
    event.WaitOne()
  }

3. Вычислительные потоки крутятся в цикле:
  while (true)
  {
    rwlock.AcquireReaderLock();
    сделать_работу();
    event.Set();
    rwlock.ReleaseReaderLock()
  }
Re[2]: Красивое решение для задачи с потоками
От: WolfHound  
Дата: 13.03.15 01:15
Оценка: 12 (2) +2
Здравствуйте, 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) А. Эйнштейн
Re: Красивое решение для задачи с потоками
От: nikov США http://www.linkedin.com/in/nikov
Дата: 13.03.15 04:49
Оценка: 11 (3) +7 -1
Здравствуйте, Cynic, Вы писали:

C>Не устраивают они меня потому, что пока нужно только три "вычислительных" потока с этим можно жить, а представьте во что это превратиться если их будет пару десятков.

C>Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?

Советую забыть про потоки и учить таски и TPL.
Re[2]: Красивое решение для задачи с потоками
От: mapnik США http://www.hooli.xyz/
Дата: 13.03.15 06:39
Оценка: -7 :)
Здравствуйте, nikov, Вы писали:

N>Советую забыть про потоки и учить таски и TPL.


Отличный совет! А где же совет учить Windows Longhorn, ObjectSpaces и WinFS. И что там еще у MS инновационного?
Re[3]: Красивое решение для задачи с потоками
От: Sinix  
Дата: 13.03.15 06:59
Оценка: +1 -1
Здравствуйте, mapnik, Вы писали:

M>Отличный совет! А где же совет учить Windows Longhorn, ObjectSpaces и WinFS. И что там еще у MS инновационного?

С разморозкой

Таски были "инновационными" в 2007м, если чо.
Re[4]: Красивое решение для задачи с потоками
От: mapnik США http://www.hooli.xyz/
Дата: 13.03.15 07:04
Оценка: -7
Здравствуйте, Sinix, Вы писали:

S>Таски были "инновационными" в 2007м, если чо.


Вот это новость!
Пока космические корабли с софтом на java бороздят просторы вселенной и Марса как минимум 10 лет, MS внедряет инновационные "таски" и async/await. Видимо с помощью "программистов шарепоинт".
Re[5]: Красивое решение для задачи с потоками
От: Sinix  
Дата: 13.03.15 07:21
Оценка: +13 -1 :)
Здравствуйте, mapnik, Вы писали:

M>Пока космические корабли с софтом на java бороздят просторы вселенной и Марса как минимум 10 лет, MS внедряет инновационные "таски" и async/await. Видимо с помощью "программистов шарепоинт".

У btn1 появился конкурент

Это тематический раздел. Для посраться и показать себя красивого есть КСВ и мусорные разделы. Если не можете общаться без набросов — проходите мимо пожалуйста.
Re[6]: Красивое решение для задачи с потоками
От: mapnik США http://www.hooli.xyz/
Дата: 13.03.15 07:24
Оценка: -7
Здравствуйте, Sinix, Вы писали:

S>Это тематический раздел. Для посраться и показать себя красивого есть КСВ и мусорные разделы. Если не можете общаться без набросов — проходите мимо пожалуйста.


Да вы что? И что теперь в тематических разделах на вопросы по синхронизации потоков модно отвечать — "а иди как ты учи TPL", особенно когда человек пишет "Производительность решения очень важна!".
Я бы за такой ответ сразу банил, если совсем уже грубо
Re: Красивое решение для задачи с потоками
От: mapnik США http://www.hooli.xyz/
Дата: 13.03.15 07:38
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Производительность решения очень важна!


Я бы использовал Monitor вместо AutoResetEvent. AutoResetEvent довольно медленное решение. Напр. здесь есть некоторая статистика по методам синхронизации для потоков, возможно она будет вам полезна — http://blog.teamleadnet.com/2012/02/why-autoresetevent-is-slow-and-how-to.html
Re[3]: Красивое решение для задачи с потоками
От: Pzz Россия https://github.com/alexpevzner
Дата: 13.03.15 07:59
Оценка:
Здравствуйте, WolfHound, Вы писали:

Pzz>>Ну, на вскидку:

WH>Рабочий поток будет вечно круги нарезать.

Да, согласен.
Re: Красивое решение для задачи с потоками
От: Sharov Россия  
Дата: 13.03.15 08:02
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Требуется найти "красивое" решение для следующей задачи.

C>Есть четыре потока: один "управляющий", и три "вычислительных". Задача "управляющего потока" раздавать задачи "вычислительным потокам", а задача "вычислительных" выполнять эти задачи. При этом "вычислительные потоки" должны работать по следующему алгоритму:
C> C>Кроме того:
C>

Если .net >= 4.0, то:
1) Я бы посмотрел на блокирующую очередь для ожидания новой
задачи;
2) Для синхронизации ("дожидания") выполнения потоков барьер уже посоветовали выше.
Кодом людям нужно помогать!
Re[7]: Красивое решение для задачи с потоками
От: Sinix  
Дата: 13.03.15 08:04
Оценка: 144 (5) +8
Здравствуйте, mapnik, Вы писали:


M>Да вы что? И что теперь в тематических разделах на вопросы по синхронизации потоков модно отвечать — "а иди как ты учи TPL", особенно когда человек пишет "Производительность решения очень важна!".


Я ещё раз попытаюсь, ок?

1. Вы не знаете про таски вообще ничего. Иначе не писали бы про производительность, инновационность тасков и про мёртвую технологию. Все три утверждения — мимо.

2. Вам топик интересен именно на предмет посраться. Вот это

что там еще у MS инновационного?
...
космические корабли с софтом на java бороздят просторы вселенной и Марса как минимум 10 лет, MS внедряет инновационные "таски" и async/await. Видимо с помощью "программистов шарепоинт"

не я писал.

3. TPL тут именно что правильное решение. Изучить недолго, всяко быстрее, чем изобрести свой велосипед с похожими характеристиками.


Если есть аргументированные возражения — велкам. Если нет — я пас.
Re: Красивое решение для задачи с потоками
От: Sinix  
Дата: 13.03.15 09:21
Оценка: 64 (3)
Здравствуйте, 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;
        }


Мораль: пишите ассерты.
Отредактировано 13.03.2015 11:20 Sinix . Предыдущая версия . Еще …
Отредактировано 13.03.2015 10:50 Sinix . Предыдущая версия .
Re: Красивое решение для задачи с потоками
От: vorona  
Дата: 13.03.15 09:31
Оценка:
Здравствуйте, Cynic, Вы писали:

static class Programm
{
    static void Main()
    {
        int count = 4;
        var workers = Enumerable.Range(1, count).Select(i => (Action)(() => Console.WriteLine(i))).ToArray();

        var tcs = new TaskCompletionSource<bool>();
        int index = 0;
        do
        {
            TaskCompletionSource<bool> tcs2 = tcs;
            foreach (Action worker in workers)
            {
                Task.Run(async () =>
                {
                    worker();
                    if ((Interlocked.Increment(ref index) % count) == 0)
                    {
                        tcs = new TaskCompletionSource<bool>();
                        tcs2.SetResult(true);
                    }
                    await tcs2.Task;
                });
            }
            tcs2.Task.Wait();
            Console.WriteLine("do work, or esc for exit");
        }
        while (Console.ReadKey().Key != ConsoleKey.Escape);
    }
}
Re: Красивое решение для задачи с потоками
От: IB Австрия http://rsdn.ru
Дата: 13.03.15 09:43
Оценка: +2
Здравствуйте, Cynic, Вы писали:


C>Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?

Есть, можно сказать, уже классическая статья Joe Albahary Threading in C#. Там множество наглядных примеров, в том числе и на аналогичные задачи, если я правильно помню.
Мы уже победили, просто это еще не так заметно...
Re: Красивое решение для задачи с потоками
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 13.03.15 10:03
Оценка: +3
Здравствуйте, 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>>
AVK Blog
Re[2]: Красивое решение для задачи с потоками
От: Cynic Россия  
Дата: 13.03.15 17:25
Оценка:
Здравствуйте, AndrewVK, Вы писали:
AVK>2) Если уж хочется руками, то выкинуть примитивы ОС — они медленные, могут дать провал перформанса на 1-2 порядка. Вместо этого использовать lock, ReaderWriterLockSlim и Monitor.

Я конечно извиняюсь, скорее всего я туплю, но как можно использовать например ReaderWriterLockSlim для моей задачи
Мне нужно, чтобы например три рабочих потока взяли себе по задаче выполнили их и при этом каждый из них дождался пока закончат остальные, потом все снова взяли себе по задаче и т.д. lock() (ну и Monitor) я ещё как то смог прикрутить к этой задаче, а вот про ReaderWriterLockSlim идей чего то нету. Можно примерчик

p/s: Про TPL подумаю.
:)
Отредактировано 13.03.2015 17:26 Cynic . Предыдущая версия .
Re[2]: Красивое решение для задачи с потоками
От: Cynic Россия  
Дата: 14.03.15 07:57
Оценка:
Здравствуйте, nikov, Вы писали:

N>Советую забыть про потоки и учить таски и TPL.


По поводу TPL: Правильно ли я понимаю, что TPL реализует паттерн поставщик-потребитель, т.е. создаёт один или несколько рабочих потоков и скармливает им задачи. При это сами потоки не создаются и уничтожаются каждый раз для новой задачи? Вопрос такой возник, потому что важна производительность решения.
:)
Re[3]: Красивое решение для задачи с потоками
От: Sinix  
Дата: 14.03.15 10:26
Оценка:
Здравствуйте, Cynic, Вы писали:

C>По поводу TPL: Правильно ли я понимаю, что TPL реализует паттерн поставщик-потребитель, т.е. создаёт один или несколько рабочих потоков и скармливает им задачи. При этом сами потоки не создаются и уничтожаются каждый раз для новой задачи? Вопрос такой возник, потому что важна производительность решения.


Не совсем так. Скорее, таски — это довольно толковая реализация пары work item + шедулер.

Шадулер может быть любой, по умолчанию используется крайне эффективный пул задач. Плюс с тасками и await очень удобно писать код с non-blocking wait. Вот тут
Автор: Sinix
Дата: 13.02.14
был пример, можете попробовать достичь того же без тасков.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.