Управляющий параллелизм с условной блокировкой?

Подводя итог, у меня есть рекурсивная задача, и я хочу использовать все 4 из моих процессоров для обработки этого действительно большого дерева быстрее. Моя текущая производственная реализация использует Parallel.ForEach и выходит из-под контроля, привязывая все 4 моего процессора и быстро исчерпав память. Итак, я знаю, что правильный алгоритм может дать мне все 4 процессора на 70-80%, которые, как я нашел, быстро выполнит работу по обходам, в то время как пользовательский интерфейс будет реагировать, а мой компьютер в целом реагирует на легкие пользовательские пользовательские задачи. Эта задача является фоновой задачей.

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

Я хочу, чтобы этот код использовал максимум 4 потока, чтобы рекурсивно создавать 20 страшных голов, пока не будет достигнута глубина вложенности 10 во всех ветвях. Я изменил его с 2 до 20 голов, потому что это больше похоже на мою реальную проблему. Мое фактическое дерево имеет только 4-5 уровней, но довольно широкий, и каждый узел требует гораздо больше процессора, чем Console.WriteLine.

Это было не так просто, как я предполагал.

Я пытаюсь сделать все потоки больше, чем 4, до тех пор, пока не будет достаточно потоков, которые были до конца, чтобы довести общее количество потоков до 4 до их продолжения. Итак, это нормально, если создано несколько более 4 потоков, если только те> # 4 просто ждут. Следовательно, условная часть ожидания (блокировки).

Мой пример кода явно предназначен для понятий, и это именно то, что я пробовал. Не стесняйтесь отклоняться от деталей моей реализации.

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

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

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

 public class ScaryTeddy
 {
 public ScaryTeddy(ScaryTeddy parent, int position)
 {
 Parent = parent;
 Position = position;
 DoSomethingHeavy();
 }

 public BlockingCollection<scaryteddy> Heads = new BlockingCollection<scaryteddy>();
 public ScaryTeddy Parent { get; set; }
 private string _path;
 public string Path
 {
 get
 {
 if (_path == null)
 {
 if (Parent != null)
 _path = string.Format("{0}.{1}", Parent.Path, Position);
 else
 _path = Position.ToString();
 }
 return _path;
 }
 }

 public int Position { get; set; }

 // short in duration but taxing on cpu and memory
 private static void DoSomethingHeavy()
 {
 // look at all the text inside every jpg in my pictures. Admire my girl friend beauty!
 FileSystem.FindInFilesFileList(@"C:\Documents\Pictures", new List<string>() { "Exif" }, new List<string>() { "*.jpg" }, null, null);
 }

 // these have to be static b/c CreateScaryTeddy is static
 private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(4, 4); // 4 cpus
 private static object _lock = new object(); // one object for all instances? Is that correct?
 private static int _scaryTeddyFactories = 0; // just a way to inspect how many are concurrent

 // this only produces 2 cpus working at about 20%; I want all 4 working at 70-80%
 public static ScaryTeddy CreateScaryTeddy(ScaryTeddy parent = null, int position = 1)
 {
 SemaphoreSlim.Wait();
 lock (_lock) _scaryTeddyFactories++;
 var scaryTeddy = new ScaryTeddy(parent, position);
 Console.WriteLine("Thread {0} with slot {1} created Scary Teddy {2}", Thread.CurrentThread.ManagedThreadId, _scaryTeddyFactories, scaryTeddy.Path);
 lock (_lock) _scaryTeddyFactories--;
 SemaphoreSlim.Release();

 if (scaryTeddy.Path.Split(".".ToCharArray()).Length <= 10)
 {
 Parallel.For(0, 20,
 new ParallelOptions { MaxDegreeOfParallelism = 2 },
 babyHead => scaryTeddy.Heads.Add(CreateScaryTeddy(scaryTeddy, babyHead)));
 }

 return scaryTeddy;
 }
 }
</string></string></scaryteddy></scaryteddy>

Изменение: результаты

Все 4 процессора почти привязаны - отлично!

Консольный вывод показывает, что задействован пул потоков. Я предполагаю, что семафор работает, так как открытый слот ВСЕГДА # 4?

Thread 1 with slot 1 created Scary Teddy 1
Thread 6 with slot 2 created Scary Teddy 1.10
Thread 1 with slot 3 created Scary Teddy 1.0
The thread '<no name="">' (0x1668) has exited with code 0 (0x0).
The thread '<no name="">' (0x3bd0) has exited with code 0 (0x0).
Thread 5 with slot 4 created Scary Teddy 1.10.0
Thread 1 with slot 4 created Scary Teddy 1.0.10
Thread 6 with slot 4 created Scary Teddy 1.10.10
Thread 3 with slot 4 created Scary Teddy 1.0.0
Thread 5 with slot 4 created Scary Teddy 1.10.0.0
Thread 1 with slot 4 created Scary Teddy 1.0.10.0
Thread 9 with slot 4 created Scary Teddy 1.0.10.10
Thread 6 with slot 4 created Scary Teddy 1.10.10.0
</no></no>

У нас есть 4 потока, выполняющих работу, чего я хотел, и остальная часть пула ждет, чего ИМХО не так уж много.

1 ответ

Замените механизм дросселирования outOfControl new SemaphoreSlim(4). Это встроено в структуру. (Кроме того, вы читаете _scaryTeddyFactoryCount без блокировки, которая небезопасна, но это исчезает, когда вы используете семафор.)

Внесите переменные экземпляра статических переменных. В настоящий момент их ценности опасно распределяются между всеми экземплярами.

Возможно, вы захотите переключиться на асинхронный стиль ожидания (например, используя async/await или ContinueWith), чтобы использовать меньше потоков. Дерево рекурсии довольно велико, и вы можете получить очень много потоков, ожидающих семафора. Вы могли бы даже исчерпать пул потоков и тупик.

Вы добавляете элементы в коллекцию (scaryTeddy.Heads.Add) несинхронизированным способом. Это небезопасно.

licensed under cc by-sa 3.0 with attribution.