多线程之旅(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

猜你喜欢

转载自www.cnblogs.com/lidaying5/p/12555149.html