使用线程池而不是创建线程

  在我们开发程序时,若存在耗性能、高并发处理的任务时,我们会想到用多线程来处理。在多线程处理中,有手工创建线程与线程池2种处理方式,手工创建线程存在管理与维护的繁琐。.Net线程池能够帮我们完成线程资源的管理工作,使用我们专注业务处理,而不是代码的细微实现。在你创建了过多的任务,线程池也能用列队把无法即使处理的请求保存起来,直至有线程释放出来。

  当应用程序开始执行重复的后台任务,且并不需要经常与这些任务交互时,使用.Net线程池管理这些资源将会让性能更佳。我们可以使用ThreadPool.QueueUserWorkItem方法来让线程池为你管理资源,将方法资源排入队列以便执行。 此方法在有线程池线程变得可用时执行。QueueUserWorkItem方法有2中重载,分别为有参数与无参数。如下列出:

  线程池会根据正在运行的任务数量与线程池大小,被加入的任务可能会立即执行,或等待直至有空余的线程再处理。线程池由每个处理器中一定数量的就绪线程和一系列I/O读取线程组成,具体的数字因硬件与.Net版本不同而有差别,在开始向列队中插入执行的任务时,线程池可能会创建更多的线程,也可能等待有可用线程再去执行,这取决于当前内存与其他资源的可用情况。我们不需要详细明白线程池内部的具体实现,因为线程池本身就是为了降低我们的工作,并让框架帮我们分担。简而言之,线程池中的线程数量将在可用线程数据和最小化已分配但尚未使用的资源之间自动平衡。

  线程池同样也会管理线程结束后的维护工作,当任务结束后,线程并不会被销毁,而是返回到可用状态,以便执行其他任务。所有的QueueUserWorkItem使用的线程池中的线程均为后台线程,这就意味着你并不需要在应用程序推出之前手工清理资源,若是应用程序在这些后台线程还在运行时就退出了,那么系统将会停止这些后台任务,并释放所有与应用程序相关的资源。我们只要确保在应用程序退出之前停止了所有非后台线程即可。

  如下给出了3种线程的测试代码,分别为单个线程、手工线程、线程池

/// <summary>
/// 测试单线程、手工线程、线程池
/// </summary>
public static class TestThread
{
    private static uint lowerBound = 0, upperBound = 1000000;
    private const double tolerance = 1.0e-8;//公差

    // 获得数字的平方根
    private static double SquareRoot(double number)
    {
        double guess = 1, error = Math.Abs(guess * guess - number);
        while (error > tolerance)
        {
            guess = (number / guess + guess) / 2;
            error = Math.Abs(guess * guess - number);
        }
        return guess;
    }

    public static double SingleThread()
    {
        Stopwatch start = new Stopwatch();
        start.Start();
        for (uint i = lowerBound; i < upperBound; i++)
        {
            double answer = SquareRoot(i);
        }
        start.Stop();
        return start.ElapsedMilliseconds;
    }

    public static double ManualThreads(int numThreads)
    {
        Stopwatch start = new Stopwatch();
        using (AutoResetEvent e = new AutoResetEvent(false))
        {
            int workerThreads = numThreads;
            start.Start();
            for (int thread = 0; thread < numThreads; thread++)
            {
                Thread t = new Thread(() =>
                {
                    for (uint i = lowerBound; i < upperBound; i++)
                    {
                        //并行计算
                        if (i % numThreads == thread)
                        {
                            double answer = SquareRoot(i);
                        }
                    }
                    //减少计数器的值
                    if (Interlocked.Decrement(ref workerThreads) == 0)
                    {
                        e.Set();//设置事件
                    }
                });
                t.Start();
            }
            //等待信号
            e.WaitOne();
            start.Stop();
            return start.ElapsedMilliseconds;
        }
    }

    public static string content = "线程池输出的内容";
    public static double ThreadPoolThreads(int numThreads)
    {
        Stopwatch start = new Stopwatch();
        //此处使用AutoResetEvent类,用于通知当前线程(等待的线程,即主线程)发生了什么。
        using (AutoResetEvent e = new AutoResetEvent(false))//false标记为非终止状态,即任务未完成
        {
            int workerThreads = numThreads;
            start.Start();
            for (int thread = 0; thread < numThreads; thread++)
            {
                /*WaitCallback,回调委托,代表由系统(程序)自动执行的方法,不需要自己手动去执行。在使用QueueUserWorkItem单个参数方法时,WaitCallback委托中state参数为null;若要使用state参数进行数据处理,需要调用2个参数的QueueUserWorkItem方法。*/
                ThreadPool.QueueUserWorkItem(x =>
                {
                    Console.WriteLine(content);
                    for (uint i = lowerBound; i < upperBound; i++)
                    {
                        //并行计算
                        if (i % numThreads == thread)
                        {
                            double answer = SquareRoot(i);
                        }
                    }
                    //递减计数器的值,Interlocked类是连锁-互锁,为多线程共享的资源在并发时,锁定共享的资源只能同时有一个线程在执行。此处也可以使用lock(关键字)同步锁进行处
                    if (Interlocked.Decrement(ref workerThreads) == 0)
                    {
                        //设置事件状态为终止状态,即任务完成(告诉等待的线程不用等待,可以继续执行了)。
                        e.Set();
                    }
                }, content);
            }
            //等待信号,阻塞当前线程(直到任务完成-即AutoResetEvent设置为终止状态,才继续执行)
            e.WaitOne();
            start.Stop();
            return start.ElapsedMilliseconds;
        }
    }
}

  下面是控制台主方法的代码,及输出的内容。

static void Main(string[] args)
{
    try
    {
        double result = TestThread.SingleThread();
        Console.WriteLine("单个线程执行10回百万个数字的平方根的耗时:{0}ms", result * 10);
        result = TestThread.ManualThreads(10);
        Console.WriteLine("手工线程执行10回百万个数字的平方根的耗时:{0}ms", result);
        result = TestThread.ThreadPoolThreads(10);
        Console.WriteLine("线程池执行10回百万个数字的平方根的耗时:{0}ms", result);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
    Console.Read();
}

  以上输出的耗时结果所使用的CPU型号是Inter(R) Core(TM) i3-3220 CPU @3.30GHz

  从上面的耗时结果"单个线程>手工线程>线程池"可以看出使用线程给算法带来的影响。之所以线程池的实现要优于手工创建线程,主要有2个因素。

  1. 线程池将重用那些被释放了的线程,而手工创建线程时,必须为每个任务创建一个全新的线程,线程的创建与销毁所花费的时间要高于.Net线程池管理所带来的开销。
  2. 线程池将为你管理活动线程的数量,若创建了过多的线程,那么系统将挂起一部分,直到有足够的资源执行,QueueUserWorkItem则将工作交给线程池中接下来的一个可用线程,并帮你完成一定的线程管理工作。若应用程序的线程池中所有的线程均被占用,那么线程池也会挂起任务,直至出现可用线程。

  我们在开发.Net服务端应用程序时,例如WCF、ASP.Net、.Net远程处理等,都会或多或少的要用到多线程,这些.Net子系统均使用了线程池来管理线程,因此我们也应该采用这种做法。线程池能够降低额外开销,进而提高性能。

时间: 2024-10-25 01:17:37

使用线程池而不是创建线程的相关文章

Android性能优化之线程池策略和对线程池的了解

线程的运行机制 1. 开启线程过多,会消耗cpu 2. 单核cpu,同一时刻只能处理一个线程,多核cpu同一时刻可以处理多个线程 3. 操作系统为每个运行线程安排一定的CPU时间----`时间片`,系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因为时间相当短,多个线程频繁地发生切换,因此给用户的感觉就是好像多个线程同时运行一样,但是如果计算机有多个CPU,线程就能真正意义上的同时运行了. 线程池的作用 1. 线程池是预先创建线程的一种技术.线程池在还没有任务到来之前,创建一定数

线程池;java实现线程池原理

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中.如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙.如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值.超过最大值的线程可以排队,但他们要等到其他线程完成后才启动. 组成部分 1.线程池

线程池;java的线程池的实现原理;适用于频繁互动(如电商网站)

线程池是一种多线程处理形式,处理过程中将任务加入到队列,然后在创建线程后自己主动启动这些任务.线程池线程都是后台线程.每一个线程都使用默认的堆栈大小,以默认的优先级执行.并处于多线程单元中. 假设某个线程在托管代码中空暇(如正在等待某个事件),则线程池将插入还有一个辅助线程来使全部处理器保持繁忙. 假设全部线程池线程都始终保持繁忙,但队列中包括挂起的工作,则线程池将在一段时间后创建还有一个辅助线程但线程的数目永远不会超过最大值.超过最大值的线程能够排队,但他们要等到其它线程完毕后才启动. 组成部

【转】线程池体系介绍及从阿里Java开发手册学习线程池的正确创建方法

jdk1.7中java.util.concurrent.Executor线程池体系介绍 java.util.concurrent.Executor : 负责线程的使用与调度的根接口  |–ExecutorService:Executor的子接口,线程池的主要接口  |–ThreadPoolExecutor:ExecutorService的实现类  |–ScheduledExecutorService:ExecutorService的子接口,负责线程的调度  |–ScheduledThreadPo

线程池原理及创建并C++实现

本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关.另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量.文章的最后,我们给出一个简单示例程序,通过该示例程序,我们会发现,通过该线程池框架执行多线程任务是多么的简单. 为什么需要线程池 目前的大多数网络服务器,包括Web服务器.Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短. 传统多线程方案中我们采用的

线程池原理及创建(C++实现)

http://www.cnblogs.com/lidabo/p/3328402.html 本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关.另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量.文章的最后,我们给出一个简单示例程序,通过该示例程序,我们会发现,通过该线程池框架执行多线程任务是多么的简单. 为什么需要线程池 目前的大多数网络服务器,包括Web服务器.Email服务器以及数据库服务器等都具有一个共同点,就

从阿里Java开发手册学习线程池的正确创建方法

前言 最近看阿里的 Java开发手册,上面有线程池的一个建议: [强制]线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险. 结合最近面试的经历,发现这条建议还是十分有用的,因为自己经常使用Executors提供的工厂方法创建线程池,所以忽略了线程池内部的实现.特别是拒绝策略,面试被问到两次,因为使用Executors创建线程池不会传入这个参数而使用默认值所以我们常常忽略这一参

线程池的优点及线程池的创建方式

什么是线程池 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池. 在开发过程中,合理地使用线程池能够带来3个好处.第一:降低资源消耗.通过重复利用机制已降低线程创建和销毁造成的消耗.第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行.第三:提高线程的可管理性.线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配.调优和监控.但是,要做到合理利用线程池,必须对其实现原理了如指掌.

线程池异常处理之重启线程处理任务

线程池异常处理之重启线程处理任务 本文记录一下在使用线程池过程中,如何处理 while(true)循环长期运行的任务,在业务处理逻辑中,如果抛出了运行时异常时怎样重新提交任务. 这种情形在Kafka消费者中遇到,当为每个Consumer开启一个线程时, 在线程的run方法中会有while(true)循环中消费Topic数据. 本文会借助Google Guava包中的com.google.common.util.concurrent.ThreadFactoryBuilder类创建线程工厂,因为它能