0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

多线程之线程池

汽车电子技术 来源:程序猿知秋 作者:程序猿知秋 2023-02-28 09:53 次阅读

图片

线程池的基本概念

什么是线程池?

  • ** .NET Framework的ThreadPool类提供一个线程池**
  • “线程池”是可以用来在后台执行多个任务的线程集合
  • 线程池通常用于服务器应用程序。 每个传入请求都将分配给线程池中的一个线程,因此可以异步处理请求,而不会占用主线程,也不会延迟后续请求的处理
  • 一旦池中的某个线程完成任务,它将返回到等待线程队列中,等待被再次使用。 这种重用使应用程序可以避免为每个任务创建新线程的开销
  • 线程池通常具有最大线程数限制。 如果所有线程都繁忙,则额外的任务将放入队列中,直到有线程可用时才能够得到处理

为什么要用线程池?

  • 线程是非常消耗资源的,如果我们每次需要子线程来执行任务,就去创建一个新的线程,那当我们执行1千次1万次甚至是100万次的时候,那么电脑的资源消耗就非常严重,甚至承受不了
  • 线程池可以避免大量的创建和销毁的开支,具有更好的性能和稳定性
  • 开发人员把线程交给系统管理,可以集中精力处理其他任务

前台线程&后台线程

  • 前台线程: 当程序运行起来后,主线程已经运行结束了,但是程序却没有停止,子线程依然在运行
  • 后台线程: 只要主线程和所有的前台线程执行结束,就算后台线程的任务还没完成,也会强行打断直接退出
  • 特点: 后台适用于不太重要的任务,即被中断了也没事的。 前台线程适用于比较重要的任务,必须等任务执行完程序才能关闭
  • ThreadPool 线程池中的线程都是 后台线程

线程池的使用

设置线程池大小

  • ThreadPool.SetMaxThreads (int workerThreads,int completionPortThreads)
  • ThreadPool.SetMinThreads (int workerThreads,int completionPortThreads)
  • 参数解析:
    • completionPortThreads:线程池中异步 I/O 线程的数目 (I/O 完成线程)
    • workerThreads:线程池中辅助线程的数目**(工作线程)**
  • 对于以上两个参数的解释,摘自网络其它博客:
    • 主要用来完成 输入和输出的工作 ,在这种情况下, 计算机需要I/O设备完成输入和输出的任务。在处理过程中, CPU是不需要参与处理过程的此时正在运行的线程将处于等待状态只有等任务完成后才会有事可做这样就造成线程资源浪费的问题。为了解决这样的问题,可以通过线程池来解决这样的问题,让线程池来管理线程

    • .NET中的一些API方法,通过APM(异步编程模式),内部实现了ThreadPool.BindHandle方法。BeginXXX方法将用户的回调委托送到某个设备驱动程序,然后返回线程池。

      当做完成后,OS会通过IOCP提醒CLR它工作已经完成,当接收到通知后,I/O线程会醒来并且运行用户的回调

    • 所以工作线程由开发人员调用, I/O线程由CLR调用 。所以通常情况下,开发者并不会直接用到它。因此可以认为, 工作者线程和I/O线程没有区别,它们都是普通的线程但是CLR线程池中区分它们的目的是为了避免线程都去处理I/O回调而被耗尽,从而引发死锁 。(设想,所有的工作者线程每一个都去等待I/O异步完成。)

    • 用来完成一些 计算的任务 ,在任务执行的过程中,需要 CPU不间断地处理 ,所以,在工作者线程的执行过程中,CPU和线程的资源是充分利用的

    • .NET中的术语工作者线程指的是任何线程而不是仅仅主线程

    • 工作者线程

    • I/O线程

启动线程任务使用: QueueUserWorkItem()方法

  • static bool QueueUserWorkItem(WaitCallback callBack):参数为一个带一个object类型参数的委托,最后返回bool值成功则返回true
class ThreadPoolTest
{
  static void Main()
  {
    Console.WriteLine("启动多线程...");
    for(int i = 0; i <10; i++)
    {
      ThreadPool.QueueUserWorkItem( p => printStr("当前线程") );
    }
    Console.WriteLine("结束多线程...");
    Console.ReadKey();
  }


  private static void printStr(string str)
  {
    Console.WriteLine("{0}是:{1}", str, Thread.CurrentThread.ManagedThreadId);
  }
}

输出结果, 可以看到有很多线程ID是重复的,这就是线程池的强大之处了

启动多线程...
结束多线程...
当前线程是:8
当前线程是:6
当前线程是:10
当前线程是:9
当前线程是:7
当前线程是:11
当前线程是:12
当前线程是:9
当前线程是:6
当前线程是:10

查看最大/最小线程数 && 设置最大/最小线程数

class ThreadPoolTest
{
  static void Main()
  {
    // 获取默认的线程池中的最大,最小线程数
    ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads);
    Console.WriteLine("最大线程数,工作线程:{0}, IO线程数:{1}", maxWorkerThreads, maxCompletionPortThreads);
    ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads);
    Console.WriteLine("最小线程数,工作线程:{0}, IO线程数:{1}", minWorkerThreads, minCompletionPortThreads);


    // 重新设置最大、最小线程数
    ThreadPool.SetMaxThreads(10, 10);
    ThreadPool.SetMinThreads(3, 3);
    // 获取默认的线程池中的最大,最小线程数
    ThreadPool.GetMaxThreads(out int newMaxWorkThread, out int newMaxIOThread);
    Console.WriteLine("重新设置后的最大线程数,工作线程:{0}, IO线程数:{1}", newMaxWorkThread, newMaxIOThread);
    ThreadPool.GetMinThreads(out int newMinWorkThread, out int newMinIOThread);
    Console.WriteLine("重新设置后的最小线程数,工作线程:{0}, IO线程数:{1}", newMinWorkThread, newMinIOThread);




    Console.WriteLine("启动多线程...");
    for(int i = 0; i <10; i++)
    {
      ThreadPool.QueueUserWorkItem( p => printStr("当前线程") );
    }
    Console.WriteLine("结束多线程...");
    Console.ReadKey();
  }


  private static void printStr(string str)
  {
    Console.WriteLine("{0}是:{1}", str, Thread.CurrentThread.ManagedThreadId);
  }
}

输出结果

最大线程数,工作线程:32767, IO线程数:1000
最小线程数,工作线程:8, IO线程数:8
重新设置后的最大线程数,工作线程:10, IO线程数:10
重新设置后的最小线程数,工作线程:3, IO线程数:3
启动多线程...
结束多线程...
当前线程是:6
当前线程是:9
当前线程是:7
当前线程是:8
当前线程是:11
当前线程是:10
当前线程是:12
当前线程是:11
当前线程是:7
当前线程是:9

ThreadPool.SetMaxThreads的默认值

  • 它取决于.NET框架版本,在2.0,3.0和4.0中进行了更改。 在2.0中它是核心数量的50倍。 在3.0(又名2.0 SP1)中,它是内核数量的250倍,4.0根据位数和操作系统资源使其动态化。 默认 Max I / O完成线程是1000
  • 一般来说,它是非常的高,一个程序永远不会接近

使用以上方法设置线程数据大小时需注意

  • 不能将最大工作线程数或 I/O 完成线程数设置为小于计算机上的处理器数的数字
  • 不能将最大工作线程数或 I/O 完成线程数设置为小于相应最小工作线程数或 I/O 完成线程数的数字
  • 默认情况下,最小线程数设置为系统上的处理器数。 可以使用该方法 SetMinThreads 增加最小线程数。 但是,不必要地增加这些值可能导致性能问题。 如果在同一时间开始太多的任务,则所有任务均可能会很慢。 在大多数情况下,线程池将使用自己的算法更好地分配线程。 将最小处理器减少到小于处理器数也会损害性能

查看系统cpu的处理数

图片编辑

**线程等待 **

由于线程池中的线程都是后台线程,当主线程及所有前台线程执行完时,后台线程就会被中断,但如果我们希望 主线程等待 线程池执行完成任务后再关闭程序 怎么办呢?

需要使用到ManualResetEvent类

图片编辑

**ManualResetEvent需要一个bool类型的参数来表示暂停和停止

**

class ThreadPoolTest2
{
  // 参数设置为false
  static ManualResetEvent manualResetEvent = new ManualResetEvent(false);

  static void Main()
  {
    Console.WriteLine("启动多线程...");
    ThreadPool.QueueUserWorkItem(p => printStr("当前线程"));
    // 等待 线程池执行任务完成
    manualResetEvent.WaitOne();
    Console.WriteLine("结束多线程...");
  }


  private static void printStr(string str)
  {
    Console.WriteLine("{0}是:{1}", str, Thread.CurrentThread.ManagedThreadId);
    // 设置为true
    manualResetEvent.Set();
  }
}

输出结果

启动多线程...
当前线程是:6
结束多线程...

ManualResetEvent类的参数值执行顺序如下:

  • false-- WaitOne等待
  • true--WaitOne通过

注:一般情况下,不要阻塞线程池中的线程,因为写代码无意间造成的线程等待没有释放,一旦线程池线程耗尽就会形成死锁

造成死锁的案例: 设置了最大线程数是9,循环创建15个线程,意味着 后6个线程必然需要利用线程池中前面用过的线程, 但是由于前面创建的线程都直接让阻塞了,没有释放,这时循环到第10个线程时由于没有空闲线程,线程池没法执行就直接跳过了,主线程会继续执行后面的循环,这样也永远不会 执行 manualResetEvent.Set() 方法,最后就成死锁了

private static void Test1()
{
  // 设置最大线程
  ThreadPool.SetMaxThreads(9, 9);
  // 设置最小线程
  ThreadPool.SetMinThreads(3, 3);
  ManualResetEvent manualResetEvent = new ManualResetEvent(false);
  for (int i = 0; i < 15; i++)
  {
    int k = i;
    ThreadPool.QueueUserWorkItem(p =>
    {
      Console.WriteLine(k);
      if (k < 10)
      {
        manualResetEvent.WaitOne();
      }
      else
      {
        // 设为true
        manualResetEvent.Set();
      }
    });
  }
  if (manualResetEvent.WaitOne())
  {
    Console.WriteLine("没有死锁。。。。。。。。。");
  }
  Console.WriteLine("结束。。。。。。。。。。");
}

输出结果

1
0
2
3
4
5
6
7
8
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 服务器
    +关注

    关注

    12

    文章

    9016

    浏览量

    85175
  • 应用程序
    +关注

    关注

    37

    文章

    3240

    浏览量

    57599
  • 线程池
    +关注

    关注

    0

    文章

    57

    浏览量

    6834
收藏 人收藏

    评论

    相关推荐

    C语言线程的实现方案

    这是一个简单小巧的C语言线程实现,在 Github 上有 1.1K 的 star,很适合用来学习 Linux 的多线程编程。
    的头像 发表于 01-29 16:43 1477次阅读

    跨平台的线程组件--TP组件

    /销毁代价是很高的。那么我们要怎么去设计多线程编程呢???答案:对于长驻的线程,我们可以创建独立的线程去执行。但是非长驻的线程,我们可以通过线程
    的头像 发表于 04-06 15:39 848次阅读

    多线程程之一: 问题提出

    多线程程之一 问题提出编写一个耗时的单线程程序:  新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮,ID为
    发表于 10-22 11:41

    多线程程之线程间通讯

    多线程程之线程间通讯七、线程间通讯  一般而言,应用程序中的一个次要线程总是为主线程执行特
    发表于 10-22 11:43

    多线程程之线程的同步

    多线程程之线程的同步八、线程的同步  虽然多线程能给我们带来好处,但是也有不少问题需要解决。例如,对于像磁盘驱动器这样独占性系统资源,
    发表于 10-22 11:43

    Delphi教程之多线程与数据库

    Delphi教程之多线程与数据库,很好的Delphi资料,快来下载学习吧。
    发表于 04-11 15:59 5次下载

    多线程好还是单线程好?单线程多线程的区别 优缺点分析

    摘要:如今单线程多线程已经得到普遍运用,那么到底多线程好还是单线程好呢?单线程多线程的区别又
    发表于 12-08 09:33 8.1w次阅读

    mfc多线程编程实例及代码,mfc多线程间通信介绍

    摘要:本文主要以MFC多线程为中心,分别对MFC多线程的实例、MFC多线程之间的通信展开的一系列研究,下面我们来看看原文。
    发表于 12-08 15:23 1.8w次阅读
    mfc<b class='flag-5'>多线程</b>编程实例及代码,mfc<b class='flag-5'>多线程</b>间通信介绍

    从CPU说起多线程以及线程

    从这篇开始将会开启高性能、高并发系列,本篇是该系列的开篇,主要关注多线程以及线程。 一切要从CPU说起 你可能会有疑问,讲多线程为什么要从CPU说起呢?原因很简单,在这里没有那些时髦
    的头像 发表于 03-02 13:48 1902次阅读

    PyQT5+OpenCV多线程协作演示

    学习多线程最典型的问题就是如何在多个线程之间传递消息与写作,PyQT5的线程支持在不同线程之间传递信号触发事件,实现多个线程之间的协助,完成
    的头像 发表于 03-08 14:58 1303次阅读

    如何用C++实现一个线程呢?

    C++线程是一种多线程管理模型,把线程分成任务执行和线程调度两部分。
    发表于 06-08 14:53 1732次阅读
    如何用C++实现一个<b class='flag-5'>线程</b><b class='flag-5'>池</b>呢?

    Spring 的线程应用

    我们在日常开发中,经常跟多线程打交道,Spring 为我们提供了一个线程方便我们开发,它就是 ThreadPoolTaskExecutor ,接下来我们就来聊聊 Spring 的线程
    的头像 发表于 10-13 10:47 594次阅读
    Spring 的<b class='flag-5'>线程</b><b class='flag-5'>池</b>应用

    线程基本概念与原理

    、17、20等的新特性,简化了多线程编程的实现。 提高性能与资源利用率 线程主要解决两个问题:线程创建与销毁的开销以及线程竞争造成的性能瓶
    的头像 发表于 11-10 10:24 484次阅读

    什么是动态线程?动态线程的简单实现思路

    因此,动态可监控线程一种针对以上痛点开发的线程管理工具。主要可实现功能有:提供对 Spring 应用内线程
    的头像 发表于 02-28 10:42 589次阅读

    java实现多线程的几种方式

    了多种实现多线程的方式,本文将详细介绍以下几种方式: 1.继承Thread类 2.实现Runnable接口 3.Callable和Future 4.线程 5.Java 8中
    的头像 发表于 03-14 16:55 575次阅读