Выбор минимума среди минимумов с помощью 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;
}
Я подробно описываю использование локального состояния для агрегации в своем блоге. Это в основном меняет операцию на одну операцию блокировки на поток вместо одной блокировки на элемент обработки, так что вы получаете намного более высокую пропускную способность, чем простое решение для блокировки.