线程最佳实现



我有一个应用程序,我有很多"搜索"同时运行(搜索需要1-10秒才能完成,具体取决于可用的结果数量)问题是搜索时的延迟越来越大(我认为是因为最多有25个线程)我使用的是Backgroundworker class Atm。所以我查阅了其他一些实现:

简单示例:

    static void Main()
{
    for (int i = 0; i < 500; i++)
    {
        try
        {
            new Thread(new ParameterizedThreadStart(doWork)).Start(i);
        }
        catch { }
    }
    Console.ReadLine();
}
static void doWork(object i)
{
    Console.WriteLine(i + ": started");
    Thread.Sleep(1000);
    Console.WriteLine(i + " done");
    Thread.CurrentThread.Abort();
}

但我会遇到异常,我会中止线程(这让我很担心)所以我尝试了线程池:

static void Main()
{
    for (int i = 0; i < 500; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(doWork), i);
    }
    Console.ReadLine();
}
static void doWork(object i)
{
    Console.WriteLine(i + ": started");
    Thread.Sleep(1000);
    Console.WriteLine(i + " done");
}

但进展非常缓慢。。。

我仍在寻找最好的实现,有人能帮我吗?

编辑:DoWork方法建立网络连接(并等待其完成)这是使用API,因此我无法执行异步

如果搜索是CPU绑定的,我将使用Parallel.For或并行linq,并手动指定MaxDegreeOfParallelism。在这种情况下,虚拟核心的数量通常是线程的最佳数量。

如果搜索正在等待外部的东西(例如IO绑定,等待网络上的响应,…),我会考虑非阻塞API,所以每次搜索不需要一个线程。

任何涉及排队500个项目以供ThreadPool处理的事情都不会以最佳吞吐量运行。ThreadPool通常不愿意增加额外的线程,因为这种用法不是设计者想要的。

在我看来,这听起来像是绑定了IO,在这种情况下,您可以异步执行IO,并用很少的线程为所有内容提供服务。然而,在不了解更多工作量的情况下,这有点像一场猜谜游戏。

尝试此解决方案:

static void Main(string[] args)
{
    for (int i = 0; i < 100; i++)
    {
        Task t = new Task(doWork, i);
        t.Start();
    }
    Console.ReadLine();
}
static void doWork(object i)
{
    Console.WriteLine(i + ": started");
    Thread.SpinWait(20000000); // It depends on what doWork actually does whether SpinWait or Sleep is the most appropriate test
    //Thread.Sleep(1000);
    Console.WriteLine(i + " done");
}

有了任务,你就有了更好的方法来控制你的工作项目,并用可以提高性能的选项来增加这些项目。任务的默认TaskScheduler使用ThreadPool对工作项进行排队。(阅读此答案的底部,了解有关任务的更多信息。)

因此,为了回答这个问题,我们需要知道doWork实际上做了什么:-)但总的来说,Task将是一个很好的选择和很好的抽象。

并行foreach

如果你使用循环来生成作业,并且你正在进行数据并行,那么并行foreach就可以完成这项工作:

Parallel.For(0, 500, i => doWork(i));

链接:

  • http://msdn.microsoft.com/en-us/library/dd537609.aspx
  • http://msdn.microsoft.com/en-us/library/dd460717.aspx

致消费者的评论

http://msdn.microsoft.com/en-us/library/dd537609.aspx

任务提供两个主要好处:

1) 更高效、更可扩展地使用系统资源。

在幕后,任务被排队到ThreadPool通过算法(如爬山)进行增强根据最大化吞吐量的线程数进行调整。这使得任务相对较轻,您可以创建许多任务实现细粒度并行。为了补充这一点,众所周知工作窃取算法被用来提供负载平衡。

2) 与线程或工作项相比,可以进行更多的编程控制。

任务和围绕它们构建的框架提供了一组丰富的API支持等待、取消、继续、健壮异常处理、详细状态、自定义日程安排等。

更新的答案

不幸的是,这是一个糟糕的API,因为它不允许您异步执行。它可能会变慢,因为你同时启动了太多连接(或者启动的连接太少)。

试试这个:

var jobs = new[] { 1, 2, 3};
var options = new ParallelOptions { MaxDegreeOfParallelism = 3 };
Parallel.ForEach(jobs, options, i => doWork(i));

并对CCD_ 8值进行了实验。

线程池试图最大限度地减少创建的线程数量,而不是为排队的任务创建新线程,它可以等待池中的其他线程被释放。它这样做是有原因的——太多的线程可能会影响性能。但是,您可以覆盖在此限制发生之前要创建的最小线程数。

以下是您的原始代码,并进行了更正以使其快速工作:

static void Main()
{
    int minWorker, minIOC;
    ThreadPool.GetMinThreads(out minWorker, out minIOC);
    ThreadPool.SetMinThreads(50, minIOC);
    for (int i = 0; i < 500; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(doWork), i);
    }
    Console.ReadLine();
}
static void doWork(object i)
{
    Console.WriteLine(i + ": started");
    Thread.Sleep(1000);
    Console.WriteLine(i + " done");
}

不要使用Thread.Abort。这是对一个只想优雅结束的糟糕工作威胁的一个相当暴力的结束。你可以让原始的doWork方法结束,线程就会被释放。

关于您的第二个解决方案,您将在一秒钟内排队500个线程,这远远超过ThreadPool可以并发运行的数量。这需要更多的时间,因为他们一个接一个地跑。

.NET 4.0中的另一个选项是System.Threading.Tasks中的任务库,这是您可能会考虑的一个更智能的基于线程池的解决方案。

但回到你最初的问题——当使用BackgroundWorkers时,你所说的"延迟变得更长"到底是什么意思?你的意思是,当有多个搜索正在进行时,每个单独的搜索需要更长的时间(接近10秒)吗?如果是这样的话,我会尝试在其他地方寻找瓶颈。什么是"搜索"?您正在访问数据库吗?网络连接?也许您在应用程序的那个部分中有导致瓶颈的锁定。

1)如果您调用中止,则会得到ThreadAbortException:Thread.Artrt

2) ?500个螺纹?我认为你做的事情很糟糕。(除非您使用GPU)

最新更新