C#并行编程(2):.NET线程池
线程 Thread
在总结线程池之前,先来看一下.NET线程。
.NET线程与操作系统(Windows)线程有什么区别?
.NET利用Windows的线程处理功能。在C#程序编写中,我们首先会新建一个线程对象System.Threading.Thread
,并为其指定一个回调方法;当我们调用线程对象的Start
方法启动线程时,会创建一个操作系统线程来执行回调方法。.NET中的线程实际上等价于Windows系统线程,都是CPU调度和分配的对象。
前台线程和后台线程
.NET把线程分为前台线程和后台线程,两者几乎相同,唯一的区别是,前台线程会阻止进程的正常退出,后台线程则不会。下面用一个例子描述前、后台线程的区别:
class Program
{
static void Main(string[] args)
{
ThreadDemo threadDemo = new ThreadDemo();//间隔500ms持续打印1-20至控制台
//threadDemo.RunForegroundThread(); //用前台线程打印
threadDemo.RunBackgroundThread(); //用台线程打印
{//把主线程挂起5s,然后终止主线程
Thread.Sleep(5000);
Thread.CurrentThread.Abort();
}//如果用前台线程工作,打印到20才会终止;以后台线程,则不会打印到20
Console.ReadKey();
}
}
public class ThreadDemo
{
private readonly Thread _foregroundThread;
private readonly Thread _backgroundThread;
public ThreadDemo()
{
this._foregroundThread = new Thread(WriteNumberWorker) { Name = "ForegroundThread"};//默认是前台线程
this._backgroundThread = new Thread(WriteNumberWorker) { Name = "BackgroundThread", IsBackground = true };//后台线程
}
/// <summary>
/// 模拟线程工作
/// </summary>
private static void WriteNumberWorker()
{
for (int i = 0; i < 20; i++)
{
Console.WriteLine($"{DateTime.Now}=> {Thread.CurrentThread.Name} writes {i + 1}.");//打印从1到20到控制台
Thread.Sleep(500);//挂起500毫秒,方便测试
}
}
/// <summary>
/// 运行前台线程
/// </summary>
public void RunForegroundThread()
{
this._foregroundThread?.Start();
}
/// <summary>
/// 运行后台线程
/// </summary>
public void RunBackgroundThread()
{
this._backgroundThread?.Start();
}
}
线程池 ThreadPool
线程的创建和销毁要耗费很多时间,而且过多的线程不仅会浪费内存空间,还会导致线程上下文切换频繁,影响程序性能。为改善这些问题,.NET运行时(CLR)会为每个进程开辟一个全局唯一的线程池来管理其线程。
线程池内部维护一个操作请求队列,程序执行异步操作时,添加目标操作到线程池的请求队列;线程池代码提取记录项并派发给线程池中的一个线程;如果线程池中没有可用线程,就创建一个新线程,创建的新线程不会随任务的完成而销毁,这样就可以避免线程的频繁创建和销毁。如果线程池中大量线程长时间无所事事,空闲线程会进行自我终结以释放资源。
线程池通过保持进程中线程的少量和高效来优化程序的性能。
C#中线程池是一个静态类,维护两种线程,工作线程
和异步IO线程
,这些线程都是后台线程。线程池不会影响进程的正常退出。
线程池的使用
线程池提供两个静态方法SetMaxThreads
和SetMinThreads
让我们设置线程池的最大线程数和最小线程数。最大线程数指的是,该线程池能够创建的最大线程数,当线程数达到设定值且忙碌,异步任务将进入请求队列,直到有线程空闲才会执行;最小线程数指的是,线程池优先尝试以设置数量的线程处理请求,当请求数达到一定量(未做深入研究)时,才会创建新的线程。
下面的例子展示了线程池的特性及常见使用方式。
class Program
{
static void Main(string[] args)
{
//RunThreadPoolDemo();
RunCancellableWork();
Console.ReadKey();
}
static void RunThreadPoolDemo()
{
ThreadPoolDemo.ThreadPoolDemo.ShowThreadPoolInfo();
ThreadPool.SetMaxThreads(100, 100); // 默认(1023,1000)(8核心CPU)
ThreadPool.SetMinThreads(8, 8); // 默认是CPU核心数
ThreadPoolDemo.ThreadPoolDemo.ShowThreadPoolInfo();
ThreadPoolDemo.ThreadPoolDemo.MakeThreadPoolDoSomeWork(100);//计算限制任务
ThreadPoolDemo.ThreadPoolDemo.MakeThreadPoolDoSomeIOWork();//IO限制任务
}
static void RunCancellableWork()
{
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] started a work");
Console.WriteLine("Press 'Esc' to cancel the work.");
Console.WriteLine();
ThreadPoolDemo.ThreadPoolDemo.DoSomeWorkWithCancellation();
if (Console.ReadKey(true).Key == ConsoleKey.Escape)
{// 发送取消通知
ThreadPoolDemo.ThreadPoolDemo.CTSource.Cancel();
}
}
}
public class ThreadPoolDemo
{
/// <summary>
/// 显示线程池信息
/// </summary>
public static void ShowThreadPoolInfo()
{
int workThreads, completionPortThreads;
//当前线程池可用的工作线程数量和异步IO线程数量
ThreadPool.GetAvailableThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"GetAvailableThreads => workThreads:{workThreads};completionPortThreads:{completionPortThreads}");
//线程池最大可用的工作线程数量和异步IO线程数量
ThreadPool.GetMaxThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"GetMaxThreads => workThreads:{workThreads};completionPortThreads:{completionPortThreads}");
//出现新的请求,判断是否需要创建新线程的依据
ThreadPool.GetMinThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"GetMinThreads => workThreads:{workThreads};completionPortThreads:{completionPortThreads}");
Console.WriteLine();
}
/// <summary>
/// 让线程池做些事情
/// </summary>
public static void MakeThreadPoolDoSomeWork(int workCount = 10)
{
for (int i = 0; i < workCount; i++)
{
int index = i;
// 将方法排队进入线程池工作项队列,当线程池有空闲线程时执行方法
ThreadPool.QueueUserWorkItem(s =>
{
Thread.Sleep(100); //模拟工作时长
Debug.Print($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] is running. [{index}]");
ShowAvailableThreads("WorkerThread");
});
}
}
/// <summary>
/// 让线程池做些IO工作
/// </summary>
public static void MakeThreadPoolDoSomeIOWork()
{
//随便找一些可以访问的网址
IList<string> uriList = new List<string>()
{
"http://news.baidu.com/",
"https://www.hao123.com/",
"https://map.baidu.com/",
"https://tieba.baidu.com/",
"https://wenku.baidu.com/",
"http://fanyi-pro.baidu.com",
"http://bit.baidu.com/",
"http://xueshu.baidu.com/",
"http://www.cnki.net/",
"http://www.wanfangdata.com.cn",
};
foreach (string uri in uriList)
{
WebRequest request = WebRequest.Create(uri);
request.BeginGetResponse(ac =>
{// 异步请求网址,将会利用线程池中异步IO线程
try
{
WebResponse response = request.EndGetResponse(ac);
ShowAvailableThreads("IOThread");
Debug.Print($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] is running. [{response.ContentLength}]");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}, request);
}
}
/// <summary>
/// 打印线程池当前可用线程数
/// </summary>
private static void ShowAvailableThreads(string sourceTag = null)
{
int workThreads, completionPortThreads;
ThreadPool.GetAvailableThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"{sourceTag} GetAvailableThreads => workThreads:{workThreads};completionPortThreads:{completionPortThreads}");
Console.WriteLine();
}
/// <summary>
/// 取消通知者
/// </summary>
public static CancellationTokenSource CTSource { get; set; } = new CancellationTokenSource();
/// <summary>
/// 执行可取消的任务
/// </summary>
public static void DoSomeWorkWithCancellation()
{
ThreadPool.QueueUserWorkItem(t =>
{
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] begun running. [0 - 9999]");
for (int i = 0; i < 10000; i++)
{
if (CTSource.Token.IsCancellationRequested)
{
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] recived the cancel token. [{i}]");
break;
}
Thread.Sleep(100);// 模拟工作时长
}
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] was cancelled.");
});
}
}
线程池的调度
前面提到,线程池内部维护者一个工作项队列,这个队列指的是线程池全局队列。实际上,除了全局队列,线程池会给每个工作者线程维护一个本地队列。
当我们调用ThreadPool.QueueUserWorkItem
方法时,工作项会被放入全局队列;使用定时器Timer
的时候,也会将工作项放入全局队列;但是,当我们使用任务Task
的时候,假如使用默认的任务调度器,任务会被调度到工作者线程的本地队列中。
工作者线程优先执行本地队列中最新进入的任务,如果本地队列中已经没有任务,线程会尝试从其他工作者线程任务队列的队尾取任务执行,这里需要进行同步。如果所有工作者线程的本地队列都没有任务可以执行,工作者线程才会从全局队列取最新的工作项来执行。所有任务执行完毕后,线程睡眠,睡眠一定时间后,线程醒来并销毁自己以释放资源。
线程池处理异步IO的内部原理
上面的例子中,从网站获取信息需要用到线程池的异步IO线程,线程池内部利用IOCP(IO完成端口)与硬件设备建立连接。异步IO实现过程如下:
- 托管的IO请求线程调用Win32本地代码ReadFile方法
- ReadFile方法分配IO请求包IRP并发送至Windows内核
- Windows内核把收到的IRP放入对应设备驱动程序的IRP队列中,此时IO请求线程已经可以返回托管代码
- 驱动程序处理IRP并将处理结果放入.NET线程池的IRP结果队列中
- 线程池分配IO线程处理IRP结果
小结
.NET线程池是并发编程的主要实现方式。C#中Timer
、Parallel
、Task
在内部都是利用线程池实现的异步功能,深入理解线程池在并行编程中十分重要。
C#并行编程(2):.NET线程池的更多相关文章
- Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析
目录 引出线程池 Executor框架 ThreadPoolExecutor详解 构造函数 重要的变量 线程池执行流程 任务队列workQueue 任务拒绝策略 线程池的关闭 ThreadPoolEx ...
- 【Java并发编程六】线程池
一.概述 在执行并发任务时,我们可以把任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程,只要池里有空闲的线程,任务就会分配一个线程执行.在线程池的内部,任务被插入一个阻塞队列(Blo ...
- Java并发编程:Java线程池
转载自:http://www.cnblogs.com/dolphin0520/p/3932921.html 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题 ...
- 并发编程-concurrent指南-线程池ExecutorService的实例
1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { ...
- day33_8_15 并发编程4,线程池与协程,io模型
一.线程池 线程池是一个处理线程任务的集合,他是可以接受一定量的线程任务,并创建线程,处理该任务,处理结束后不会立刻关闭池子,会继续等待提交的任务,也就是他们的进程/线程号不会改变. 当线程池中的任务 ...
- 并发编程 --进、线程池、协程、IO模型
内容目录: 1.socket服务端实现并发 2.进程池,线程池 3.协程 4.IO模型 1.socket服务端实现并发 # 客户端: import socket client = socket.soc ...
- Java并发编程 (九) 线程调度-线程池
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 声明:实际上,在开发中并不会普遍的使用Thread,因为它具有一些弊端,对并发性能的影响比较大,如下: ...
- Python并行编程(十三):进程池和mpi4py模块
1.基本概念 多进程库提供了Pool类来实现简单的多进程任务.Pool类有以下方法: - apply():直到得到结果之前一直阻塞. - apply_async():这是apply()方法的一个变体, ...
- Python并行编程(九):线程通讯queue
1.基本概念 当线程之间要共享资源或数据的时候,可能变的非常复杂.Python的threading模块提供了很多同步原语,包括信号量,条件变量,事件和锁.如果可以使用这些原语的话,应该优先考虑使用这些 ...
- Python并行编程(五):线程同步之信号量
1.基本概念 信号量是由操作系统管理的一种抽象数据类型,用于在多线程中同步对共享资源的使用.本质上说,信号量是一个内部数据,用于标明当前的共享资源可以有多少并发读取. 同样在threading中,信号 ...
随机推荐
- 下拉框combobox用法&级联餐单
如果下来内容不用后台取数据,直接写死的话不用url属性,直接用data即可: <input id="orderstate" name="orderstate&quo ...
- SpringMVC——SpringMVC简介
Spring web mvc 和Struts2 都属于表现层的框架,它是Spring 框架的一部分,我们可以从Spring 的整体结构中看得出来:
- 代码控制打电话、发短信、发邮件、打开手机app等操作
很多时候我们需要利用我门自己的app进行一些打电话.发短信等的操作,那么如何利用代码实现呢,下面就介绍一些简单的方法来实现这些操作. 一.打电话: <1>最简单.最直接的方法----直接跳 ...
- Windows Server 2008 R2 服务器系统安装图文教程
https://www.jb51.net/os/535658.html http://www.machenike.com/article.php?id=207
- python中datetime与string的相互转换
>>> import datetime >>> value = '2016-10-30 01:48:31' >>> datetime.strpti ...
- Python中的exec、eval使用实例
Python中的exec.eval使用实例 这篇文章主要介绍了Python中的exec.eval使用实例,本文以简洁的方式总结了Python中的exec.eval作用,并给出实例,需要的朋友可以参考下 ...
- 驱动开发--【字符设备、块设备简介】【sky原创】
驱动开发 字符设备,块设备,网络设备 字符设备 以字节流的方式访问, 不能随机访问 有例外,显卡.EEPROM可以随机访问 EEPROM可以擦写1亿次,是一种字符设备,可以随机访问 读写是 ...
- Python3学习笔记22-文件读写
读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操 ...
- WebRTC服务器——Licode 环境搭建
WebRTC服务器--Licode 环境搭建 系统配置 阿里云服务器 Ubuntu 14.04.5 LTS Docker 环境搭建 在一台空的机器上搭建docker环境,先要安装docker,执行下面 ...
- svn数据库自动备份脚本
创建一个存放备份数据的路径 mkdir /data/svnbak -p 采用shell脚本的方式实现自动备份 #vim backup.sh #!/bin/bash log="/data/sv ...