用条件锁控制并行性?

时间:2013-12-14 06:43:40

标签: .net multithreading c#-4.0 recursion locking

enter image description here

总而言之,我有一个递归任务,我想使用我的所有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;
        }
    }

修改:结果

enter image description here

所有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

enter image description here

我们有4个线程正在工作,这是我想要的,其余的池正在等待,恕我直言不是太多。

1 个答案:

答案 0 :(得分:1)

outOfControl替换new SemaphoreSlim(4)限制机制。这是内置于框架中的。 (此外,您正在阅读_scaryTeddyFactoryCount而没有锁定,这是不安全的。但是当您使用信号量时,这会消失。)

制作静态变量实例变量。目前,他们的价值观在所有实例之间危险地分享。

您可能希望切换到异步等待方式(例如,使用async/awaitContinueWith)以使用更少的线程。你的递归树非常大,你最终可能会有很多线程在等待信号量。你甚至可能耗尽线程池和死锁。

您正在以不同步的方式向集合(scaryTeddy.Heads.Add)添加项目。那是不安全的。