【问题标题】:Speed up loop using multithreading in C# (Question)在 C# 中使用多线程加速循环(问题)
【发布时间】:2010-09-11 03:31:32
【问题描述】:

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

f.ex:

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

它消耗很多时间,因为CalculateSmth是一个非常耗时的函数。

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

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

也许任何人都可以展示一些例子或文章..

忘了说我在 .NET 2.0 中需要它

【问题讨论】:

  • 您需要按相同顺序返回结果吗?
  • 您可以使用多个后台工作人员吗?创建某种逻辑来计算字符串列表的数量,然后创建 X 数量的 BW 并划分每个

标签: c# multithreading .net-2.0


【解决方案1】:

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

这些允许您编写如下内容:

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

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

【讨论】:

  • 在这种情况下,结果集合中会不会有竞态条件?毕竟多个线程可能同时执行 result.add ...
  • 忘了说我在 .NET 2.0 中需要它
  • 好的,那么你可以看看反射器中的 Parallel.Foreach 源代码......虽然我相信它下面还有一个完整的其他层,所以它不会是一个简单的复制意大利面来获得相似.NET 2.0 中的功能。
  • 并行扩展现在是 .net 4.0 的一部分,因此不再是 CTP。 :)
  • 小问题:Parallel.ForEach 不是 Parallel.Foreach 的情况错误
【解决方案2】:

并不是说我现在这里有什么好文章,但你想做的是一些带有线程池的生产者-消费者。

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

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

需要考虑的事情:

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

【讨论】:

    【解决方案3】:

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

    如果您的函数 CalculateSmth() 基本上是 CPU 密集型的,即 CPU 使用率很高且基本上没有 I/O 使用率,那么我很难看出使用线程的意义,因为线程将相互竞争相同的资源,在本例中为 CPU。

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

    我完全同意对我的回答的评论。我做了一个错误的假设,我们说的是单核 CPU,但现在我们有多核 CPU,我的错。

    【讨论】:

    • 取决于它是否是多核系统。例如,如果您有四个可用的内核,那么使用四个线程应该会看到大约四倍的处理速度(假设线程之间没有相互依赖关系)。
    【解决方案4】:

    并行扩展很酷,但这也可以通过使用线程池来完成,如下所示:

    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);
            }
        }
    }
    

    【讨论】:

    • 你怎么知道它什么时候完成?嗯。
    • 可能有一个WaitCallback函数调用的ManualResetEvent和主线程WaitOne。
    • WaitHandle.WaitAll() 有什么用?我收到 NotSupportedException :“WaitHandles 的数量必须小于或等于 64”
    • 您正在生成超过 64 个线程...这可能不是一个好主意... :) 尝试使用更少的线程。
    【解决方案5】:

    请注意,并发不会神奇地为您提供更多资源。您需要确定是什么导致CalculateSmth 变慢。

    例如,如果它受 CPU 限制(并且您在单核上),那么无论您是顺序执行还是并行执行,代码都会有相同数量的 CPU 滴答声。另外,您会从管理线程中获得一些开销。同样的论点适用于其他约束(例如 I/O)

    只有在CalculateSmth 在其执行期间释放资源(可以由另一个实例使用)时,您才能在此获得性能提升。这并不少见。例如,如果任务涉及 IO,然后是一些 CPU 的东西,那么进程 1 可能正在做 CPU 的东西,而进程 2 正在做 IO。正如 mats 指出的那样,如果您拥有基础设施,一系列生产者 - 消费者单元可以实现这一目标。

    【讨论】:

      【解决方案6】:

      您需要并行拆分您想要完成的工作。以下是如何将工作一分为二的示例:

      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;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-10-25
        • 1970-01-01
        • 1970-01-01
        • 2011-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-20
        相关资源
        最近更新 更多