首页 > 编程语言 >并发编程 ----为何要线程池化

并发编程 ----为何要线程池化

时间:2023-07-19 20:57:02浏览次数:46  
标签:ThreadPool Task BackgroundWorker ---- 池化 backgroundWorker 线程 内核

合集 - c#基础(6)   1.编码技巧 --- 如何实现字符串运算表达式的计算07-122.编码技巧 --- 同步锁对象的选定07-133.解读 --- yield 关键字07-174.并发编程 --- 信号量线程同步07-18 5.并发编程 ---为何要线程池化07-18 6.编码技巧 --- 谨防闭包陷阱07-19 收起  

引言

众所周知,使用线程可以极大的提高应用程序的效率和响应性,提高用户体验,但是不可以无节制的使用线程,为什么呢?

线程的开销

线程的开销实际上是非常大的,我们从空间开销和时间开销上分别讨论。

线程的空间开销

线程的空间开销来自这四个部分:

  1. 线程内核对象(Thread Kernel Object)。每个线程都会创建一个这样的对象,它主要包含线程上下文信息,在32位系统中,它所占用的内存在700字节左右。
  2. 线程环境块(Thread Environment Block)。TEB包括线程的异常处理链,32位系统中占用4KB内存。
  3. 用户模式栈(User Mode Stack),即线程栈。线程栈用于保存方法的参数、局部变量和返回值。每个线程栈占用1024KB的内存。要用完这些内存很简单,写一个不能结束的递归方法,让方法参数和返回值不停地消耗内存,很快就会发生 OutOfMemoryException
  4. 内核模式栈(Kernel Mode Stack)。当调用操作系统的内核模式函数时,系统会将函数参数从用户模式栈复制到内核模式栈。在32位系统中,内核模式栈会占用12KB内存。

线程的时间开销

线程的时间开销来自这三个过程:

  1. 线程创建的时候,系统相继初始化以上这些内存空间。

  2. 接着CLR会调用所有加载DLL的DLLMain方法,并传递连接标志(线程终止的时候,也会调用DLL的DLLMain方法,并传递分离标志)。

  3. 线程上下文切换。一个系统中会加载很多的进程,而一个进程又包含若干个线程。但是一个CPU内核在任何时候都只能有一个线程在执行。为了让每个线程看上去都在运行,系统会不断地切换“线程上下文”:每个线程及其短暂的执行时间片,然后就会切换到下一个线程了。

    这个线程上下文切换过程大概又分为以下5个步骤:

    • 步骤1进入内核模式。
    • 步骤2将上下文信息(主要是一些CPU寄存器信息)保存到正在执行的线程内核对象上。
    • 步骤3系统获取一个 Spinlock ,并确定下一个要执行的线程,然后释放 Spinlock 。如果下一个线程不在同一个进程内,则需要进行虚拟地址交换。
    • 步骤4从将被执行的线程内核对象上载入上下文信息。
    • 步骤5离开内核模式。

所以,由于要进行如此多的工作,所以创建和销毁一个线程就意味着代价“昂贵”,即使现在的CPU多核多线程,如无节制的使用线程,依旧会严重影响性能。

引入线程池

为了免程序员无节制地使用线程,微软开发了“线程池”技术。简单来说,线程池就是替开发人员管理工作线程。当一项工作完毕时,CLR不会销毁这个线程,而是会保留这个线程一段时间,看是否有别的工作需要这个线程。至于何时销毁或新起线程,由CLR根据自身的算法来做这个决定。

线程池技术能让我们重点关注业务的实现,而不是线程的性能测试。

微软除实现了线程池外,还需要关注一个类型:BackgroundWorkerBackgroundWorker 是在内部使用了线程池的技术:同时,在WinForm或WPF编码中,它还给工作线程和UI线程提供了交互的能力。

实际上, ThreadThreadPool 默认都没有提供这种交互能力,而 BackgroundWorker 却通过事件提供了这种能力。这种能力包括:报告进度、支持完成回调、取消任务、暂停任务等。

BackgroundWorker 的简单示例如下:

private BackgroundWorker backgroundWorker = new BackgroundWorker();

private void AsyncButton_Click(object sender, RoutedEventArgs e)
{
    //注册要执行的任务
    backgroundWorker.DoWork += BackgroundWorker_DoWork;
    //注册报告进度
    backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
    //注册完成时的回调
    backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
    //设置允许任务取消
    backgroundWorker.WorkerSupportsCancellation = true;
    //设置允许报告进度
    backgroundWorker.WorkerReportsProgress = true;
    backgroundWorker.RunWorkerAsync();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
    //取消任务
    if (backgroundWorker.IsBusy)
        backgroundWorker.CancelAsync();
}
private void BackgroundWorker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
    //完成时回调
    MessageBox.Show("BackgroundWorker RunWorkerCompleted");
}

private void BackgroundWorker_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{   
    //报告进度
    this.textbox.Text = e.ProgressPercentage.ToString();
}

private void BackgroundWorker_DoWork(object? sender, DoWorkEventArgs e)
{
    BackgroundWorker? worker = sender as BackgroundWorker;

    if (worker != null)
    {
        for (int i = 0; i < 20; i++)
        {
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                break;
            }
            worker.ReportProgress(i);

            Thread.Sleep(100);
        }
    }
}

建议使用WinForm和WPF的开发人员使用 BackgroundWorker

Task替代ThreadPool

ThreadPool 相对于 Thread 来说具有很多优势,但是 ThreadPool 在使用上却存在一定的不方便。比如:

  • ThreadPool 不支持线程的取消、完成、失败通知等交互性操作。
  • ThreadPool 不支持线程执行的先后次序。

所以随着 Task 类及其所提供的异步编程模型的引入,Task相较ThreadPool具有更多的优势。大概有一下几点:

  1. Task是.NET Framework的一部分,它提供了更高级别的抽象来表示异步操作或并发任务。相比之下,ThreadPool较为底层,需要手动管理线程池和任务队列。通过使用Task,我们可以以更简洁、更可读的方式表达并发逻辑,而无需关注底层线程管理的细节。

  2. Task是基于Task Parallel Library(TPL)构建的核心组件,它提供了强大的异步编程支持。利用Task,我们能够轻松定义异步方法、等待异步操作完成以及处理任务结果。与此相反,ThreadPool主要用于执行委托或操作,缺乏直接的异步编程功能。

  3. Task在底层使用ThreadPool来执行任务,但它提供了更优秀的性能和资源管理机制。通过使用Task,我们可以利用TPL提供的任务调度器,智能化地管理线程池的大小、工作窃取算法和任务优先级。这样一来,我们能够更有效地利用系统资源,并获得更好的性能表现。

  4. Task拥有强大的任务关联和组合功能。我们可以使用Task的 ContinueWith()When()WhenAll()Wait()等方法定义任务之间的依赖关系,以及在不同任务完成后执行的操作。这种任务组合方式使并发编程更加灵活且易于管理。

  5. Task提供了更好的异常处理和取消支持机制。我们可以利用Task的异常处理机制捕获和处理任务中的异常,而不会导致整个应用程序崩溃。此外,Task还引入 CancellationToken 的概念,可用于取消任务的执行,从而更好地控制并发操作。

所以,尽管ThreadPool在某些情况下仍然有其用途,但在C#编程中,使用Task替代ThreadPool已变为通用实践,推荐优先考虑使用Task来处理并发任务。

参考

编写高质量代码:改善C#程序的157个建议 / 陆敏技著.一北京:机械工业出版社,2011.9

 

 

出处:https://www.cnblogs.com/pandefu/p/17536277.html

标签:ThreadPool,Task,BackgroundWorker,----,池化,backgroundWorker,线程,内核
From: https://www.cnblogs.com/mq0036/p/17566706.html

相关文章

  • 函数式编程-part1概述和理解
    为什么学?能够看懂公司里的代码大数量下处理集合效率高,因为有并行流,而自己创建处理会有很多问题代码可读性高消灭嵌套地狱本系列将从Lambda表达式、Stream流、Optional、函数式接口、方法引用等顺序开始讲解//查询未成年作家的评分在70以上的书籍由于洋流影响所......
  • 属性
          ......
  • Eigen库操作
    #include<iostream>#include<eigen3/Eigen/Dense>usingnamespacestd;usingnamespaceEigen;intmain(){Matrix2fss;ss<<2.3f,3.2f,3.4f,3.1f;cout<<ss<<endl;cout<<"======="<<endl;......
  • 编码技巧 --- 谨防C#闭包陷阱
    合集-c#基础(6) 1.编码技巧---如何实现字符串运算表达式的计算07-122.编码技巧---同步锁对象的选定07-133.解读---yield关键字07-174.并发编程---信号量线程同步07-185.并发编程---为何要线程池化07-186.编码技巧---谨防闭包陷阱07-19收起 引言先......
  • P4136 谁能赢呢?
    题目描述小明和小红经常玩一个博弈游戏。给定一个n×n的棋盘,一个石头被放在棋盘的左上角。他们轮流移动石头。每一回合,选手只能把石头向上,下,左,右四个方向移动一格,并且要求移动到的格子之前不能被访问过。谁不能移动石头了就算输。假如小明先移动石头,而且两个选手都以最优策略......
  • springboot整合mybatis
    项目结构: 1.添加依赖<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://mave......
  • c语言学习7
    函数传参1、函数中定义的变量属于该函数,出了该函数就不能再被别的函数直接使用2、实参与形参之间是以赋值的方式进行传递数据的,并且是单向值传递3、return语句其实是把返回值数据放入公共区域内存中(调用者和被调用者都可以访问),调用者会从该区域获取返回值;如果不写return语句,......
  • Day07_2.深浅copy之深copy
    使用场景:如果需要拷贝一个列表,并且让两个列表的改操作完全独立开,就用深copy,否则就用浅copy深拷贝:把可变和不可变类型做了区分对待,不可变类型的指向的还是原来的值id不变,可变类型指向的是新的值id改变1.深拷贝在拷贝原列表后会产生一个新列表id不相同: 2.深拷贝可变类型数据内的......
  • 天安门
    #include<iostream>#include<string>#include<ctime>#include"minecraft.h"usingnamespacestd;TxMinecraftmc;intx,y,z;voidno0(){//初始化 x=245; y=53; z=420;}voidno1(intid,intdata){//第一层 no0(); mc.fillBlocks(x,......
  • QSqlDatabasePrivate::removeDatabase: connection ‘myConnection’ is still in use
    1.解决QSqlDatabasePrivate::removeDatabase:connection‘myConnection’isstillinuse,allquerieswillceasetowork的问题该问题主要是因为没有关闭之前的数据库连接,然后又需要创建新的数据库连接导致。解决方案:必须释放该连接的所有查询,即删除所有与该连接有关的quer......