代码之家  ›  专栏  ›  技术社区  ›  Lukas Å alkauskas

C#中使用多线程加速循环(问题)

  •  15
  • Lukas Å alkauskas  · 技术社区  · 17 年前

    想象一下,我有一个函数,它遍历一百万/十亿个字符串并检查其中的smth。

    例如:

    foreach (String item in ListOfStrings)
    {
        result.add(CalculateSmth(item));
    }
    

    它消耗了大量时间,因为Calculatemth是一个非常耗时的函数。

    我想问:如何在这种过程中集成多线程?

    f.ex:我想启动5个线程,每个线程都返回一些结果,然后一直持续到列表中有项目为止。

    也许任何人都可以举一些例子或文章。。

    忘了说我需要它。NET 2.0

    6 回复  |  直到 17 年前
        1
  •  17
  •   Tobi    16 年前

    你可以试试 Parallel extensions (.NET 4.0的一部分)

    这些允许您编写类似以下内容:

    Parallel.Foreach (ListOfStrings, (item) => 
        result.add(CalculateSmth(item));
    );
    

    当然result.add需要是线程安全的。

        2
  •  18
  •   noocyte    17 年前

    并行扩展很酷,但这也可以通过使用这样的线程池来实现:

    using System.Collections.Generic;
    using System.Threading;
    
    namespace noocyte.Threading
    {
        class CalcState
        {
            public CalcState(ManualResetEvent reset, string input) {
                Reset = reset;
                Input = input;
            }
            public ManualResetEvent Reset { get; private set; }
            public string Input { get; set; }
        }
    
        class CalculateMT
        {
            List<string> result = new List<string>();
            List<ManualResetEvent> events = new List<ManualResetEvent>();
    
            private void Calc() {
                List<string> aList = new List<string>();
                aList.Add("test");
    
                foreach (var item in aList)
                {
                    CalcState cs = new CalcState(new ManualResetEvent(false), item);
                    events.Add(cs.Reset);
                    ThreadPool.QueueUserWorkItem(new WaitCallback(Calculate), cs);
                }
                WaitHandle.WaitAll(events.ToArray());
            }
    
            private void Calculate(object s)
            {
                CalcState cs = s as CalcState;
                cs.Reset.Set();
                result.Add(cs.Input);
            }
        }
    }
    
        3
  •  12
  •   slim    17 年前

    请注意,并发并不能神奇地为您提供更多资源。您需要确定是什么减缓了CalculateSth的速度。

    例如,如果它是CPU受限的(并且你在一个内核上),那么无论你是顺序执行还是并行执行,代码都会收到相同数量的CPU滴答声。此外,管理线程会带来一些开销。同样的论点也适用于其他约束(例如I/O)

    只有当CalculateSmth在执行过程中释放出可供其他实例使用的可用资源时,您才能获得性能提升。这并不罕见。例如,如果任务涉及IO和一些CPU工作,那么进程1可能正在做CPU工作,而进程2正在做IO。正如mats指出的那样,如果你有基础设施,一系列生产者-消费者单元可以实现这一点。

        4
  •  5
  •   Hallgrim    17 年前

    你需要把你想同时做的工作分开。以下是一个如何将工作一分为二的示例:

    List<string> work = (some list with lots of strings)
    
    // Split the work in two
    List<string> odd = new List<string>();
    List<string> even = new List<string>();
    for (int i = 0; i < work.Count; i++)
    {
        if (i % 2 == 0)
        {
            even.Add(work[i]);
        }
        else
        {
            odd.Add(work[i]);
        }
    }
    
    // Set up to worker delegates
    List<Foo> oddResult = new List<Foo>();
    Action oddWork = delegate { foreach (string item in odd) oddResult.Add(CalculateSmth(item)); };
    
    List<Foo> evenResult = new List<Foo>();
    Action evenWork = delegate { foreach (string item in even) evenResult.Add(CalculateSmth(item)); };
    
    // Run two delegates asynchronously
    IAsyncResult evenHandle = evenWork.BeginInvoke(null, null);
    IAsyncResult oddHandle = oddWork.BeginInvoke(null, null);
    
    // Wait for both to finish
    evenWork.EndInvoke(evenHandle);
    oddWork.EndInvoke(oddHandle);
    
    // Merge the results from the two jobs
    List<Foo> allResults = new List<Foo>();
    allResults.AddRange(oddResult);
    allResults.AddRange(evenResult);
    
    return allResults;
    
        5
  •  2
  •   Mats Wiklander    17 年前

    您必须回答的第一个问题是是否应该使用线程

    如果你的函数CalculateSmth()基本上是CPU受限的,即CPU使用率很高,基本上没有i/O使用率,那么我很难理解使用线程的意义,因为线程将争夺相同的资源,在这种情况下是CPU。

    如果你的Calculatemth()同时使用CPU和I/O,那么它可能是使用线程的一个点。

    我完全同意对我的回答的评论。我错误地假设我们谈论的是单核CPU,但现在我们有多核CPU,这是我的错。

        6
  •  1
  •   Mats Fredriksson    17 年前

    我现在并没有什么好文章,但你想做的是将生产者-消费者与线程池结合起来。

    生产者循环并创建任务(在这种情况下,可能只是在列表或堆栈中排队)。例如,消费者是五个线程,从堆栈中读取一个项目,通过计算来消费它,然后将其存储在其他位置。

    这样,多线程仅限于这五个线程,它们都有工作要做,直到堆栈为空。

    需要考虑的事情:

    • 对输入和输出列表进行保护,例如互斥体。
    • 如果顺序很重要,请确保保持输出顺序。一个例子是将它们存储在SortedList或类似的东西中。
    • 确保CalculateSmth是线程安全的,它不使用任何全局状态。