说到.NET Threadpool我想大家都知道,只是平时比较零散,顾现在整理一下:

一码阻塞,万码等待:ASP.NET Core 同步方法调用异步方法“死锁”的真相

.NET Threadpool starvation, and how queuing makes it worse

New and Improved CLR 4 Thread Pool Engine

所以本文主要是验证和这里这几个文章

Threadpool queue

当您调用ThreadPool.QueueUserWorkItem时,就是想象一个全局队列,其中工作项(本质上是委托)在全局队列中排队,多个线程在一个队列中选择它们。先进先出顺序。

左侧的图像显示了主程序线程,因为它创建了一个工作项; 第二个图像显示代码排队3个工作项后的全局线程池队列; 第三个图像显示了来自线程池的2个线程,它们抓取了2个工作项并执行它们。如果在这些工作项的上下文中(即来自委托中的执行代码),为CLR线程池创建了更多的工作项,它们最终会出现在全局队列中(参见右图),并且生命仍在继续。

在CLR 4中,线程池引擎已对其进行了一些改进(它在CLR的每个版本中都进行了积极的调整),并且这些改进中的一部分是在使用新的System.Threading.Tasks时可以实现的一些性能提升。创建和启动一个Task(传递一个委托),相当于在ThreadPool上调用QueueUserWorkItem。通过基于任务的API使用时可视化CLR线程池的一种方法是,除了单个全局队列之外,线程池中的每个线程都有自己的本地队列

正如通常的线程池使用一样,主程序线程可以创建将在全局队列(例如Task1和Task2)上排队的任务,并且线程将通常以FIFO方式获取这些任务。事情分歧的是,在执行任务的上下文中创建的任何新任务(例如,Task2,Task23)最终在该线程池线程的本地队列上。

因此,从图片中进一步提升,让我们假设Task2还创建了另外两个任务,例如Task4和Task5。

任务按预期结束在本地队列上,但是线程选择在完成当前任务(即Task2)时执行哪个任务?最初令人惊讶的答案是它可能是Task5,它是排队的最后一个 - 换句话说,LIFO算法可以用于本地队列。在大多数情况下,队列中最后创建的任务所需的数据在缓存中仍然很热,因此将其拉下并执行它是有意义的。显然,这意味着顺序没有承诺,但为了更好的表现,放弃了某种程度的公平。

其他工作线程完成Task1然后转到其本地队列并发现它为空; 然后它进入全局队列并发现它为空。我们不希望它闲置在那里,所以发生了一件美妙的事情:偷工作。该线程进入另一个线程的本地队列并“窃取”一个任务并执行它!这样我们就可以保持所有核心的繁忙,这有助于实现细粒度并行负载平衡目标。在上图中,注意“窃取”以FIFO方式发生,这也是出于地方原因的好处(其数据在缓存中会很冷)。此外,在许多分而治之的场景中,之前生成的任务可能会产生更多的工作(例如Task6),这些工作现在最终会在另一个线程的队列中结束,从而减少频繁的窃取。

线程池有 n+1 个队列,每个线程有自己的本地队列(n),整个线程池有一个全局队列(1)。每个线程接活(从队列中取出任务执行)的顺序是这样的:先从自己的本地队列中找活 -> 如果本地队列为空,则从全局队列中找活 -> 如果全局队列为空,则从其他线程的本地队列中抢活。

TASK

先看以下4个demo

1:如果你运行程序,你会发现它在控制台中设法显示“Ended”几次,然后就没有任何事情发生了,就像假死了

using System;
using System.Threading;
using System.Threading.Tasks; namespace Starvation
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Environment.ProcessorCount);
ThreadPool.SetMinThreads(8, 8);
Task.Factory.StartNew( Producer,TaskCreationOptions.None);
Console.ReadLine();
}
static void Producer()
{
while (true)
{
Process();
Thread.Sleep(200);
}
}
static async Task Process()
{
await Task.Yield();
var tcs = new TaskCompletionSource<bool>();
Task.Run(() =>
{
Thread.Sleep(1000);
tcs.SetResult(true);
});
tcs.Task.Wait();
Console.WriteLine("Ended - " + DateTime.Now.ToLongTimeString());
}
}
}

2:删除Task.Yield并在Producer中手动启动新任务。应用程序最初有点挣扎,直到线程池足够增长。然后我们有一个稳定的消息流,并且线程数是稳定的

using System;
using System.Threading;
using System.Threading.Tasks; namespace Starvation
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Environment.ProcessorCount);
ThreadPool.SetMinThreads(8, 8);
Task.Factory.StartNew( Producer,TaskCreationOptions.None);
Console.ReadLine();
} static void Producer()
{
while (true)
{
// Creating a new task instead of just calling Process
// Needed to avoid blocking the loop since we removed the Task.Yield
Task.Factory.StartNew(Process);
Thread.Sleep(200);
}
} static async Task Process()
{
// Removed the Task.Yield
var tcs = new TaskCompletionSource<bool>();
Task.Run(() =>
{
Thread.Sleep(1000);
tcs.SetResult(true);
});
tcs.Task.Wait();
Console.WriteLine("Ended - " + DateTime.Now.ToLongTimeString());
}
}
}

3:工作代码但在其自己的线程中启动Producer会怎么样,运行效果和1相似,有假死的效果, 但是感觉比1 好点

using System;
using System.Threading;
using System.Threading.Tasks; namespace Starvation
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Environment.ProcessorCount);
ThreadPool.SetMinThreads(8, 8);
Task.Factory.StartNew( Producer, TaskCreationOptions.LongRunning); // Start in a dedicated thread
Console.ReadLine();
} static void Producer()
{
while (true)
{
Process();
Thread.Sleep(200);
}
} static async Task Process()
{
await Task.Yield();
var tcs = new TaskCompletionSource<bool>();
Task.Run(() =>
{
Thread.Sleep(1000);
tcs.SetResult(true);
});
tcs.Task.Wait();
Console.WriteLine("Ended - " + DateTime.Now.ToLongTimeString());
}
}
}

4.Producer放回线程线程,但在启动Process任务时使用PreferFairness标志,再次遇到第一种情况:应用程序锁定,线程数无限增加。

using System;
using System.Threading;
using System.Threading.Tasks; namespace Starvation
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Environment.ProcessorCount);
ThreadPool.SetMinThreads(8, 8);
Task.Factory.StartNew(Producer, TaskCreationOptions.None);
Console.ReadLine();
} static void Producer()
{
while (true)
{
Task.Factory.StartNew(Process, TaskCreationOptions.PreferFairness); // Using PreferFairness
Thread.Sleep(200);
}
} static async Task Process()
{
var tcs = new TaskCompletionSource<bool>();
Task.Run(() =>
{
Thread.Sleep(1000);
tcs.SetResult(true);
});
tcs.Task.Wait();
Console.WriteLine("Ended - " + DateTime.Now.ToLongTimeString());
}
}
}

线程挑选项目排队的规则很简单:

  • 该项将被排入全局队列:

    • 如果排队项目的线程不是线程池线程

    • 如果它使用ThreadPool.QueueUserWorkItem / ThreadPool.UnsafeQueueUserWorkItem

    • 如果它使用Task.Factory.StartNewTaskCreationOptions.PreferFairness标志

    • 如果它在默认任务调度程序上使用Task.Yield

  • 在几乎所有其他情况下,该项将被排入线程的本地队列

每当线程池线程空闲时,它将开始查看其本地队列,并以LIFO顺序对项目进行出列。如果本地队列为空,则线程将查看全局队列并以FIFO顺序出列。如果全局队列也为空,则线程将查看其他线程的本地队列并以FIFO顺序出列(以减少与队列所有者的争用,该队列以LIFO顺序出列)。

在代码的所有变体中,Thread.Sleep(1000)在本地队列中排队,因为Process总是在线程池线程中执行。但在某些情况下,我们将Process排入全局队列,而将其他队列放入本地队列:

  • 在代码的第一个版本中,我们使用Task.Yield,它排队到全局队列

  • 在第二个版本中,我们使用Task.Factory.StartNew,它排队到本地队列

  • 在第三个版本中,我们将Producer线程更改为不使用线程,因此Task.Factory.StartNew排队到全局队列

  • 在第四个版本中,Producer再次是一个线程线程,但我们在将Process 排入队列时使用TaskCreationOptions.PreferFairness,因此再次使用全局队列

由于使用全局队列引起的优先级,我们添加的线程越多,我们对系统施加的压力就越大,当使用本地队列(代码的第二个版本)时,新生成的线程将从其他线程的本地队列中选择项目,因为全局队列为空。因此,新线程有助于减轻系统压力。

.NET Threadpool的一点认识的更多相关文章

  1. C#多线程学习 之 线程池[ThreadPool](转)

    在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应                   这一般使用ThreadPo ...

  2. Java threadpool机制深入分析

    简介 在前面的一篇文章里我对java threadpool的几种基本应用方法做了个总结.Java的线程池针对不同应用的场景,主要有固定长度类型.可变长度类型以及定时执行等几种.针对这几种类型的创建,j ...

  3. C# WinForm多线程(二)ThreadPool 与 Timer

    本文接上文,继续探讨WinForm中的多线程问题,再次主要探讨threadpool 和timer 一  ThreadPool 线程池(ThreadPool)是一种相对较简单的方法,它适应于一些需要多个 ...

  4. HTTP协议 HttpWebRequest和 Socket的一点总结

    HTTP协议 HttpWebRequest和 Socket的一点总结 相信接触过网络开发的人对HTTP.HttpWebRequest.Socket这些东西都不陌生吧.它们之间的一些介绍和关系我这里都忽 ...

  5. Writing A Threadpool in Rust

    文 Akisann@CNblogs / zhaihj@Github 本篇文章同时发布在Github上:https://zhaihj.github.io/writing-a-threadpool-in- ...

  6. .NET 异步多线程,Thread,ThreadPool,Task,Parallel,异常处理,线程取消

    今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主,毕竟用的比较多的现在就是这些了,再往前去的,除非是老项目,不然真的应该是挺少了,大概有个概念,就当了解一 ...

  7. 线程(Thread,ThreadPool)、Task、Parallel

    线程(Thread.ThreadPool) 线程的定义我想大家都有所了解,这里我就不再复述了.我这里主要介绍.NET Framework中的线程(Thread.ThreadPool). .NET Fr ...

  8. [No0000181]改善C#程序的建议9:使用Task代替ThreadPool和Thread

    一:Task的优势 ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便.比如: 1: ThreadPool不支持线程的取消.完成.失败通知等交互 ...

  9. C# 线程池ThreadPool的用法简析

    https://blog.csdn.net/smooth_tailor/article/details/52460566 什么是线程池?为什么要用线程池?怎么用线程池? 1. 什么是线程池? .NET ...

随机推荐

  1. mysql查看变量/配置文件位置

    //查看所有变量 show global variables //查看某个变量的值 show global variables where variable_name like '%innodb_fl ...

  2. Spring(四)使用注解注入Bean

    注解简单介绍 是代码里面的特殊标记,使用注解完成功能. 注解写法@ 注解名称(属性名=属性值). 注解可以作用在类.方法.属性上面. 使用流程: 在ApplicationContext.xml中开启注 ...

  3. STC15W408AS简单使用教程-简单的光强检测!

    第一步:搭建开发环境 安装最新版本的STC_ISP程序烧录软件,链接:http://pan.baidu.com/s/1slLPnOD 密码:6bov 安装keil C51的51系列单片机集成IDE软件 ...

  4. 如何将.SQL文件的数据导入到Mysql的数据库中

    一.用cmd的调试环境导入.sql文件中的数据: WinR键打开cmd输入: MySQL -u root -p 进入MySQL后MySQL>use DR;   MySQL> source  ...

  5. Codeforces 1131F Asya And Kittens (构造)【并查集】

    <题目链接> 题目大意:有$n$只小猫,开始将它们放在指定的n个单元格内,然后随机从n-1个隔板中拆除隔板,最终使得这些小猫在同一单元格.现在依次给出拆除隔板的顺序,比如:1 4 就表示1 ...

  6. Django之模板基础

    Django之模板 目录 变量 过滤器 标签的使用 变量 变量的引用格式 使用双括号,两边空格不能省略. 语法格式: {{var_name}} Template和Context对象 context 字 ...

  7. Emoji表情编解码库XXL-EMOJI

    <Emoji表情编解码库XXL-EMOJI> 一.简介 1.1 概述 XXL-EMOJI 是一个灵活可扩展的Emoji表情编解码库,可快速实现Emoji表情的编解码. 1.2 特性 1.简 ...

  8. CSS3 animation 练习

    css3 的动画让 html 页面变得生机勃勃,但是如何用好动画是一门艺术,接下来我来以一个demo为例,来练习css3 animation. 我们先详细了解一下animation 这个属性. ani ...

  9. 可编辑DIV与移动端软键盘兼容性问题汇总

    此文复现的所有兼容性问题均为以下情况: 1. 腾讯X5内核 2. 全屏webview 问题如下: 1. IOS12 中软键盘弹出导致页面顶部截断,并且无法恢复. 解决方法:添加交互事件,调用本地方法, ...

  10. linux 学习笔记 wc命令

    #wc 文件名.txt 输出 4 13 65 文件名.txt  -->4 行13个单词 #wc -w 文件名.txt  统计单词数量 #wc -l 文件名.txt  统计行数 #wc -c  文 ...