线程池之ThreadPool类与辅助线程 - <第二篇>

一、CLR线程池

  管理线程开销最好的方式:

  1. 尽量少的创建线程并且能将线程反复利用(线程池初始化时没有线程,有程序请求线程则创建线程);
  2. 最好不要销毁而是挂起线程达到避免性能损失(线程池创建的线程完成任务后以挂起状态回到线程池中,等待下次请求);
  3. 通过一个技术达到让应用程序一个个执行工作,类似于一个队列(多个应用程序请求线程池,线程池会将各个应用程序排队处理);
  4. 如果某一线程长时间挂起而不工作的话,需要彻底销毁并且释放资源(线程池自动监控长时间不工作的线程,自动销毁);
  5. 如果线程不够用的话能够创建线程,并且用户可以自己定制最大线程创建的数量(当队列过长,线程池里的线程不够用时,线程池不会坐视不理);

  微软早就替我们想到了,为我们实现了线程池。

  CLR线程池并不会在CLR初始化时立即建立线程,而是在应用程序要创建线程来运行任务时,线程池才初始化一个线程。

  线程池初始化时是没有线程的,线程池里的。线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。

  这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。

通过CLR线程池所建立的线程总是默认为后台线程,优先级数为ThreadPriority.Normal

二、工作者线程与I/O线程

  CLR线程池分为工作者线程(workerThreads)与I/O线程(completionPortThreads)两种:

  • 工作者线程是主要用作管理CLR内部对象的运作,通常用于计算密集的任务。
  • I/O(Input/Output)线程主要用于与外部系统交互信息,如输入输出,CPU仅需在任务开始的时候,将任务的参数传递给设备,然后启动硬件设备即可。等任务完成的时候,CPU收到一个通知,一般来说是一个硬件的中断信号,此时CPU继续后继的处理工作。在处理过程中,CPU是不必完全参与处理过程的,如果正在运行的线程不交出CPU的控制权,那么线程也只能处于等待状态,即使操作系统将当前的CPU调度给其他线程,此时线程所占用的空间还是被占用,而并没有CPU处理这个线程,可能出现线程资源浪费的问题。如果这是一个网络服务程序,每一个网络连接都使用一个线程管理,可能出现大量线程都在等待网络通信,随着网络连接的不断增加,处于等待状态的线程将会很消耗尽所有的内存资源。可以考虑使用线程池解决这个问题。

  线程池的最大值一般默认为1000、2000。当大于此数目的请求时,将保持排队状态,直到线程池里有线程可用。

  使用CLR线程池的工作者线程一般有两种方式:

  • 通过ThreadPool.QueueUserWorkItem()方法;
  • 通过委托;

  要注意,不论是通过ThreadPool.QueueUserWorkItem()还是委托,调用的都是线程池里的线程。

三、ThreadPool类常用方法

  通过以下两个方法可以读取和设置CLR线程池中工作者线程与I/O线程的最大线程数。

  1. ThreadPool.GetMax(out in workerThreads,out int completionPortThreads);
  2. ThreadPool.SetMax(int workerThreads,int completionPortThreads);

  若想测试线程池中有多少线程正在投入使用,可以通过ThreadPool.GetAvailableThreads(out in workThreads,out int conoletionPortThreads)方法。

方法 说明
GetAvailableThreads 剩余空闲线程数
GetMaxThreads 最多可用线程数,所有大于此数目的请求将保持排队状态,直到线程池线程变为可用
GetMinThreads 检索线程池在新请求预测中维护的空闲线程数。
QueueUserWorkItem 启动线程池里得一个线程(队列的方式,如线程池暂时没空闲线程,则进入队列排队)
SetMaxThreads 设置线程池中的最大线程数
SetMinThreads 设置线程池最少需要保留的线程数

    class Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            int j = 0;
            //前面是辅助(也就是所谓的工作者)线程,后面是I/O线程
            ThreadPool.GetMaxThreads(out i, out j);
            Console.WriteLine(i.ToString() + "   " + j.ToString()); //默认都是1000

            //获取空闲线程,由于现在没有使用异步线程,所以为空
            ThreadPool.GetAvailableThreads(out i, out j);
            Console.WriteLine(i.ToString() + "   " + j.ToString()); //默认都是1000

            Console.ReadKey();
        }
    }

四、各种调用线程池线程的方法

  1、通过QueueUserWorkItem启动工作者线程

  ThreadPool线程池中有两个重载的静态方法可以直接启动工作者线程:

  •   ThreadPool.QueueUserWorkItem(waitCallback);
  •   ThreadPool.QueueUserWorkItem(waitCallback,Object);

  先把WaitCallback委托指向一个带有Object参数的无返回值方法,再使用ThreadPool.QueueUserWorkItem(WaitCallback)就可以一步启动此方法,此时异步方法的参数被视为null。

  下面来试下用QueueUserWorkItem启动线程池里的一个线程。注意哦,由于是一直存在于线程池,所以不用new Thread()。

    class Program
    {
        static void Main(string[] args)
        {
            //工作者线程最大数目,I/O线程的最大数目
            ThreadPool.SetMaxThreads(1000, 1000);
            //启动工作者线程
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunWorkerThread));

            Console.ReadKey();
        }

        static void RunWorkerThread(object state)
        {
            Console.WriteLine("RunWorkerThread开始工作");
            Console.WriteLine("工作者线程启动成功!");
        }
    }

  输出:

  

  使用第二个重载方法ThreadPool.QueueUserWorkItem(WaitCallback,object)方法可以把object对象作为参数传送到回调函数中。

    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person(1,"刘备");
            //启动工作者线程
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunWorkerThread), p);
            Console.ReadKey();
        }

        static void RunWorkerThread(object obj)
        {
            Thread.Sleep(200);
            Console.WriteLine("线程池线程开始!");
            Person p = obj as Person;
            Console.WriteLine(p.Name);
        }
    }

    public class Person
    {
        public Person(int id,string name) { Id = id; Name = name; }
        public int Id { get; set; }
        public string Name { get; set; }
    }

  输出结果如下:

  

  通过ThreadPool.QueueUserWork启动工作者线程非常方便,但是WaitCallback委托指向的必须是一个带有object参数的无返回值方法。所以这个方法启动的工作者线程仅仅适合于带单个参数和无返回值的情况。

  那么如果要传递多个参数和要有返回值又应该怎么办呢?那就只有通过委托了。

  2、BeginInvoke与EndInvoke委托异步调用线程

  异步调用委托的步骤如下:

  1. 建立一个委托对象,通过IAsyncResult BeginInvoke(string name,AsyncCallback callback,object state)异步调用委托方法,BeginInvoke方法除最后的两个参数外,其他参数都是与方法参数相对应的。
  2. 利用EndInvoke(IAsyncResult--上一步BeginInvoke返回的对象)方法就可以结束异步操作,获取委托的运行结果。

    class Program
    {
        //除了最后两个参数,前面的都是你可定义的
        delegate string MyDelegate(string name,int age);
        static void Main(string[] args)
        {
            //建立委托
            MyDelegate myDelegate = new MyDelegate(GetString);
            //异步调用委托,除最后两个参数外,前面的参数都可以传进去
            IAsyncResult result = myDelegate.BeginInvoke("刘备",22, null, null);  //IAsynResult还能轮询判断,功能不弱

            Console.WriteLine("主线程继续工作!");

            //调用EndInvoke(IAsyncResult)获取运行结果,一旦调用了EndInvoke,即使结果还没来得及返回,主线程也阻塞等待了
            //注意获取返回值的方式
            string data = myDelegate.EndInvoke(result);
            Console.WriteLine(data);

            Console.ReadKey();
        }

        static string GetString(string name, int age)
        {
            Console.WriteLine("我是不是线程池线程" + Thread.CurrentThread.IsThreadPoolThread);
            Thread.Sleep(2000);
            return string.Format("我是{0},今年{1}岁!",name,age);
        }
    }

  输出如下:

  

  这种方法有一个缺点,就是不知道异步操作什么时候执行完,什么时候开始调用EndInvoke,因为一旦EndInvoke主线程就会处于阻塞等待状态。

  3、IAsyncResult轮询

  为了克服上面提到的缺点,此时可以好好利用IAsyncResult提高主线程的工作性能,IAsyncResult有如下成员。

public interface IAsyncResult
{
  object AsyncState {get;}       //获取用户定义的对象,它限定或包含关于异步操作的信息。
  WailHandle AsyncWaitHandle {get;}  //获取用于等待异步操作完成的 WaitHandle。
  bool CompletedSynchronously {get;} //获取异步操作是否同步完成的指示。
  bool IsCompleted {get;}        //获取异步操作是否已完成的指示。
}

  示例如下:

    class Program
    {
        delegate string MyDelegate(string name,int age);
        static void Main(string[] args)
        {
            MyDelegate myDelegate = new MyDelegate(GetString);
            IAsyncResult result = myDelegate.BeginInvoke("刘备",22, null, null);

            Console.WriteLine("主线程继续工作!");

            //比上个例子,只是利用多了一个IsCompleted属性,来判断异步线程是否完成
            while (!result.IsCompleted)
            {
                Thread.Sleep(500);
                Console.WriteLine("异步线程还没完成,主线程干其他事!");
            }

            string data = myDelegate.EndInvoke(result);
            Console.WriteLine(data);

            Console.ReadKey();
        }

        static string GetString(string name, int age)
        {
            Thread.Sleep(2000);
            return string.Format("我是{0},今年{1}岁!",name,age);
        }
    }

  输出如下:

  

  以上例子,除了IsCompleted属性外,还可以使用AsyncWaitHandle如下3个方法实现同样轮询判断效果:

  • WaitOne:判断单个异步线程是否完成;
  • WaitAny:判断是否异步线程是否有指定数量个已完成;
  • WaitAll:判断是否所有的异步线程已完成;

  WaitOne:

  //比上个例子,判断条件由IsCompleted属性换成了AsyncWaitHandle,仅此而已
  while (!result.AsyncWaitHandle.WaitOne(200))
  {
      Console.WriteLine("异步线程没完,主线程继续干活!");
  }

  WaitAny:

  //是否完成了指定数量
  WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };
  while (WaitHandle.WaitAny(waitHandleList, 200) > 0)
  {
      Console.WriteLine("异步线程完成数未大于0,主线程继续甘其他事!");
  }

  WaitAll:

  WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };
  //是否全部异步线程完成
  while (!WaitHandle.WaitAll(waitHandleList, 200))
  {
      Console.WriteLine("异步线程未全部完成,主线程继续干其他事!");
  }

  4、IAsyncResult回调函数

  使用轮询方式来检测异步方法的状态非常麻烦,而且影响了主线程,效率不高。能不能异步线程完成了就直接调用实现定义好的处理函数呢?

  有,还是强大的IAsyncResult对象。

    class Program
    {
        delegate string MyDelegate(string name, int age);

        static void Main(string[] args)
        {
            //建立委托
            MyDelegate myDelegate = new MyDelegate(GetString);
            //倒数第二个参数,委托中绑定了完成后的回调方法
            IAsyncResult result1 = myDelegate.BeginInvoke("刘备",23, new AsyncCallback(Completed), null);
            //主线程可以继续工作而不需要等待
            Console.WriteLine("我是主线程,我干我的活,不再理你!");
            Thread.Sleep(5000);
            //Console.ReadKey();
        }

        static string GetString(string name, int age)
        {
            Thread.CurrentThread.Name = "异步线程";
            //注意,如果不设置为前台线程,则主线程完成后就直接卸载程序了
            //Thread.CurrentThread.IsBackground = false;
            Thread.Sleep(2000);
            return string.Format("我是{0},今年{1}岁!", name, age);
        }

        //供异步线程完成回调的方法
        static void Completed(IAsyncResult result)
        {
            //获取委托对象,调用EndInvoke方法获取运行结果
            AsyncResult _result = (AsyncResult)result;
            MyDelegate myDelegaate = (MyDelegate)_result.AsyncDelegate;
            //获得参数
            string data = myDelegaate.EndInvoke(_result);
            Console.WriteLine(data);
            //异步线程执行完毕
            Console.WriteLine("异步线程完成咯!");
            Console.WriteLine("回调函数也是由" + Thread.CurrentThread.Name + "调用的!");
        }
    }

  输出如下:

  

  注意:

  1. 回调函数依然是在辅助线程中执行的,这样就不会影响主线程的运行。
  2. 线程池的线程默认是后台线程。但是如果主线程比辅助线程优先完成,那么程序已经卸载,回调函数未必会执行。如果不希望丢失回调函数中的操作,要么把异步线程设为前台线程,要么确保主线程将比辅助线程迟完成。

  到目前为止,BeginInvoke("刘备",23, new AsyncCallback(Completed), null)还有最后一个参数没用过的。那么最后一个参数是用来干什么?传参:

namespace 控制台___学习测试
{
    class Program
    {
        delegate string MyDelegate(string name, int age);

        static void Main(string[] args)
        {
            Person p = new Person(2,"关羽");

            //建立委托
            MyDelegate myDelegate = new MyDelegate(GetString);
            //最后一个参数的作用,原来是用来传参的
            IAsyncResult result1 = myDelegate.BeginInvoke("刘备", 23, new AsyncCallback(Completed), p);
            //主线程可以继续工作而不需要等待
            Console.WriteLine("我是主线程,我干我的活,不再理你!");
            Console.ReadKey();
        }

        static string GetString(string name, int age)
        {
            Thread.CurrentThread.Name = "异步线程";
            //注意,如果不设置为前台线程,则主线程完成后就直接卸载程序了
            Thread.CurrentThread.IsBackground = false;
            Thread.Sleep(2000);
            return string.Format("我是{0},今年{1}岁!", name, age);
        }

        //供异步线程完成回调的方法
        static void Completed(IAsyncResult result)
        {
            //获取委托对象,调用EndInvoke方法获取运行结果
            AsyncResult _result = (AsyncResult)result;
            MyDelegate myDelegaate = (MyDelegate)_result.AsyncDelegate;
            //获得参数
            string data = myDelegaate.EndInvoke(_result);
            Console.WriteLine(data);

            Person p = result.AsyncState as Person;
            Console.WriteLine("传过来的参数是:" + p.Name);
            //异步线程执行完毕
            Console.WriteLine("异步线程完成咯!");
            Console.WriteLine("回调函数也是由" + Thread.CurrentThread.Name + "调用的!");
        }
    }

    public class Person
    {
        public Person(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id
        {
            get;
            set;
        }

        public string Name
        {
            get;
            set;
        }
    }
}

  输出如下:

  

时间: 2024-10-09 04:46:35

线程池之ThreadPool类与辅助线程 - <第二篇>的相关文章

C#多线程--线程池(ThreadPool)

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

高并发之——不得不说的线程池与ThreadPoolExecutor类浅析

一.抛砖引玉 既然Java中支持以多线程的方式来执行相应的任务,但为什么在JDK1.5中又提供了线程池技术呢?这个问题大家自行脑补,多动脑,肯定没坏处,哈哈哈... 说起Java中的线程池技术,在很多框架和异步处理中间件中都有涉及,而且性能经受起了长久的考验.可以这样说,Java的线程池技术是Java最核心的技术之一,在Java的高并发领域中,Java的线程池技术是一个永远绕不开的话题.既然Java的线程池技术这么重要(怎么能说是这么重要呢?那是相当的重要,那家伙老重要了,哈哈哈),那么,本文我

线程池(ThreadPool)

线程池(ThreadPool) https://www.cnblogs.com/jonins/p/9369927.html 线程池概述 由系统维护的容纳线程的容器,由CLR控制的所有AppDomain共享.线程池可用于执行任务.发送工作项.处理异步 I/O.代表其他线程等待以及处理计时器. 线程池与线程 性能:每开启一个新的线程都要消耗内存空间及资源(默认情况下大约1 MB的内存),同时多线程情况下操作系统必须调度可运行的线程并执行上下文切换,所以太多的线程还对性能不利.而线程池其目的是为了减少

线程(Thread)、线程池(ThreadPool)技术

线程:是Windows任务调度的最小单位.线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针.程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数,在一个应用程序中,常常需要使用多个线程来处理不同的事情,这样可以提高程序的运行效率,也不会使主界面出现无响应的情况.在这里主要介绍线程(Thread).线程池(ThreadPool)两种不同创建线程的区别 在通常的情况下,当我们需要开启一个新的线程时,我们直接通过Thread(继承自 System.Threading;)去创建

线程池的管理类MyThreadPoolManager

import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 线程池的管理类,单例 */ public class MyThre

QT 线程池 + TCP 小试(一)线程池的简单实现

*免分资源链接点击打开链接http://download.csdn.net/detail/goldenhawking/4492378 很久以前做过ACE + MFC/QT 的中轻量级线程池应用,大概就是利用线程池执行客户机上的运算需求,将结果返回.ACE是跨平台重量级的通信中间件,与常见的应用程序框架需要精心契合,才能不出问题.最近想到既然QT框架本身就已经具有各类功能,何不玩一玩呢,那就开搞!这个实验的代码可以从我的资源内下载. 第一步打算实现的模式,我们需要一个设置为CPU核心数的线程池,这

图解线程池工作机制,手写线程池?

ThreadPoolExecutor构造函数的各个参数说明 public ThreadPoolExecutor(int corePoolSize,//线程池中核心线程数 int maximumPoolSize,//允许的最大线程数 long keepAliveTime,//线程空闲下来后,存活的时间,这个参数只在> corePoolSize才有用 TimeUnit unit,//存活时间的单位值 BlockingQueue<Runnable> workQueue,//保存任务的阻塞队列

使用jdk自带的线程池。加载10个线程。

在开发中使用线程,经常不经意间就new Thread()一个出来,然后发现,这样做不是很好,特别是很多线程同时处理的时候,会出现CPU被用光导致机器假死,线程运行完成自动销毁后,又复活的情况. 所以在这个时候,就需要使用到线程池.. 线程池就是类似数据库连接池,限定一个规定大小的连接数(线程数),然后,需要处理的线程直接调用连接池执行线程.当插入的线程个数超过线程池个数的时候,就会排队等待... 线程池不需要到别的地方找,JDK就自带有一个挺不错的池:java.util.concurrent.E

初学线程池--1,自己实现一个线程池

自己实现一个简单的线程池 public interface ThreadPool<Job extends Runnable> { // 启动 void execute(Job job); // 关闭 void shutDown(); // 增加线程 void addWorkThread(int num); // 减少线程 void reduceWorkThread(int num) throws Exception; // 正在执行的线程数 int getSize(); } 实现类 publi