多线程之旅(ThreadPool 线程池)

一、什么是ThreadPool 线程池(源码

1.线程池顾名思义,有我们的系统创建一个容器装载着我们的线程,由CLR控制的所有AppDomain共享。线程池可用于执行任务、发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。所以使用线程池不需要自己创建线程,而是通过线程池来创建和执行和管理线程。

二、ThreadPool 线程池和线程的区别

1.ThreadPool 线程池是在.NET 2.0出现的,是一个享元模式整个程序共同享用这一个线程池,当我们的线程执行任务之后它不会立刻销毁,它会回到线程池中,如果有新的任务它就会去执行。避免了我们线程的重复创建和销毁(也不会造成我们CPU的上下文切换的损耗)。

2.大家仔细看一下我前面写的Thread 创建线程执行任务之后,它会自动销毁。那问题来了我们经常的创建、销毁线程这可都是资源的浪费呀!!所以我们要利用每个线程占有的资源。

三、ThreadPool 线程池缺点

1.线程池在性能上优于线程,但是它也是有缺点的。它不支持线程的取消、完成、失败通知等交互性操作。

2.它不能设置池中线程的Name,会增加使用者的难度。

  3.线程池中线程通常都是后台线程,优先级为ThreadPriority.Normal

  4.线程池堵塞会影响我们的性能,阻塞会使CLR错误地认为它占用了大量CPU。CLR能够检测或补偿(往池中注入更多线程),但是这可能使线程池受到后续超负荷的印象。Task (也及时后面要讲的)解决了这个问题。

  5.线程池使用的是全局队列,全局队列中的线程依旧会存在竞争共享资源的情况,从而影响性能(Task 解决了这个问题,方案是使用本地队列)。

四、线程池工作原理

  1.CLR初始化时,线程池中是没有线程的,但是内部有一个操作请求队列,当我们的应用程序使用异步时,会将一个记录项添加到线程池的队列中,线程池队列会自动读取这个记录项,并且发给一个线程池的线程,如果线程池没有线程就会创建一个线程执行这任务,当线程完成任务它不会自动销毁而是回到我们的线程池中,等待线程池派发新的任务。

  2.如果程序给线程池派发了很多任务,线程池也会使用这一个线程执行所有的任务,如果我们的请求速度大于了我们的线程处理速度,就会创建额外线程,就不会导致我们创建了过多的线程。

  2.当我们的线程有大量休息的,它们会在一段时间内自动销毁。这样很好的控制了我们应用程序的性能。

五、ThreadPool 线程池使用

  1.ThreadPool是一个静态类,调用QueueUserWorkItem方法,是可以将一个异步计算放入我们的线程池队列中。

public static bool QueueUserWorkItem(WaitCallback callBack);
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
方法 说明
QueueUserWorkItem 启动线程池里的一个线程(工作者线程)
GetMinThreads 检索线程池在新请求预测中能够按需创建的线程的最小数量。
GetMaxThreads 最多可用线程数,所有大于此数目的请求将保持排队状态,直到线程池线程由空闲。
GetAvailableThreads 剩余空闲线程数。
SetMaxThreads 设置线程池中的最大线程数(请求数超过此值则进入队列)。
SetMinThreads 设置线程池最少需要保留的线程数。

  2.我们可以看到ThreadPool比Thread少了很多的API,被砍掉了

/// <summary>
        /// ThreadPool的使用
        /// workerThreads  CLR线程池分为工作者线程(workerThreads)
        /// completionPortThreads I/O线程(completionPortThreads)
        /// </summary>
        public static void Show()
        {
            //使用线程
            ThreadPool.QueueUserWorkItem((x) => Running());
            ThreadPool.GetAvailableThreads(out int workerThreads, out int completionPortThreads);
            Console.WriteLine($"没有设置线程数之前 workerThreads:{workerThreads}  completionPortThreads:{completionPortThreads}");
            //设置最大的线程数
            ThreadPool.SetMaxThreads(16, 16);
            //设置最小的线程数
            ThreadPool.SetMinThreads(8, 8);
            ThreadPool.GetAvailableThreads(out int workerThreads1, out int completionPortThreads1);
            Console.WriteLine($"设置最大的线程数之后 workerThreads:{workerThreads1}  completionPortThreads:{completionPortThreads1}");
            Console.ReadLine();
        }

View Code

  3.我们要注意的就是堵塞线程的时候一定要做好处理,最好是不要堵塞我们的线程,不然很容易造成死锁GG

/// <summary>
        /// ThreadPool 线程等待
        ///类  包含了一个bool属性
        ///false--WaitOne等待--Set--true--WaitOne直接过去
        ///true--WaitOne直接过去--ReSet--false--WaitOne等待
        ///https://www.cnblogs.com/howtrace/p/11362284.html
        /// </summary>
        public static void Show1()
        {
            //设置最大的线程数
            ThreadPool.SetMaxThreads(16, 16);
            //设置最小的线程数
            ThreadPool.SetMinThreads(8, 8);
            //设置false使用WaitOne()会直接堵塞线程,不会释放 、Set()设置为true
            ManualResetEvent manualResetEvent = new ManualResetEvent(false);
            //设置false使用WaitOne()会直接堵塞线程,不会释放 、Set()设置为true
            AutoResetEvent autoResetEvent = new AutoResetEvent(false);
            //上面两种方法都是可以拦截线程,都是继承EventWaitHandle 接口
            //就都具有Reset() //红灯 设置为false导致线程等待
            //Set() //绿灯 设置为true 启动线程继续执行
            //WaitOne() // 等待信号 会根据我们线程状态执行,为true不需要等待直接执行
            //反之为false会等待线程状态为true才会执行

            //不同点 ManualResetEvent AutoResetEvent
            //ManualResetEvent 在·使用Set()的时候会所有处理 WaitOne 状态线程均继续执行。
            //AutoResetEvent 在使用Set()的时候会执行一个线程其他的线程继续等待执行。

            for (int i = 0; i < 20; i++)
            {
                var k = i;
                ThreadPool.QueueUserWorkItem(x =>
                {
                    Console.WriteLine(k);
                    if (k < 18)
                    {
                        //等待线程,但是上面我们只开了16个线程,结果我18个线程全部等待
                        //导致了死锁
                        manualResetEvent.WaitOne();
                    }
                    else
                    {
                        //恢复执行状态
                        manualResetEvent.Set();
                    }
                });
                if (manualResetEvent.WaitOne())
                {
                    Console.WriteLine("没有死锁、、、");
                }
                Console.WriteLine("等着QueueUserWorkItem完成后才执行");
            }
            Console.ReadLine();
        }

View Code

(0)

相关推荐

  • 你可能写了个假异步,并不能提高请求线程池的吞吐量

    不知道用什么词形容,就叫它假异步吧. 写异步方法,async 和 await 要一路写到底,否则就是假异步,并不能提高请求线程池的吞吐量. 真正的异步,我的理解是这样的:比如调用一个查询接口,在当前线 ...

  • .NET异步和多线程系列(二)- Thread和ThreadPool

    一.Thread类 C#里面的多线程:Thread类是C#语言对线程对象的一个封装. 首先看下如何开启线程,执行委托的内容: /// <summary>/// 一个比较耗时耗资源的私有方法 ...

  • 多线程之旅(10)_QueueUserWorkItem和UnsafeQueueUserWorkItem的区别

    转载:https://blog.csdn.net/yangwohenmai1/article/details/90490880 这是个比较冷门的点,是我在写多线程之旅(2)_创建一个属于自己的精简线程 ...

  • C#多线程编程(二)线程池与TPL

    一.直接使用线程的问题 每次都要创建Thread对象,并向操作系统申请创建一个线程,这是需要耗费CPU时间和内存资源的. 无法直接获取线程函数返回值 无法直接捕捉线程函数内发生的异常 使用线程池可以解 ...

  • python爬虫14 | 就这么说吧,如果你不懂多线程和线程池,那就去河边摸鱼!

    你知道吗? 在我的心里 你是多么的重要 就像 恩 请允许我来一段 freestyle 你们准备好了妹油 你看 这个碗 它又大又圆 就像 这条面 它又长又宽 你们 在这里 看文章 觉得 很开心 就像 我 ...

  • 带你通俗易懂的理解——线程、多线程与线程池

    进程与线程 进程:进程就是正在执行的程序. 线程:是程序执行的一条路径, 一个进程中可以包含多条线程. 通俗理解:例如你打开微信就是打开一个进程,在微信里面和好友视频聊天就是开启了一条线程. 两者之间 ...

  • 如何合理地估算线程池大小?

    这个问题虽然看起来很小,却并不那么容易回答. 大家如果有更好的方法欢迎赐教,先来一个天真的估算方法: 假设要求一个系统的TPS(Transaction Per Second或者Task Per Sec ...

  • C#线程学习笔记三:线程池中的I/O线程

    本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/20/MultiThreads.html,记录一下学习过程以备后续查用.     一.I/O线 ...

  • 多线程之旅(Thread)

    在上篇文章中我们已经知道了多线程是什么了,那么它到底可以干嘛呢?这里特别声明一个前面的委托没看的同学可以到上上上篇博文查看,因为多线程要经常使用到委托.源码一.异步.同步1.同步(在计算的理解总是要你 ...

  • Java主线程等待子线程、线程池

    print public class TestThread extends Thread { public void run() { System.out.println(this.getName() ...

  • 线程池ThreadPoolExecutor源码分析,看这一篇就够了

    前言 多线程是我们日常工作中很少能接触到的技术,但是面试的时候100%会被问到,万一工作中用到了基本不会,本篇咱们就来深入分析线程池的实现类ThreadPoolExecutor. 1.构造方法 构造方 ...

  • 分析源码,学会正确使用 Java 线程池

    在日常的开发工作当中,线程池往往承载着一个应用中最重要的业务逻辑,因此我们有必要更多地去关注线程池的执行情况,包括异常的处理和分析等.本文主要聚焦在如何正确使用线程池上,以及提供一些实用的建议.文中会 ...