总而言之,我有一个递归任务,我想使用我的所有4个处理器来更快地处理这个非常大的树。我当前的生产实现使用Parallel.ForEach
并且失控,将我的所有4个cpus挂起并快速耗尽内存。所以,我知道正确的算法可以给我所有4 cpus 70-80%,我发现这将快速完成抓取工作,同时保持UI响应和我的计算机整体响应轻型UI用户驱动的任务。此任务是后台任务。
我正在尝试的方法(下面列出)是并行和递归的,我想使用条件锁来限制线程。
我希望此代码最多使用4个线程来保持递归创建20个可怕的头,直到所有分支中的嵌套深度达到10。我将它从2个改为20个,因为这更像是我的实际问题。我的实际树只有4-5级深,但很宽,每个节点需要比Console.WriteLine
更多的cpu。
这并不像我想象的那样容易实现。
我正在尝试使所有线程大于4等待,直到完成之前有足够的线程将总线程返回到4继续之前。所以如果创建了超过4个线程,只要那些>就可以了。 #4正在等待。因此有条件的等待(锁定)部分。
我的代码示例仅用于概念目的,正是我尝试过的。请随意偏离我的实施细节。
编辑:昨晚我改变了我的实施,使用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's 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;
}
}
修改:结果
所有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
我们有4个线程正在工作,这是我想要的,其余的池正在等待,恕我直言不是太多。
答案 0 :(得分:1)
用outOfControl
替换new SemaphoreSlim(4)
限制机制。这是内置于框架中的。 (此外,您正在阅读_scaryTeddyFactoryCount
而没有锁定,这是不安全的。但是当您使用信号量时,这会消失。)
制作静态变量实例变量。目前,他们的价值观在所有实例之间危险地分享。
您可能希望切换到异步等待方式(例如,使用async/await
或ContinueWith
)以使用更少的线程。你的递归树非常大,你最终可能会有很多线程在等待信号量。你甚至可能耗尽线程池和死锁。
您正在以不同步的方式向集合(scaryTeddy.Heads.Add
)添加项目。那是不安全的。