多线程之旅(11)_如何限制系统线程池ThreadPool的最大最小并发数量_SetMaxThreads/SetMinThreads用法

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.有时候我们胸有成竹的实验,结果往往并不具有普遍性,换了一个环境可能就会变的不适用。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
以下是引用中的代码示例: ```java ExecutorService executor = Executors.newFixedThreadPool(2); Runnable worker = new Runnable() { public void run() { // do some work } }; executor.execute(worker); ``` 这段代码创建了一个固定大小的线程池,其中corePoolSize为2,maximumPoolSize也为2。这意味着线程池最多会保持2个线程处于活动状态,并在任务到达时创建新的线程来处理任务。当队列中没有任务时,线程池中的线程会进入空闲状态,直到有新的任务到来。如果使用的是无界队列(例如LinkedBlockingQueue),那么线程就不会超过corePoolSize。keepAliveTime参指定了线程池中的空闲线程在等待新任务时的存活时间。 以下是引用中的代码示例: ```java ExecutorService executor = Executors.newFixedThreadPool(5); Runnable worker = new Runnable() { public void run() { // do some work } }; executor.execute(worker); ``` 这段代码创建了一个固定大小的线程池,其中corePoolSize为5,maximumPoolSize也为5。这意味着线程池最多会保持5个线程处于活动状态,并在任务到达时创建新的线程来处理任务。当队列中没有任务时,线程池中的线程会进入空闲状态,直到有新的任务到来。如果使用的是无界队列(例如LinkedBlockingQueue),那么线程就不会超过corePoolSize。keepAliveTime参指定了线程池中的空闲线程在等待新任务时的存活时间。这段代码创建了一个5个线程的线程池,如果需要创建更多的线程,可以调整corePoolSize和maximumPoolSize的值。如果使用的是有界队列(例如ArrayBlockingQueue),那么可以通过调用put()方法将任务放入队列中。这段代码展示了如何创建一个固定大小的线程池,并使用execute()方法执行任务。如果需要停止线程池,可以使用shutdown()方法或者shutdownNow()方法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [线程池ThreadPool详解](https://blog.csdn.net/qq_43651945/article/details/124717896)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日拱一两卒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值