zoukankan      html  css  js  c++  java
  • 多线程之旅(11)_如何限制系统线程池ThreadPool的最大最小并发数量_SetMaxThreads/SetMinThreads用法

    转自https://blog.csdn.net/smooth_tailor/article/details/52460566

    ThreadPool有两个设置线程池并发数量的方法,分别是:

    ThreadPool.SetMinThreads(int workerThreads, int completionPortThreads)//设置最小线程并发数

    ThreadPool.SetMaxThreads(int workerThreads, int completionPortThreads)//设置最大线程并发数

    参数解释:
    workerThreads  要由线程池根据需要创建的新的最小工作程序线程数。

    completionPortThreads  要由线程池根据需要创建的新的最小空闲异步 I/O 线程数。

    使用这两个方法可以控制线程池ThreadPool运行过程中的并发数量,他的效果怎么样呢,我们写段代码来测试一下:

        public static void ThreadUseAndConstruction()
        {
            ThreadPool.SetMinThreads(5, 5); // 设置线程池最小线程数量为5
            ThreadPool.SetMaxThreads(15, 15); // 设置线程池最大线程数量为15
         
            Stopwatch watch = new Stopwatch();
            watch.Start();
         
            WaitCallback callback = index =>
            {
                Console.WriteLine(String.Format("{0}: Task {1} started", watch.Elapsed, index));
                Thread.Sleep(10000);
                Console.WriteLine(String.Format("{0}: Task {1} finished", watch.Elapsed, index));
            };
         
            for (int i = 0; i < 20; i++)
            {
                ThreadPool.QueueUserWorkItem(callback, i);
            }
        }

    这段代码部分执行结果如下

        00:00:00.0707416: Task 2 started
        00:00:00.0706114: Task 0 started
        00:00:00.0708271: Task 1 started
        00:00:00.0708882: Task 3 started
        00:00:00.0709376: Task 4 started
        00:00:01.0110528: Task 5 started
        00:00:01.5121437: Task 6 started
        00:00:02.0163181: Task 7 started
        00:00:02.5215778: Task 8 started
        00:00:03.0237865: Task 9 started
        00:00:03.5251736: Task 10 started
        00:00:04.0279218: Task 11 started
        00:00:04.5336314: Task 12 started
        00:00:05.0385531: Task 13 started
        00:00:06.0427984: Task 14 started
        00:00:10.0755285: Task 0 finished
        00:00:10.0755484: Task 3 finished
        00:00:10.0756457: Task 1 finished
        00:00:10.0756738: Task 15 started
        00:00:10.0756873: Task 16 started
        00:00:10.0755484: Task 4 finished
        00:00:10.0757537: Task 17 started
        00:00:10.0757709: Task 2 finished
        00:00:10.0757800: Task 18 started
        00:00:10.0758043: Task 19 started
        00:00:11.0137430: Task 5 finished
        00:00:11.5173026: Task 6 finished
        00:00:12.0214753: Task 7 finished
        00:00:12.5266871: Task 8 finished
        00:00:13.0289345: Task 9 finished
        00:00:13.5254343: Task 10 finished
        00:00:14.0330949: Task 11 finished
        00:00:14.5365363: Task 12 finished
        00:00:15.0412648: Task 13 finished
        00:00:16.0458671: Task 14 finished
        00:00:20.0808412: Task 15 finished
        00:00:20.0808480: Task 16 finished
        00:00:20.0808836: Task 17 finished
        00:00:20.0810045: Task 19 finished
        00:00:20.0810050: Task 18 finished

    这段结果值得仔细分析,

    1.程序在0秒的时候,瞬间创建出了5个线程,这看起来似乎和我们设置的最小线程数有关系

    2.在第6-15个线程创建过程中,可以清晰的看到每个线程的创建时间几乎都是间隔0.5秒

    3.创建到第15个的时候,线程池停止创建新线程,直到10秒后有线程结束,才再次开始创建新线程,

    4.从创建到第15个线程后,存活的线程一直保持在15个,直到所有线程都创建完成

    从上述观察结果不难得出,ThreadPool.SetMinThreads和ThreadPool.SetMaxThreads确实有控制线程池并发放量的功能。
     
    另一个实验:CLR线程池与IO线程池的存在和关联

    在阅读一篇文章时(https://www.cnblogs.com/JeffreyZhao/archive/2009/10/20/thread-pool-3-lab.html),文中提到了一个CLR线程池与IO线程池的实验,这个实验探讨了CLR线程池与IO线程池两者的存在关系和关联关系。即首先验证了CLR线程池与IO线程池两者是否真的存在,在线程调度过程中两者是什么关系。

    根据作者文中的实验结果得出一个结论,IO线程池是真实存在的,并且和CLR线程池是相对独立的。于是我也按照作者的方法进行了验证:

        public static void IoThread()
        {
            ThreadPool.SetMinThreads(5, 3);
            ThreadPool.SetMaxThreads(5, 3);
         
            ManualResetEvent waitHandle = new ManualResetEvent(false);
         
            Stopwatch watch = new Stopwatch();
            watch.Start();
         
            WebRequest request = HttpWebRequest.Create("http://www.cnblogs.com/");
            request.BeginGetResponse(ar =>
            {
                var response = request.EndGetResponse(ar);
                Console.WriteLine(watch.Elapsed + ": Response Get");
         
            }, null);
         
            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(index =>
                {
                    Console.WriteLine(String.Format("{0}: Task {1} started", watch.Elapsed, index));
                    waitHandle.WaitOne();
                }, i);
            }
         
            waitHandle.WaitOne();
        }

    这段代码通过对ThreadPool进行阻塞,判断当ThreadPool被阻塞时,是否有其他线程池来完成未完成的IO操作,作者得出的结果是:

        00:00:00.0923543: Task 0 started
        00:00:00.1152495: Task 2 started
        00:00:00.1153073: Task 3 started
        00:00:00.1152439: Task 1 started
        00:00:01.0976629: Task 4 started
        00:00:01.5235481: Response Get

    当启动5个线程后,ThreadPool线程池被阻塞,但是仍然有一个Response Get被执行了,这说明在CLR线程池之外,应该还有一个独立的IO线程池,在CLR线程池阻塞时仍然可以正常工作。

    然而我的实验结果却是:

        00:00:00.0923543: Task 0 started
        00:00:00.1152495: Task 2 started
        00:00:00.1153073: Task 3 started
        00:00:00.1152439: Task 1 started
        00:00:01.0976629: Task 4 started

    当CLR线程被阻塞后,并没有Response Get被执行,考虑到可能是因为用的VS Code编译器问题,毕竟在2019年VS Code for Mac +.Net Core这个组合还是一个新鲜的用法。于是我换了VS2017 for Mac + .Net Core 这套环境,重新执行了一次代码,结果如下:

        00:00:00.3555712: Task 0 started
        00:00:00.3555281: Task 3 started
        00:00:00.3555586: Task 1 started
        00:00:00.3555431: Task 4 started
        00:00:00.3555174: Task 2 started
        00:00:00.4287879: Task 5 started
        00:00:01.5534256: Task 6 started
        00:00:01.5535319: Task 7 started
        00:00:02.5546253: Task 8 started
        00:00:02.5546795: Task 9 started
        00:00:03.7896417: Response Get

    结果果然不一样了,最后一个线程结束的时候出现了Response Get,但是发现10个线程全被调用了,于是我很奇怪,为何此时线程池不再阻塞,后来反复测试发现,如果ThreadPool.SetMaxThreads方法中第二个参数completionPortThreads小于等于3 时,此时线程池不会阻塞,completionPortThreads大于3 时,线程池会阻塞。

    这更加让我奇怪,我开始怀疑是不是因为我用的是Mac的原因,毕竟C#是微软的产品,于是我换了台Windows电脑,在Net Core环境下再次执行,结果如下:

        00:00:00.0923543: Task 0 started
        00:00:00.1152495: Task 2 started
        00:00:00.1153073: Task 3 started
        00:00:00.1152439: Task 1 started
        00:00:01.0976629: Task 4 started

    此时Windows+vs2017+.Net Core运行的结果和VS Code for Mac + .Net Core运行的结果是一样的,即使ThreadPool.SetMaxThreads方法中第二个参数completionPortThreads小于等于3 时,线程池依然是阻塞的,并且传说中的IO线程池也没有显示出他的存在感。

    于是我又尝试了最后一种组合,Windows + vs2017 + .Net Framework,此时的运行结果:

        00:00:00.0923543: Task 0 started
        00:00:00.1152495: Task 2 started
        00:00:00.1153073: Task 3 started
        00:00:00.1152439: Task 1 started
        00:00:01.0976629: Task 4 started
        00:00:01.5235481: Response Get

    出现了和引用文章中作者一样的结果。

    到这里已经可以得出结论了:

    1.引用文章中的作者测试环境是Windows + vs2017 + .Net Framework,在.Net Framework框架下,当CLR线程池被阻塞后,“可能”存在一个独立的IO线程池,来继续完成IO操作。之所以说可能,根据结果来看,并不能证明最后的写IO操作是由IO线程池完成的。仍需要据需实验测试。

    2..Net Core和.Net Framework两个框架中线程池的实现是有差异的。

    3.有时候我们胸有成竹的实验,结果往往并不具有普遍性,换了一个环境可能就会变的不适用。
    ————————————————
    版权声明:本文为CSDN博主「佚名兄」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/yangwohenmai1/article/details/90648566

  • 相关阅读:
    Parquet文件结构笔记
    parquet 简介
    Kubernetes 路由问题&网络问题
    postgresql:terminate hung query
    Python 动态加载并下载"梨视频"短视频
    Python 豆瓣mv爬取
    Ubuntu 硬盘分区只读,重新挂载为读写分区之后,文件依然创建出错
    Ubuntu 装机软件
    iTOP4412开发板相关内容
    linux driver ------ GPIO的驱动编写和调用
  • 原文地址:https://www.cnblogs.com/lidaying5/p/12555149.html
Copyright © 2011-2022 走看看