关于使用ThreadPool.SetMinThreads方法提升API服务响应性能的总结

使用该方法的背景?

某个API服务在每日请求量40W的情况下,流量增多时会产生大量请求异常:The operation was canceled,从实际情况来看,并不是外部依赖接口或者服务实例不足导致,于是设置线程池数量后,服务性能提升效果显著。

方法定义: 设置线程池在新请求预测中维护的空闲线程数。

public static bool SetMinThreads (int workerThreads, int completionPortThreads)

参数:

  • workerThreads:要由线程池维护的新的最小空闲辅助线程数。
  • completionPortThreads:要由线程池维护的新的最小空闲异步 I/O 线程数。

返回值:

如果更改成功,则为 true;否则为 false。

默认的线程池设置是什么?

使用ThreadPool.GetMinThreads方法获取默认的线程池中工作线程及异步I/O线程数量,该数量默认为CPU的物理核心数,在我电脑测试则两个值都为6。

ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);

线程池中可设置的线程数量最大值是多少?

工作线程最大值为:32767,异步I/O线程最大值为:1000,该数量与CPU核心数无关,如果使用SetMinThreads方法时,数量大于可设置的最大值时,将设置失败,即SetMinThreads方法返回false,表示更改失败。

//以下两次调用ThreadPool.SetMinThreads将返回false
ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
var result_workerThreads = ThreadPool.SetMinThreads(100, completionPortThreads + 1);
var result_completionPortThreads = ThreadPool.SetMinThreads(workerThreads + 1, completionPortThreads);

什么情况下应该去设置线程池中的线程数量?

您可能需要调整空闲线程数,以实现最佳的总体性能。对于那些在一段时间不活动之后爆发大量活动的应用,少量增加空闲线程数可以显著提高吞吐量。

以上是微软官方文档的建议,意思很明确了,就是服务的流量在不平稳的情况下,而且不定时会有流量高峰,那么调整空闲线程数会是建议的选择。

线程数量应该设置多少比较合适?

  • 首先,不要把线程数设置为比默认值(即CPU核心数)更低的值,当然一般不会这么做,这就是反向优化了,因为这会对性能造成很大影响。
  • 【默认值 - 最大值】这个范围都是允许的线程数量取值范围,具体的设置值应该参考物理机的性能及服务的负载情况,常见的取值范围为100-300,维护大量的空闲线程会消耗系统资源,应以能够解决服务性能问题的最小线程数为设置参考值,如经过测试将线程数设置为100可解决服务的大量超时问题,则建议将线程数量设置为150即可。

线程池数量不足时对性能影响的简单测试:

代码如下:设置线程池最小空闲线程为6,最大值为10,向线程池中添加20个任务,观察执行时间

public async static Task Main()
{
    ThreadPool.SetMinThreads(6, 6);
    ThreadPool.SetMaxThreads(10, 10);

    Stopwatch watch = new Stopwatch();
    watch.Start();

    void callback(object 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);
    }

    Console.ReadKey();
}

输出结果如下:

00:00:00.0510352: Task 1 started
00:00:00.0510410: Task 3 started
00:00:00.0510345: Task 2 started
00:00:00.0510340: Task 0 started
00:00:00.0510520: Task 4 started
00:00:00.0510603: Task 5 started

这里开始已经超过了最小的空闲线程数,需要开始创建新线程

结论:在最小空闲线程数范围内,任务会立即开始执行

00:00:00.8377380: Task 6 started
00:00:01.8375997: Task 7 started
00:00:02.8375141: Task 8 started
00:00:03.8355775: Task 9 started

在空闲线程内的任务数量是立即开始的,而以上四个新线程创建时却花了将近4秒才创建并开始任务,
开销惊人

结论:线程数量不足时去创建线程非常耗时,如果突然有大量请求进入,耗时可想而知

00:00:10.1052187: Task 5 finished
00:00:10.1052447: Task 2 finished
00:00:10.1052449: Task 0 finished
00:00:10.1052572: Task 1 finished
00:00:10.1052602: Task 3 finished
00:00:10.1052599: Task 4 finished

00:00:10.1053114: Task 10 started
00:00:10.1064071: Task 11 started
00:00:10.1064901: Task 12 started
00:00:10.1065869: Task 13 started
00:00:10.1066193: Task 14 started
00:00:10.1066530: Task 15 started

在线程池最大值为10的情况下,第10个线程任务,并没有被创建出新线程去执行,
而是等待其他线程任务执行完毕,利用空闲线程开始任务

结论:在达到最大允许的线程数数量时,会停止创建新线程

00:00:10.8389135: Task 6 finished
00:00:10.8393725: Task 16 started

注意观察以上两波线程任务开始和结束的情况,都是相同数量的任务结束后,马上执行新任务(在达到最大线程数量的情况下)

结论:线程数量不足时,新任务会等待到空闲线程后立即执行

00:00:11.8383439: Task 7 finished
00:00:11.8384659: Task 17 started
00:00:12.8378835: Task 8 finished
00:00:12.8380083: Task 18 started
00:00:13.8357117: Task 9 finished
00:00:13.8363946: Task 19 started
00:00:20.1068402: Task 10 finished
00:00:20.1068384: Task 11 finished
00:00:20.1078310: Task 15 finished
00:00:20.1078314: Task 14 finished
00:00:20.1078460: Task 13 finished
00:00:20.1078566: Task 12 finished
00:00:20.8402527: Task 16 finished
00:00:21.8392482: Task 17 finished
00:00:22.8385449: Task 18 finished
00:00:23.8371386: Task 19 finished

关于使用ThreadPool.SetMinThreads方法提升API服务响应性能的总结

上一篇:UOS windows字体安装


下一篇:WPF TextBox限制只能输入数字