Выбор минимума среди минимумов с помощью Parallel.ForEach

Я новичок в C#, Parallel.ForEachи.NET в целом. Я хочу распараллелить поиск, который включает тысячи местоположений. Для каждого местоположения я вычисляю большое расстояние по кругу. Это расчет, который я хочу распространить на разные ядра. Мой вопрос: как мне это сделать, если у меня есть только одна локальная переменная потока, как в этом примере MSDN TPL? Для результата я посмотрел на Interlockedи увидел его варианты Add, CompareExchange, Decrement, Exchange, Increment а также Read, но я не просто добавляю, увеличиваю, уменьшаю или проверяю равенство. Я хочу вернуть объект через несколько потоков, работающих параллельно, который имеет самое короткое общее расстояние. Моя интуиция говорит, что это должно быть легко, что я должен быть в состоянии создать какой-то маленький объект, который оборачивает Location и расстояние, но как мне получить лучший ответ из каждой цепочки, а затем выбрать кратчайшее расстояние среди них? Вот непараллельная версия:

Location findClosestLocation(Location myLocation, List<Location> allLocations)
{
  double closest = double.MaxValue;
  Location closestLoc = null;
  foreach (Location aLoc in allLocations)
  {
    if (aLoc != myLocation)
    {
      double d = greatCircle(myLocation, aLoc);
      if (d < closest)
      {
        closest = d;
        closestLoc = aLoc;
      }
    }
  }
  return closestLoc;
}

Я видел пост в блоге DDJ, который, казалось, предлагал хороший совет, но мне было интересно, был ли это лучший совет. Я вижу, как автор перебирает массивы, и задаюсь вопросом, не существует ли более функционального способа сделать это. В функциональном мире я бы использовал map, lambda а также min,

1 ответ

Решение

Самый простой вариант - переключиться на PLINQ:

Location findClosestLocation(Location myLocation, List<Location> allLocations)
{
     return allLocations
               .AsParallel()
               .Min(location => greatCircle(myLocation, location));
}

При этом это в основном просто агрегация с параллельными конструкциями. У вас есть несколько вариантов, если вы хотите придерживаться класса Parallel. Одним из вариантов было бы синхронизировать это внутри блока, используя блокировку. Я не рекомендовал бы это, поскольку это повредит вашей общей производительности.

Лучшим вариантом является использование методов Parallel.ForEach, которые обеспечивают локальное состояние. Они позволят вам переписать это как:

Location findClosestLocation(Location myLocation, List<Location> allLocations)
{
  double closest = double.MaxValue;
  Location closestLoc = null;
  object sync = new object();

  Parallel.ForEach<Location, Tuple<double,Location>(
      allLocations,
      () => new Tuple(double.MaxValue, null),
      (location, loopState, localState) =>
      {
          double d = greatCircle(myLocation, aLoc);
          if (d < localState.Item1)
              return new Tuple(d, aLoc);
          else
              return localState;
      },
      localState =>
      {
          lock(sync)
          {
              if (localState.Item1 < closest)
              {
                  closest = localState.Item1;
                  closestLoc = localState.Item2;
              }
          }
      }
  );
  return closestLoc;
}

Я подробно описываю использование локального состояния для агрегации в своем блоге. Это в основном меняет операцию на одну операцию блокировки на поток вместо одной блокировки на элемент обработки, так что вы получаете намного более высокую пропускную способность, чем простое решение для блокировки.

Другие вопросы по тегам