线程 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();

        threadDemo.RunBackgroundThread();

        {            Thread.Sleep(5000);            Thread.CurrentThread.Abort();        }

        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 };    }

private static void WriteNumberWorker(){for (int i = 0; i < 20; i++)        {            Console.WriteLine($"{DateTime.Now}=> {Thread.CurrentThread.Name} writes {i + 1}.");            Thread.Sleep(500);        }    }

public void RunForegroundThread(){this._foregroundThread?.Start();    }

public void RunBackgroundThread(){this._backgroundThread?.Start();    }}

线程池 ThreadPool

线程的创建和销毁要耗费很多时间,而且过多的线程不仅会浪费内存空间,还会导致线程上下文切换频繁,影响程序性能。为改善这些问题,.NET运行时(CLR)会为每个进程开辟一个全局唯一的线程池来管理其线程。

线程池内部维护一个操作请求队列,程序执行异步操作时,添加目标操作到线程池的请求队列;线程池代码提取记录项并派发给线程池中的一个线程;如果线程池中没有可用线程,就创建一个新线程,创建的新线程不会随任务的完成而销毁,这样就可以避免线程的频繁创建和销毁。如果线程池中大量线程长时间无所事事,空闲线程会进行自我终结以释放资源。

线程池通过保持进程中线程的少量和高效来优化程序的性能。

C#中线程池是一个静态类,维护两种线程,工作线程异步IO线程,这些线程都是后台线程。线程池不会影响进程的正常退出。

线程池的使用

线程池提供两个静态方法SetMaxThreadsSetMinThreads让我们设置线程池的最大线程数和最小线程数。最大线程数指的是,该线程池能够创建的最大线程数,当线程数达到设定值且忙碌,异步任务将进入请求队列,直到有线程空闲才会执行;最小线程数指的是,线程池优先尝试以设置数量的线程处理请求,当请求数达到一定量(未做深入研究)时,才会创建新的线程。

下面的例子展示了线程池的特性及常见使用方式。

class Program{static void Main(string[] args){

        RunCancellableWork();

        Console.ReadKey();    }

static void RunThreadPoolDemo(){        ThreadPoolDemo.ThreadPoolDemo.ShowThreadPoolInfo();        ThreadPool.SetMaxThreads(100, 100);        ThreadPool.SetMinThreads(8, 8);        ThreadPoolDemo.ThreadPoolDemo.ShowThreadPoolInfo();        ThreadPoolDemo.ThreadPoolDemo.MakeThreadPoolDoSomeWork(100);        ThreadPoolDemo.ThreadPoolDemo.MakeThreadPoolDoSomeIOWork();    }

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{

public static void ShowThreadPoolInfo(){int workThreads, completionPortThreads;

        ThreadPool.GetAvailableThreads(out workThreads, out completionPortThreads);        Console.WriteLine($"GetAvailableThreads => workThreads:{workThreads};completionPortThreads:{completionPortThreads}");

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

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");            });        }    }

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 =>            {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);        }    }

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

public static CancellationTokenSource CTSource { get; set; } = new CancellationTokenSource();

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实现过程如下:

  1. 托管的IO请求线程调用Win32本地代码ReadFile方法

  2. ReadFile方法分配IO请求包IRP并发送至Windows内核

  3. Windows内核把收到的IRP放入对应设备驱动程序的IRP队列中,此时IO请求线程已经可以返回托管代码

  4. 驱动程序处理IRP并将处理结果放入.NET线程池的IRP结果队列中

  5. 线程池分配IO线程处理IRP结果

小结

.NET线程池是并发编程的主要实现方式。C#中TimerParallelTask在内部都是利用线程池实现的异步功能,深入理解线程池在并行编程中十分重要。

原文地址:https://www.cnblogs.com/chenbaoshun/p/10566124.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

C#并行编程(2):.NET线程池相关推荐

  1. 【转】1.2异步编程:使用线程池管理线程

    从此图中我们会发现 .NET 与C# 的每个版本发布都是有一个"主题".即:C#1.0托管代码→C#2.0泛型→C#3.0LINQ→C#4.0动态语言→C#5.0异步编程.现在我为 ...

  2. 多线程编程学习笔记——线程池(二)

    接上文 多线程编程学习笔记--线程池(一) 三.线程池与并行度 此示例是学习如何应用线程池实现大量的操作,及与创建大量线程进行工作的区别. 1. 代码如下 using System; using Sy ...

  3. 多线程编程定长线程池

    多线程编程 定长线程池,可控制线程最大并发数,超出的线程会在队列中等待 Executors的方式创建定长线程池(不推荐容易,容易内存溢出OOM) ThreadPoolExecutor构造函数创建定长线 ...

  4. 异步编程:使用线程池管理线程

    开始<异步编程:使用线程池管理线程> 示例程序:异步编程:使用线程池管理线程.rar 如今的应用程序越来越复杂,我们常常需要使用<异步编程:线程概述及使用>中提到的多线程技术来 ...

  5. 线程池 c linux 编程,关于c++:linux-c编程之高效线程池如何实现无琐化

    大多数线程池实现都离不开锁的应用,如互斥量pthread_mutex*联合条件变量pthread_cond*.家喻户晓,锁的应用对于程序性能影响较大,尽管现有的pthread_mutex*在锁的申请与 ...

  6. matlab 设置最大并行数_浅析线程池参数设置

    背景 首先先明确一下线程池的主要作用是什么 线程池解决的核心问题就是资源管理问题.在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入.这种不确定性将带来以下若干问题: 频 ...

  7. 高并发编程基础(线程池基础)

    线程池简单基础介绍: Executor: Executor是Java工具类,执行提交给它的Runnable任务.该接口提供了一种基于任务运行机制的任务提交方法,包括线程使用详细信息,时序等等.Exec ...

  8. Java面试系列之并发编程专题-Java线程池灵魂拷问

    金三银四跳槽季即将来临,想必有些猿友已经蠢蠢欲动在做相关的准备了!在接下来的日子里,笔者将坚持写作.分享Java工程师在面试求职期间的方方面面,包括简历制作.面试场景复现.面试题解答.谈薪技巧 以及 ...

  9. python实现socket编程(使用线程池)

    使用线程池实现多个客户端和服务端聊天. 首先运行服务端,待服务端运行起来之后,最后运行客户端. 客户端: import os import threadingimport cv2 import soc ...

  10. 【JUC并发编程11】线程池

    文章目录 线程池 11.1 线程池概述 11.2 线程池架构 11.3 线程池使用方式 11.4 线程池底层原则 11.5 线程池的七个参数 11.6 线程池底层工作流程 11.7 自定义线程池 线程 ...

最新文章

  1. VC运行时库(/MD、/MT等)
  2. 【字符串算法1】 再谈字符串Hash(优雅的暴力)
  3. centos mysql压缩文件直接恢复_CentOS下利用mysqlbinlog恢复MySQL数据库
  4. mysql 分表例子_mysql分表查询的简单例子
  5. Highcharter绘制中国地图
  6. selenium滑块操作(基础)
  7. leetcode第12题Python版整数转罗马字符串
  8. 【货位优化】基于遗传算法实现仓库货位优化问题含Matlab源码
  9. phpstudy教程之自带ftp server使用方法详解(图文)
  10. 《操作系统真象还原》——导读
  11. SMILES学习笔记
  12. 最近3年股息率最高排名
  13. 物联网技术周报第 103 期: DIY 智能音箱:基于 Raspberry Pi + Snowboy + AVS
  14. 每日一思(2022.5.19)——前无古人后无来者
  15. SAP各种BOM详解(包含常用BAPI)
  16. not found error :\tensorflow\contrib\coder\python\ops\_coder_ops.so——_gru_ops.so——_lstm_ops.so···
  17. 机遇与挑战并存,优信二手车强势发力
  18. Gephi使用详解 实现图可视化(janusgraph)
  19. Checkmarx-简单了解记录
  20. win7摄像头软件_菜鸟记400旧手机当摄像头,网课直播设备不用愁

热门文章

  1. Zabbix server is not running
  2. Mysql安装及自动化部署脚本方案
  3. 【自定义标签开发】01-标签简介和开发第一个标签
  4. Lucifer的一场暴强围英雄表演
  5. .NET Core程序瘦身器发布,压缩程序尺寸到1/3
  6. 如何在并发中给 HttpClient 设置不同的超时时间?
  7. 如何排查 .NET 内存泄漏
  8. 基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则
  9. 常用加解密工具集合|视频图片加解密方案
  10. C# 调用动态链接库读取二代身份证信息