Поочерёдно выполнять по одной итерации цикла в разных потоках

Рейтинг: 0Ответов: 3Опубликовано: 27.04.2015

Никак не соображу, как это реализовать.

Имеется рабочий поток, вызываемый N раз одновременно и поток-диспетчер. В обоих содержатся некие циклы.

Цикл потока-диспетчера:

  1. Выполняет 1 итерацию.
  2. Даёт разрешение рабочим потокам.
  3. Ждет, когда каждый из них выполнит 1 итерацию.

Цикл рабочего потока:

  1. Ждёт разрешения от диспетчера.
  2. Выполняет 1 итерацию.
  3. Сообщает о завершении итерации.

Сказали, что можно реализовать с помощью семафора, но я никак не соображу, как. Я только понял, как с его помощью можно ограничить доступ к фрагменту кода, но как быть с описанном выше случае?

Необходимо обязательно использовать потоки.

PS: если кого-то смутит отсутствие описания выхода из циклов - они конечные, просто, здесь это не важно.

Upd: похоже, меня не совсем поняли. Методы потоков выглядят так:

//поток-диспетчер
static void ProtocolThread(object input) {
  //...
  do {
    //...
    //здесь он должен давать разрешение и ждать
  } while (!complited);
  return;
}

//рабочий поток (вызывается несколько таких параллельно)
static void ThreadMethod(object input) {
  //...
  do {
    //здесь он должен ждать разрешение
    //...
    //здесь сообщить о завершении итерации (если это необходимо)
  } while (!complited);
  return;
}

Потоки запускаются так:

ThreadPool.QueueUserWorkItem(new WaitCallback(ProtocolThread));

Ответы

▲ 2Принят

Дополнительный ответ для случая, когда рабочие потоки не являются одинаковыми - и требуется дополнительная защита от выполнения одним потоком сразу двух задач.

  1. У WaitHandle есть такой метод, как SignalAndWait. Этот метод работает атомарно. Если применять его в рабочих потоках над двумя семафорами - это позволит гарантировать, что к моменту пробуждения диспетчера все рабочие потоки уже уснут.
  2. Также у семафоров есть метод, позволяющий сделать операцию Release сразу N раз, тоже атомарно. Применение этого метода в тот момент, когда все рабочие потоки спят, разбудит их всех одновременно.

При помощи этих двух трюков, доступных благодаря богатству WinAPI, можно решить задачу без ввода дополнительных примитивов.

▲ 2

Вы не должны использовать такие низкоуровневые конструкции, как семафоры и потоки. В C# есть более удобные конструкции.

Если вам нужно выполнить лишь по одному заданию, делайте так:

void Iteration() { ... }

// main
Iteration();
var tasks = new List<Task>();
for (int i = 0; i < n; i++)
    tasks.Add(Task.Run(Iteration));

await Task.WhenAll(tasks);

Если действительно нужны потоки, то

Iteration();
var threads = new List<Thread>();
for (int i = 0; i < n; i++)
{
    var t = new Threads(Iteration);
    t.Start();
    threads.Add(t);
}

foreach (var t in threads)
    t.Join();

Если рабочий поток один, можно воспользоваться producer-consumer'ом:

BlockingCollection<int> q = new BlockingCollection<int>();

// main

var t = new Thread(Consumer);
t.Start();

Iteration();

for (int i = 0; i < n; i++)
    q.Add(i);

q.CompleteAdding(); // останавливаем

t.Join();

// ----------------------------------

void Consumer()
{
    foreach (var n in q.GetConsumingEnumerable())
        Iteration();
}
▲ 1

Заведите два семафора. Одним семафором задача не решается.

Первый семафор будет использоваться для выдачи разрешения на работу. Второй - для сигнализации о окончании обработки.

А дальше делайте так, как написано в задании. Там и так все уже по шагам расписано.