多线程总结之旅(11):线程池的使用

  为何使用线程池?  程序员

  在面向对象编程中,建立和销毁对象是很费时间的,由于建立一个对象要获取内存资源或者其它更多资源,因此提升服务程序效率的一个手段就是尽量减小建立和销毁对象的次数,特别是一些很耗资源的对象建立和销毁。如何利用已有对象来服务就是一个须要解决的关键问题,其实这就是一些"池化资源"技术产生的缘由。好比你们所熟悉的数据库链接池正是遵循这一思想而产生的,本文将介绍的线程池技术一样符合这一思想。算法


  多线程是什么?组成?特色?数据库

  线程池是一种多线程处理形式,处理过程当中将任务添加到队列,而后在建立线程后自动启动这些任务。线程池中的线程由系统管理,程序员不须要费力于线程管理,能够集中精力处理应用程序任务。编程

   组成:服务器程序利用线程技术响应客户请求已经司空见惯,可能您认为这样作效率已经很高,但您有没有想过优化一下使用线程的方法。该文章将向您介绍服务器程序如何利用线程池来优化性能并提供一个简单的线程池实现。
  一、线程池管理器(ThreadPoolManager):用于建立并管理线程池
  二、工做线程(WorkThread): 线程池中线程
  三、任务接口(Task):每一个任务必须实现的接口,以供工做线程调度任务的执行。
  四、任务队列:用于存放没有处理的任务。提供一种缓冲机制。
 

  特色:安全

  • 一个进程有且只有一个线程池。
  • 线程池线程都是后台线程(即不会阻止进程的中止)
  • 每一个线程都使用默认堆栈大小,以默认的优先级运行,并处于多线程单元中。超过最大值的其余线程须要排队,但它们要等到其余线程完成后才启动。
  • 在CLR 2.0 SP1以前的版本中,线程池中 默认最大的线程数量 = 处理器数 * 25, CLR 2.0 SP1以后就变成了 默认最大线程数量 = 处理器数 * 250,线程上限能够改变,经过使用ThreadPool.GetMax+Threads和ThreadPool.SetMaxThreads方法,能够获取和设置线程池的最大线程数。
  • 默认状况下,每一个处理器维持一个空闲线程,即默认最小线程数 = 处理器数。
  • 当进程启动时,线程池并不会自动建立。当第一次将回调方法排入队列(好比调用ThreadPool.QueueUserWorkItem方法)时才会建立线程池。
  • 在对一个工做项进行排队以后将没法取消它。
  • 线程池中线程在完成任务后并不会自动销毁,它会以挂起的状态返回线程池,若是应用程序再次向线程池发出请求,那么这个挂起的线程将激活并执行任务,而不会建立新线程,这将节约了不少开销。只有线程达到最大线程数量,系统才会以必定的算法销毁回收线程。

  何时使用线程池?  服务器

  一、须要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是很是合适的。由于单个任务小,而任务数量巨大,你能够想象一个热门网站的点击次数。 但对于长时间的任务,好比一个Telnet链接请求,线程池的优势就不明显了。由于Telnet会话时间比线程的建立时间大多了。
  二、对性能要求苛刻的应用,好比要求服务器迅速响应客户请求。
  三、接受突发性的大量请求,但不至于使服务器所以产生大量线程的应用。突发性大量客户请求,在没有线程池状况下,将产生大量线程,虽然理论上大部分操做系统线程数目最大值不是问题,短期内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。
    
   何时不适合使用线程池?
  
  ●若是须要使一个任务具备特定优先级
  ●若是具备可能会长时间运行(并所以阻塞其余任务)的任务
  ●若是须要将线程放置到 单线程单元中(线程池中的线程均处于多线程单元中)
  ●若是须要永久标识来标识和控制线程,好比想使用专用线程来终止该线程,将其挂起或按名称发现它。

   线程池经常使用方法介绍?
  

  一、GetMaxThreads()    :  获取能够同时处于活动状态的线程池请求的最大数目。全部大于此数目的请求将保持排队状态,直到线程池线程变为可用。多线程

    •   函数原型:public static void GetMaxThreads (out int workerThreads,out int completionPortThreads)

                  参数1:workerThreads :线程池中辅助线程的最大数目。 
                  参数2:completionPortThreads :线程池中异步 I/O 线程的最大数目。 异步

 

  二、GetMinThreads()    获取线程池维护的最小空闲线程数。        函数

    • 函数原型:public static void GetMinThreads (out int workerThreads,out int completionPortThreads)
      参数1:workerThreads:当前由线程池维护的空闲辅助线程的最小数目。 
      参数2:completionPortThreads:当前由线程池维护的空闲异步 I/O 线程的最小数目。 

 

  三、SetMaxThreads()    设置能够同时处于活动状态的线程池的最大请求数目(不考虑计算机处理器的数目)性能

    • 函数原型:public static bool SetMinThreads (int workerThreads,intcompletionPortThreads)

               参数1:workerThreads::要由线程池维护的新的最小空闲辅助线程数。 
               参数2:completionPortThreads::要由线程池维护的新的最小空闲异步 I/O 线程数。 
               返回值:若是更改为功,则为 true;不然为 false。 

 

  四、SetMinThreads()     设置线程池在新请求预测中维护的空闲线程数(不考虑计算机处理器的数目)

    • 函数原型:public static bool SetMinThreads (int workerThreads,intcompletionPortThreads)

               参数1:workerThreads:要由线程池维护的新的最小空闲辅助线程数。 
               参数2:completionPortThreads:要由线程池维护的新的最小空闲异步 I/O 线程数。 
               返回值:若是更改为功,则为 true;不然为 false。 

  

  五、GetAvailableThreads()    获取由 GetMaxThreads 返回的线程池线程的最大数目和当前活动数目之间的差值。    

    • 函数原型:public static void GetAvailableThreads (out int workerThreads,out int completionPortThreads)

                参数1:workerThreads:可用辅助线程的数目。 
                参数2:completionPortThreads:可用异步 I/O 线程的数目。 

 

  六、QueueUserWorkItem()    将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。 

    • 重载方法1:public static bool QueueUserWorkItem (WaitCallback callBack)

                返回值:若是将方法成功排入队列,则为 true;不然为 false。 

    • 重载方法2:public static bool QueueUserWorkItem (WaitCallback callBack,Object state)

               参数2:state :包含方法所用数据的对象。 
               返回值:若是将方法成功排入队列,则为 true;不然为 false。 
      备注:WaitCallback 回调方法必须与System.Threading.WaitCallback委托类型相匹配。

         WaitCallback函数原型:public delegate void WaitCallback(Object state);调用QueueUserWorkItem能够经过Object来向任务过程传递参数。若是任务过程须要多个参数,能够定义包含这些数据的类,并将类的实例强制转换为Object数据类型。

 

  七、UnsafeQueueUserWorkItem()    非安全性注册一个等待 WaitHandle 的委托(将方法排入队列以便执行)。

    • 函数原型:public static bool UnsafeQueueUserWorkItem (WaitCallback callBack,Object state)      //不将调用堆栈传播到辅助线程上。这容许代码失去调用堆栈,从而提高了它的安全特权。
    • 备注:使用 UnsafeQueueUserWorkItem 可能会无心中打开一个安全漏洞。代码访问安全性的权限检查基于全部调用方对堆栈的权限进行。若是使用 UnsafeQueueUserWorkItem 将工做排在某个线程池线程上,则该线程池线程的堆栈将不会具备实际调用方的背景。恶意代码可能会利用这一点避开权限检查。

 

  八、RegisterWaitForSingleObject()     将指定的委托排队到线程池。当发生如下状况之一时,辅助线程将执行委托。

  九、UnsafeRegisterWaitForSingleObject()   非安全性将指定的委托排队到线程池。


  线程池示例?

  咱们先来看一个简单的线程实例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Begin in Main");
            Thread t = new Thread(ThreadInvoke);
            t.IsBackground = true;
            t.Start();

            //将当前线程挂起200毫秒
            Thread.Sleep(200);
            Console.WriteLine("End in Main");
            Console.ReadKey();
        }

        static void ThreadInvoke(object obj)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Execute in ThreadInvoke");
                //每隔100毫秒,循环一次
                Thread.Sleep(100);
            }
        }
    }
}

 

"End in Main"并无在ThreadInvoke()方法中全部代码执行完以后才输出。

因而可知Main方法和ThreadInvoke是并行执行的

 

  使用线程池改造上边的示例:

    上面介绍了只是一个最简单的有关线程线程的例子,但在实际开发中使用的线程每每是大量的和更为复杂的,这时,每次都建立线程、启动线程。从性能上来说,这样作并不理想(由于每使用一个线程就要建立一个,须要占用系统开销);从操做上来说,每次都要启动,比较麻烦。为此引入的线程池的概念。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Begin in Main");
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadInvoke));
            Console.WriteLine("End in Main");
            //将当前线程挂起200毫秒
            Thread.Sleep(3000);
           
            Console.ReadKey();
        }

        static void ThreadInvoke(object obj)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Execute in ThreadInvoke");
                //每隔100毫秒,循环一次
                Thread.Sleep(100);
            }
        }
    }
}

 

Thread.Sleep(3000)这句话是必须的由于当Main方法结束后,.Net环境会自动结束销毁线程池,为了保证完成线程池里的任务,因此主线程须要等待一段时间。 

由输出结果可知,Main方法和ThreadInvoke方法是并行执行的。

相关文章
相关标签/搜索