Thread 和 ThreadPool 简单梳理(C#)【并发编程系列_3】

thread,threadpool,简单,梳理,c#,并发,编程,系列 · 浏览次数 : 474

小编点评

## Thread 和 ThreadPool 性能比较 以下是代码两段的性能测试结果: **1. 使用线程** ```csharp Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100; i++) { Thread th = new Thread(() => { int count = 0; count++; }); th.Start(); } sw.Stop(); Console.WriteLine("运行创建线程所需要的时间为:{0}", sw.ElapsedMilliseconds); ``` **2. 使用 ThreadPool** ```csharp Stopwatch sw = new Stopwatch(); sw.Start(); ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc1), 100); Console.WriteLine("Press Any Key to cancel the operation\n"); Console.ReadLine(); sw.Stop(); Console.WriteLine("运行线程池所需要花费的时间:{0}", sw.ElapsedMilliseconds); ``` **3. 性能比较** ```csharp public static void Main(){ Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100; i++) { Thread th = new Thread(() => { int count = 0; count++; }); th.Start(); } sw.Stop(); Console.WriteLine("运行创建线程所需要的时间为:{0}", sw.ElapsedMilliseconds); sw.Restart(); for (int i = 0; i < 100; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc2), 100); } sw.Stop(); Console.WriteLine("运行线程池所需要花费的时间:{0}", sw.ElapsedMilliseconds); Console.ReadLine(); } private static void TaskProc1(object state) { Console.WriteLine($"TaskProc1-Thread-{Thread.CurrentThread.IsBackground}"); // ... } private static void TaskProc2(object state) { Console.WriteLine($"TaskProc2-Thread-{Thread.CurrentThread.IsBackground}"); // ... } ``` **结论:** * 使用 ThreadPool 性能更高,平均 2.5 倍。 * 这是因为 ThreadPool 使用线程池,可以并发执行任务,减少等待时间。 * 也可以通过设置线程池的最大线程数量来优化性能。

正文

〇、前言

对于 Thread 和 ThreadPool 已经是元老级别的类了。Thread 是 C# 语言对线程对象的封装,它从 .NET 1.0 版本就有了,然后 ThreadPool 是 .Net Framework 2.0 版本中出现的,都是相当成熟的存在。

当然,现在已经出现了 Task 和 PLinq 等更高效率的并发类,线程和线程池在实际开发中逐渐减少了,但是不能不知道他们的用法,因为总有需要对接的内容,别人用了你也得能看懂。

本文将结合示例,简单介绍下 Thread 和 ThreadPool。

一、Thread 类

Thread 类的功能就是,创建和控制线程,设置其优先级并获取其状态。

 下边代码简单示例说明下 Thread 的相关内容:

public static void Main()
{
    // (1)
    //var th1 = new Thread(ExecuteInForeground);
    //th1.Start();
    // (2)
    //var th2 = new Thread(ExecuteInForeground);
    //th2.IsBackground = true;
    //th2.Start();
    // (3)
    //ThreadPool.QueueUserWorkItem(ExecuteInForeground);
    Thread.Sleep(1000);
    // Console.WriteLine($"主线程 ({Thread.CurrentThread.ManagedThreadId}) 即将退出 执行 Join() 方法。。。");
    // th2.Join();
    Console.WriteLine($"主线程 ({Thread.CurrentThread.ManagedThreadId}) 即将退出。。。");
    //Console.ReadLine();
}
private static void ExecuteInForeground(object state)
{
    var sw = Stopwatch.StartNew();
    Console.WriteLine("线程 {0}: {1}, 优先级: {2}",
                        Thread.CurrentThread.ManagedThreadId,
                        Thread.CurrentThread.ThreadState,
                        Thread.CurrentThread.Priority);
    do
    {
        Console.WriteLine("线程 {0}: 计时 {1:N2} 秒",
                            Thread.CurrentThread.ManagedThreadId,
                            sw.ElapsedMilliseconds / 1000.0);
        Thread.Sleep(500);
    } while (sw.ElapsedMilliseconds <= 5000);
    sw.Stop();
}

注释部分三组线程启动的结果如下三图:

  

第 1 部分,是前台线程,必须运行完毕,主线程才会退出,所以一直运行到 5s 之前。

第 2、3 部分,均为后台线程,当主线程运行完成之时,无论是否运行完成直接中断,所以只循环了两次就退出了。

关于 Join() 方法

代码中th2.Join()如果在后台线程上执行,这结果如下图,将会等待后台线程完成后主线程才结束。

  

 二、ThreadPool 类

由于线程对象的创建时需要分配内存,GC 过程中销毁对象,然后整合零散的内存块,从而占用 CPU 资源,会影响程序性能,所以 ThreadPool 诞生了。

  • 使用线程池,可以通过向应用程序提供由系统管理的工作线程池,来更有效的使用线程。
  • 线程池可以通过重用线程、控制线程数量等操作,减少频繁创建和切换线程所带来的开销,从而提高响应速度。
  • 可直接使用线程池中空闲的线程,而不必等待线程的创建,方便管理线程。

注意,托管线程池中的线程是后台线程,其 IsBackground 属性为 true。

1、ThreadPool 的几个属性值

  • CompletedWorkItemCount:获取迄今为止已处理的工作项数。
  • PendingWorkItemCount:获取当前已加入处理队列的工作项数。
  • ThreadCount:获取当前存在的线程池线程数。

下面是一个关于线程池的几个属性值,以及开启新的后台线程并传入参数的实例:

//存放要计算的数值的字段
public static double num1 = -1;
public static double num2 = -1;
static void Main(string[] args)
{
    int workerThreads, completionPortThreads;
    // public static void GetMaxThreads (out int workerThreads, out int completionPortThreads);
    ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
    Console.WriteLine($"线程池中辅助线程的最大数目:{workerThreads}");
    Console.WriteLine($"线程池中异步 I/O 线程的最大数目:{completionPortThreads}");
    Console.WriteLine();
    // public static void GetMinThreads(out int workerThreads, out int completionPortThreads);
    ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
    Console.WriteLine($"线程池根据需要创建的最少数量的辅助线程:{workerThreads}");
    Console.WriteLine($"线程池根据需要创建的最少数量的异步 I/O 线程:{completionPortThreads}");
    Console.WriteLine();
    ThreadPool.SetMaxThreads(100, 15); // set 的值必须是 Min~Max 之间的值,否则会设置不成功
    ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
    Console.WriteLine($"set 线程池中辅助线程的最大数目:{workerThreads}");
    Console.WriteLine($"set 线程池中异步 I/O 线程的最大数目:{completionPortThreads}");
    Console.WriteLine();

    // 命名参数 传入后台线程
    int num = 2;
    // 启动第一个任务:计算x的8次方
    Console.WriteLine("启动第一个任务:计算{0}的8次方.", num);
    ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc1), num);
    // 启动第二个任务
    Console.WriteLine("启动第二个任务:计算{0}的8次方", num);
    ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc2), num);
    // 等待两个数值等完成计算
    while (num1 == -1 || num2 == -1) ;
    //打印计算结果
    Console.WriteLine($"{num} 的 8 次方为 {num1} {num2}");
    Console.ReadLine();
}
private static void TaskProc2(object state)
{
    Console.WriteLine($"TaskProc2-Thread-{Thread.CurrentThread.IsBackground}");
    num1 = Math.Pow(Convert.ToDouble(state), 8);
}
private static void TaskProc1(object state)
{
    num2 = Math.Pow(Convert.ToDouble(state), 8);
}

 输出结果:

  

 2、由线程池生成一个可以取消的后台线程

 如下代码,在没有单击回车键之前,程序会一直打印递增数字,当收到回车指令后,cts.Cancel();被执行,后台线程就取消成功了。

static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource();
    ThreadPool.QueueUserWorkItem(t => Counts(cts.Token, 1000));
    Console.WriteLine("Press Any Key to cancel the operation");
    Console.ReadLine();
    cts.Cancel();
    Console.ReadLine();
}
private static void Counts(CancellationToken token, int CountTo)
{
    for (int count = 0; count < CountTo; count++)
    {
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("Count is cancelled");
            break;
        }
        Console.WriteLine(count);
        Thread.Sleep(200);
    }
    Console.WriteLine("Count is stopped");
}

 结果如下图:

  

 三、Thread 和 ThreadPool 性能比较

如下代码,分别执行 100 次,看最终需要的时间成本:

public static void Main()
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 100; i++)
    {
        Thread th = new Thread(() =>
        {
            int count = 0;
            count++;
        });
        th.Start();
    }
    sw.Stop();
    Console.WriteLine("运行创建线程所需要的时间为:" + sw.ElapsedMilliseconds);
    sw.Restart();
    for (int i = 0; i < 100; i++)
    {
        ThreadPool.QueueUserWorkItem(t =>
        {
            int count = 0;
            count++;
        });
    }
    sw.Stop();
    Console.WriteLine("运行线程池所需要花费的时间:" + sw.ElapsedMilliseconds);
    Console.ReadLine();
}

如下图,明显线程池性能更佳:

  

参考:https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.threadpool?view=net-7.0  

https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.thread?view=net-7.0  

C#(ThreadPool)线程池的详解及使用范例.NET(C#) ThreadPool线程池的使用总结

与Thread 和 ThreadPool 简单梳理(C#)【并发编程系列_3】相似的内容:

Thread 和 ThreadPool 简单梳理(C#)【并发编程系列_3】

现在已经出现了 Task 和 PLinq 等更高效率的并发类,线程和线程池在实际开发中逐渐减少了,但是别人用了你也得能看懂,所以本文简单梳理一下。

8000字详解Thread Pool Executor

摘要:Java是如何实现和管理线程池的? 本文分享自华为云社区《JUC线程池: ThreadPoolExecutor详解》,作者:龙哥手记 。 带着大厂的面试问题去理解 提示 请带着这些问题继续后文,会很大程度上帮助你更好的理解相关知识点。@pdai 为什么要有线程池? Java是实现和管理线程池有

[转帖]实例分析 Scheduled Thread Pool Executor 与 Timer 的区别

https://my.oschina.net/u/4526289/blog/5584251 摘要:JDK 1.5 开始提供 Scheduled Thread PoolExecutor 类,Scheduled Thread Pool Executor 类继承 Thread Pool Executor

CUDA C编程权威指南:2.1-CUDA编程模型

本文主要通过例子介绍了CUDA异构编程模型,需要说明的是Grid、Block和Thread都是逻辑结构,不是物理结构。实现例子代码参考文献[2],只需要把相应章节对应的CMakeLists.txt文件拷贝到CMake项目根目录下面即可运行。 1.Grid、Block和Thread间的关系 GPU中最

[转帖]【技术剖析】8. 相同版本 JVM 和 Java 应用,在 x86 和AArch64 平台性能相差30%,何故?

https://bbs.huaweicloud.com/forum/thread-168532-1-1.html 作者: 吴言 > 编者按:目前许多公司同时使用 x86 和 AArch64 2 种主流的服务器。这两种环境的算力相当,内存相同的情况下:相同版本的 JVM 和 Java 应用,相同的 J

[转帖]【技术剖析】9. 使用 NMT 和 pmap 解决 JVM 资源泄漏问题

https://bbs.huaweicloud.com/forum/thread-168749-1-1.html 作者:宋尧飞 > 编者按:笔者使用 JDK 自带的内存跟踪工具 NMT 和 Linux 自带的 pmap 解决了一个非常典型的资源泄漏问题。这个资源泄漏是由于 Java 程序员不正确地使

一文讲尽Thread类的源码精髓

摘要:今天,我们就一起来简单看看Thread类的源码。 本文分享自华为云社区《【高并发】Thread类的源码精髓》,作者:冰 河。 前言 最近和一个朋友聊天,他跟我说起了他去XXX公司面试的情况,面试官的一个问题把他打懵了!竟然问他:你经常使用Thread创建线程,那你看过Thread类的源码吗?我

.NET 8 的 green thread 异步模型被搁置了

.NET 平台上的green thread 异步模型实验结果最近出来了,具体参见:https://github.com/dotnet/runtimelab/issues/2398 ,实验结果总结一下就是在.NET和 ASP.NET Core中实现Green Thread是可行的。Green Thre

[转帖]【技术剖析】2. JVM锁bug导致G1 GC挂起问题分析和解决

https://bbs.huaweicloud.com/forum/thread-144146-1-1.html 发表于 2021-07-29 20:07:087037查看 作者:宋尧飞 编者按:笔者在AArch64中遇到一个G1 GC挂起,CPU利用率高达300%的案例。经过分析发现问题是由JVM

[转帖]混合部署拓扑

https://docs.pingcap.com/zh/tidb/stable/tune-tikv-thread-performance 本文介绍 TiDB 集群的 TiKV 和 TiDB 混合部署拓扑以及主要参数。常见的场景为,部署机为多路 CPU 处理器,内存也充足,为提高物理机资源利用率,可单