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

时间:2008-09-19 07:39:01

标签: c# multithreading .net-2.0

想象一下,我有一个功能,它通过一百万个/十亿字符串并检查它们中的smth。

f.ex:

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

它消耗了大量的时间,因为CalculateSmth是非常耗时的功能。

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

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

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

忘记提及我在.NET 2.0中需要它

6 个答案:

答案 0 :(得分:18)

Parallel扩展很酷,但这也可以通过使用这样的线程池来完成:

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

答案 1 :(得分:17)

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

这些允许你写一些类似的东西:

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

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

答案 2 :(得分:12)

请注意,并发性并不能为您提供更多资源。你需要确定什么减慢了CalculateSmth。

例如,如果它受CPU限制(并且您在单个核心上),则无论是顺序执行还是并行执行,都会将相同数量的CPU标记输入到代码中。此外,您还可以从管理线程中获得一些开销。相同的参数适用于其他约束(例如I / O)

如果CalculateSmth在执行过程中保留资源,那么您只能获得性能提升,这可以被另一个实例使用。这并不罕见。例如,如果任务涉及IO后跟一些CPU内容,则进程1可能正在执行CPU内容,而进程2正在执行IO。正如垫子所指出的那样,如果你拥有基础设施,一系列生产者 - 消费者单位就可以实现这一目标。

答案 3 :(得分:5)

您需要并行拆分要执行的工作。以下是如何将工作分成两部分的示例:

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;

答案 4 :(得分:2)

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

如果您的函数CalculateSmth()基本上是CPU限制的,即CPU使用率很高且基本上没有I / O使用,那么我很难看到使用线程的重点,因为线程将竞争相同的资源,在这种情况下是CPU。

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

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

答案 5 :(得分:1)

不是说我现在在这里有任何好文章,但你想要做的是使用Threadpool的Producer-Consumer。

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

这样多线程只限于那五个线程,并且在堆栈为空之前它们都有工作要做。

要考虑的事情:

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