万字长文主讲介绍CSharp多线程、多进程并行开发

1 前言

要说多线程 我们明确几个概念

1.1 名词

1.1.1 操作系统

操作系统是计算机系统中的一个系统软件,是一些程序模块的集合

  • 它们能以尽量有效、合理的方式组合和管理计算机的软硬资源

系统效率,资源利用率 CPU利用率充足与否?I/O设备是否忙碌?

  • 合理地组织计算机的工作流程,控制程序的执行并向用户提供各种服务功能

各种软硬件资源的管理是否公平合理 如果不公平、不合理、则可能产生问题

两种角度:用户界面 与 编程接口

  • 使得用户能够灵活、方便地使用 计算机,使得整个计算机系统高效运行

进程/线程管理(CPU) 进程线程状态、控制、同步互斥、通信、调度、……

存储管理 分配/回收、地址转换、存储保护、内存扩充、……

文件管理 文件目录、文件操作、磁盘空间、文件存取控制、……

设备管理 设备驱动、分配回收、缓冲技术、……

用户接口 系统命令、编程接口

1.1.2 进程

一个操作系统中存在多个进程

       进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志,是计算机概念,程序在服务器运行时占据全部计算资源综合是虚拟的

  • 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
  • 并发性:任何进程都可以同其他进行一起并发执行;
  • 独立性:进程是系统进行资源分配和调度的一个独立单位;
  • 结构性:进程由程序,数据和进程控制块三部分组成

1.1.3 线程

       在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。

       后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。

以下图只是示例,实质上线程顺序更加混乱,不是1、2、3线程顺序

在这里插入图片描述

       总之,线程和进程都是一种抽象的概念,线程是一种比进程还小的抽象,线程和进程都可用于实现并发。

       在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位,它相当于一个进程里只有一个线程,进程本身就是线程。所以线程有时被称为轻量级进程

       后来,随着计算机的发展,对多个任务之间上下文切换的效率要求越来越高,就抽象出一个更小的概念-线程,一般一个进程会有多个(也可以是一个)线程。

1.1.4. 多核多线程

当我们要去买一台新电脑时,我们一般都会比较多台电脑的配置,而其中一项关键配置就是几核几线程。一般现在很多电脑都是4核8线程,甚至是8核16线程的。那么这里的4核8线程是什么意思呢?和cpu是什么关系呢?

开始菜单–>运行–>cmd–>输入wmic–>输入cpu get* 并将底部滚动条拖拽到如下图所示位置

在这里插入图片描述

NumberOfCores:6(核数为6)

NumberOfLogicalProcessors:12(逻辑线程数为12)

即这就是所谓的6核12线程

1.1.5 CPU 中央处理器

       CPU是英文Central Processing Unit的缩写,一般是指中央du处理器,它是一块超大规模的集成电路,是一台计算机的运算核心和控制核心。它的功能主要是解释计算机指令以及处理计算机软件中的数据。

       CPU由运算器、控制器和寄存器及实现它们之间联系的数据、控制及状态的总线构成。CPU的能力高低直接影响了整个电脑的运行速度。

1.1.6 GPU

此处将CPU和GPU的区别进行了详细的解释 https://www.zhihu.com/question/19903344

       CPU和GPU之所以大不相同,是由于其设计目标的不同,它们分别针对了两种不同的应用场景。CPU需要很强的通用性来处理各种不同的数据类型,同时又要逻辑判断又会引入大量的分支跳转和中断的处理。这些都使得CPU的内部结构异常复杂。而GPU面对的则是类型高度统一的、相互无依赖的大规模数据和不需要被打断的纯净的计算环境。

       GPU采用了数量众多的计算单元和超长的流水线,但只有非常简单的控制逻辑并省去了Cache。而CPU不仅被Cache占据了大量空间,而且还有有复杂的控制逻辑和诸多优化电路,相比之下计算能力只是CPU很小的一部分

1.1.7 任务类型:IO密集型 vs 计算密集型

是否需要开启多进程,多线程?开启多少个进程,线程?这与任务的类型有关。我们可以将任务分成IO密集型和计算密集型两种。

  • IO密集型任务:

       IO密集型任务是指任务中更多的是等待IO请求,比如磁盘IO或网络IO,这类任务的特点是cpu消耗的少,任务的大部分时间是在等待IO,因为IO的速度远低于cpu的速度。这类任务,应开启的更多数量的进程,线程,cpu的运行效率越高。一个任务阻塞时可以切换到其他就绪任务,提高cpu利用率。常见的大部分任务都是IO密集型任务,比如web应用。

  • 计算密集型:

       这类任务主要为复杂的逻辑判断和复杂的运算,需要进行大量的计算,消耗大量cpu资源。这类任务不能开启太多,否则任务切换时会占用太多cpu时间,降低cpu性能。这类任务的执行要达到cpu的最高效率,任务数不能大于cpu的核心数,这样不会将cpu时间浪费在切换任务上,而将所有时间用在计算上。

2 多进程和多线程

2.1概念

2.2.1 多任务

思考这样一个场景,你正在使用电脑一边播放音乐,一边浏览网页,同时还在聊QQ。此时电脑上至少有三个任务在“同时”运行,其实还有许多任务在后台执行只是你看不到罢了。任务数远大于cpu核心数,所以这里的多任务同时运行往往指的是并发执行,而不是并行执行。大量任务在cpu的多个内核上交替切换执行,但由于cpu执行速度太快,以至于我们感觉不到任务的切换,让人感觉所有任务都是在同时运行着。事实上,如果cpu是2核2进程的,则同一时刻只能有2个任务同时执行;如果cpu是2核4进程的,理论上同一时刻能够同时执行4个任务,实际上往往并不会达到4个任务并行执行,原因上节已经讲述在此不再赘述。

2.2.2 多进程与多线程

对于操作系统来说,一个任务就是一个进程。比如打开一个world就是一个进程,打开两个world就是两个进程,打开浏览器也是一个进程。但有些进程不单单只完成一件事,比如在浏览器上看电影时,浏览器既要播放视频又要播放音频还要播放字幕等,这样在一个进程内就要做多件事,这多个子任务实际上就是进程中的线程。

由于每个进程至少要干一件事,多以每个进程至少有一个线程。多个线程的执行方式和多进程执行方式一样,是由操作系统在多个线程间切换执行的,切换速度极快导致我们感觉多个线程是同时执行的,事实上,现代操作系统都将线程作为最小的调度单位,而将进程作为资源分配的最小单位。一个进程内的多个线程是可以并行运行在多个核上的,但这也并不是一定的,比如python的线程模型并不支持并行运行在多个核上。

2.2 抉择何时使用同步(串行)、多任务、多线程和多进程

2.2.1 多任务与多进程,多线程的关系

结合以上两节我们可以总结出同时执行多个任务的3种解决方案:

多进程模式:开启多个进程,每个进程开启一个线程,每个线程完成一件事,多个进程就完成多件事
多线程模式:开启一个进程,一个进程里开启多个线程,每个线程完成一件事,1个进程就完成多件事
多进程和多线程组合模式:开启多个进程,每个进程内开启多个线程,每个线程完成一件事,多个进程就完成多件事
以上三个解决方案中,方案三实现起来很复杂,需要解决的问题很多,使用的较少。而方案二,看似很好,一个进程就能完成多件事,那我们是不是可以将多个任务都在一个进程内的多个线程上完成呢?事实上,一个进程内不能无限制地开启很多个线程,当线程数量超过一定上限时,进程内线程间的切换,上下文环境的保存恢复需要占用大量时间,导致cpu性能下降。

2.2.2 多进程与多线程的关系及优缺点

  1. 进程与线程间的关系:

一个进程至少有一个线程,可以有多个线程,但不可有过多的线程,否则性能下降
一个进程内的多个线程可以并行运行在多核多线程的处理器上
一个线程只属于一个进程,不可能两个或多个进程拥有同一个线程
进程是操作系统资源分配的最小单位,一个进程内的多个线程共享该进程的资源
线程是操作系统调度的最小单位,即cpu分配给线程,真正在cpu上执行的是线程

  1. 多进程的优缺点

优点:每个进程间是相互独立的,一个进程的崩溃不会影响到其他进程的执行,稳定性高。

缺点:进程创建,调度等开销大。

  1. 多线程优缺点

优点:多线程直接共享进程资源,创建调度等开销小。

缺点:正是由于多线程共享进程资源导致任何一个线程挂掉都可能导致整个进程奔溃。

不管是多进程还是多线程,如果数量一多,效率就会下降,因为系统调度会占用大量cpu时间。

3 各语言在线程和进程上的体现

由于博主本身语言的主要发展方向为C#

3.1 CSharp

在C#中对于线程类的封装为Thread

3.1.1 同步异步

3.1.1.1 同步实现

以下代码应该是入门的代码了

  1. 代码
  • 使用代码直接调用的方式实现同步调用效果
private void btnSync_Click(object sender, EventArgs e)
{
    Console.WriteLine($"同步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 5; i++)
    {
        DoSomeThinging("同步方法");
    }
    Console.WriteLine($"同步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}
  1. 效果

在这里插入图片描述

  • 首先使用Action实现同步效果
private void btnActionSync_Click(object sender, EventArgs e)
{
    Console.WriteLine($"Action 同步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 5; i++)
    {
        Action<String> action = DoSomeThinging;
        action("Action 同步方法");
    }
    Console.WriteLine($"Action 同步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

private void DoSomeThinging(String strName)
{
    Console.WriteLine($"操作开始,当前为{strName}操作,所属线程为:{Thread.CurrentThread.ManagedThreadId},运行时间为{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    long lInfo = 1;
    for (int i = 0; i < 1000_000_000; i++)
    {
        lInfo+=i;
        double sum = 10;
        sum = sum + 5;
        sum = lInfo;
        sum = sum / 2 / 3 * 2.5;
    }
    Console.WriteLine($"操作完成,当前为{strName}操作,当前所属线程为:{Thread.CurrentThread.ManagedThreadId},运行时间为{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}

效果和同步方法是一致的,是换成的委托调用而已

在这里插入图片描述

3.1.1.2 异步

在C#中对异步的实现,无论如何去追根溯源本质上还是委托

以下介绍几种异步实现的代码样例

  1. Action
  • 使用Action的[BeginInvoke]实现异步效果
private void btnActionBeginInvokeAsync_Click(object sender, EventArgs e)
{
    Console.WriteLine($"action.BeginInvoke 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 5; i++)
    {
        标准写法
        //Action<String> action = new Action<string>(DoSomeThinging);
        //简写
        Action<String> action = DoSomeThinging;
        action.BeginInvoke("action.BeginInvoke 异步方法", null, null);
    }
    Console.WriteLine($"action.BeginInvoke 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

  • 使用Actio的[EndInvoke]实现异步效果

3.1.1.3 同步异步总结

相较于同步方法,我们可以看到

  1. 先输出完毕总结测试信息,最后才输出[DoSomeThinging]中的输出信息;界面是可以在计算过程中进行拖拽效果的,使界面不会出现在运行过程中出现卡死的情况。

           原因为同步方法中,所用的操作都由主线程(UI线程[在客户端程序中,所有的界面响应操作都由UI线程响应操作,响应操作人员对于界面的点击、选中等操作])响应,故无法及时响应界面的点击,造成卡死。而异步的使用是通过委托的方式将[DoSomeThinging]操作封装给线程操作,使主线程空闲从而可以及时响应操作人员对界面的操作

           可以改善用户体验,客户端开发中不会由于用户点击了一个耗时操作导致软件无法操作,Web端开发中不会由于用户点了一个需要服务器回传信息耗时长导致前端一直等待;

  1. 异步调用总体耗时较少
    观察上图中我们发现由于总体调用5次,同步调用总体耗时100秒,异步调用总耗时26秒,但是5个方法均执行完毕。

           但是没有出现总体调用次数*调用耗时=同步调用耗时,原因是因为:

  • 1.计算资源的调度也需要损耗资源
  • 2.单个计算机的计算资源是有上限的

下面两张图便是说明计算机的CPU资源是有限的。

下图为修改总调用次数为10次之后,CPU的情况
在这里插入图片描述

下图后半部分为同步调用CPU消耗情况
在这里插入图片描述

  1. 线程的启动是无序的、结束也是无序的,启动和结束的顺序是不对应的
    在这里插入图片描述

启动顺序为4、8、5、3、6,结束为8、3、6、4、5

启动是无序的不是:3、4、5、6、8
结束是无序的不是:3、4、5、6、8
启动和结束不是相互对应的,如启动为4、8、5、3、6但是结束不是4、8、5、3、6,而是
8、3、6、4、5

3.1.1.4 异步Action部分API调用方法

  1. 增加回调函数,给出执行完毕之后的信息
private void btnActionBeginInvokeAsync_Click(object sender, EventArgs e)
{
    Console.WriteLine($"action.BeginInvoke 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 5; i++)
    { 
        Action<String> action = DoSomeThinging;
        //标准写法
        //AsyncCallback asyncCallback = new AsyncCallback((ar) =>
        //{
        //    Console.WriteLine($"action.BeginInvoke 异步方法[{i}]执行完毕");

        //简写
        //});
        //AsyncCallback asyncCallback = new AsyncCallback((ar) =>
        //    Console.WriteLine($"action.BeginInvoke 异步方法[{i}]执行完毕")
        //);

        //简写
        //AsyncCallback asyncCallback = new AsyncCallback(ar =>
        //    Console.WriteLine($"action.BeginInvoke 异步方法[{i}]执行完毕")
        //);

        //简写
        AsyncCallback asyncCallback = ar => Console.WriteLine($"action.BeginInvoke 异步方法[{ar.AsyncState}]执行完毕");
        action.BeginInvoke("action.BeginInvoke 异步方法", asyncCallback, i);
    }
    Console.WriteLine($"action.BeginInvoke 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

action.BeginInvoke(“action.BeginInvoke 异步方法”, asyncCallback, i);

  • 第一个参数代表:委托函数参数
  • 第二个参数代表:委托执行完毕之后调用的通知委托
  • 第三个参数代表:通知委托中的参数

效果
在这里插入图片描述

  1. 异步多线程夯住,等待同步执行
private void btnActionBeginInvokeAsync_Click(object sender, EventArgs e)
{
    Console.WriteLine($"action.BeginInvoke 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 5; i++)
    { 
        Action<String> action = DoSomeThinging;

        AsyncCallback asyncCallback = ar => Console.WriteLine($"action.BeginInvoke 异步方法[{ar.AsyncState}]执行完毕,所属线程为:{Thread.CurrentThread.ManagedThreadId},运行时间为{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");

        IAsyncResult asyncResult = null;
        asyncResult=action.BeginInvoke("action.BeginInvoke 异步方法", asyncCallback, i);

        while (!asyncResult.IsCompleted)
        {
            Thread.Sleep(2);
        }
    }
    Console.WriteLine($"action.BeginInvoke 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

其他API

//等待执行结果
//while (!asyncResult.IsCompleted)
//{
//    Thread.Sleep(2);
//}

等待执行完成
//asyncResult.AsyncWaitHandle.WaitOne();

等待执行完成  -1代表一直等待
//asyncResult.AsyncWaitHandle.WaitOne(-1);

//等待执行 超过2毫秒不再等待,方法会执行完毕
asyncResult.AsyncWaitHandle.WaitOne(2);

在这里插入图片描述

在这里插入图片描述

  1. Action可以 那么Func也可以做到
  • 两者都是委托的二次封装
  • Action表达的是有参(参数个数最多16个)无返回值的委托、Func表达的是**参数无限制必须有返回值(参数个数最多16个)**的委托
private void btnFuncBeginInvokeAsync_Click(object sender, EventArgs e)
{
    Func<int> func = () => 22;
    IAsyncResult asyncResult= func.BeginInvoke(null, null);
    int info= func.EndInvoke(asyncResult);
    Console.WriteLine(info);
}

在这里插入图片描述

3.1.2 Thread 线程 诞生时间:.NetFramework1.0

Thread类是是控制线程的基础类,位于System.Threading命名空间下,具有4个重载的构造函数:

名称说明
Thread(ThreadStart)初始化 Thread 类的新实例。要执行的方法是无参的。
Thread(ThreadStart, Int32)初始化 Thread 类的新实例,指定线程的最大堆栈大小。
Thread(ParameterizedThreadStart)初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托。要执行的方法是有参的。
Thread(ParameterizedThreadStart, Int32)初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托,并指定线程的最大堆栈大小

线程的常用属性

属性名称说明
CurrentContext获取线程正在其中执行的当前上下文。
CurrentThread获取当前正在运行的线程。
ExecutionContext获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
IsAlive获取一个值,该值指示当前线程的执行状态。
IsBackground获取或设置一个值,该值指示某个线程是否为后台线程。
IsThreadPoolThread获取一个值,该值指示线程是否属于托管线程池。
ManagedThreadId获取当前托管线程的唯一标识符。
Name获取或设置线程的名称。
Priority获取或设置一个值,该值指示线程的调度优先级。
ThreadState获取一个值,该值包含当前线程的状态。
Start开启线程
Suspend暂停线程
Resume恢复 无法实时的去暂停或者恢复线程
Abort终结线程
Join等待线程执行完毕,重载函数代表等待时长毫秒数

3.1.2.1 ThreadStart

调用无参函数作为多线程中的执行逻辑

private void DoSomeThinging()
{
    String strName = "无参";
    Console.WriteLine($"操作开始,当前为{strName}操作,所属线程为:{Thread.CurrentThread.ManagedThreadId},运行时间为{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    long lInfo = 1;
    for (int i = 0; i < 100_000_000; i++)
    {
        lInfo += i;
        double sum = 10;
        sum = sum + 5;
        sum = lInfo;
        sum = sum / 2 / 3 * 2.5;
    }
    Console.WriteLine($"操作完成,当前为{strName}操作,当前所属线程为:{Thread.CurrentThread.ManagedThreadId},运行时间为{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}

private void btnThread_ThreadStart_Click(object sender, EventArgs e)
{
    Console.WriteLine($"Thread_ThreadStart 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 5; i++)
    {
        //创建无参的线程
        Thread thread = new Thread(new ThreadStart(DoSomeThinging));
        //调用Start方法执行线程
        thread.Start();
    }
    Console.WriteLine($"Thread_ThreadStart 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.2.2 Thread ParameterizedThreadStart

private void btnThread_ParameterizedThreadStart_Click(object sender, EventArgs e)
{
    Console.WriteLine($"Thread_ParameterizedThreadStart 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 5; i++)
    {
        //创建无参的线程
        Thread thread = new Thread(new ParameterizedThreadStart(DoSomeObjectThinging));
        //调用Start方法执行线程
        thread.Start("Thread_ParameterizedThreadStart");
    }
    Console.WriteLine($"Thread_ParameterizedThreadStart 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}


private void DoSomeObjectThinging(Object objValue)
{
    Console.WriteLine($"操作开始,当前为{objValue}操作,所属线程为:{Thread.CurrentThread.ManagedThreadId},运行时间为{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    long lInfo = 1;
    for (int i = 0; i < 100_000_000; i++)
    {
        lInfo += i;
        double sum = 10;
        sum = sum + 5;
        sum = lInfo;
        sum = sum / 2 / 3 * 2.5;
    }
    Console.WriteLine($"操作完成,当前为{objValue}操作,当前所属线程为:{Thread.CurrentThread.ManagedThreadId},运行时间为{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}

在这里插入图片描述

3.1.2.3 前台线程和后台线程

前台线程:只有所有的前台线程都结束,应用程序才能结束。默认情况下创建的线程
都是前台线程
后台线程:只要所有的前台线程结束,后台线程自动结束。通过Thread.IsBackground设置后台线程。必须在调用Start方法之前设置线程的类型,否则一旦线程运行,将无法改变其类型。

3.1.2.4 线程的优先级别

当线程之间争夺CPU时间时,CPU按照线程的优先级给予服务。高优先级的线程可以完全阻止低优先级的线程执行。.NET为线程设置了Priority属性来定义线程执行的优先级别,里面包含5个选项,其中Normal是默认值。除非系统有特殊要求,否则不应该随便设置线程的优先级别。

成员名称说明
Lowest可以将 Thread 安排在具有任何其他优先级的线程之后。
BelowNormal可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。
Normal默认选择。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前。
AboveNormal可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。
Highest可以将 Thread 安排在具有任何其他优先级的线程之前。

3.1.2.5 线程的状态

           通过ThreadState可以检测线程是处于Unstarted、Sleeping、Running 等等状态,它比 IsAlive 属性能提供更多的特定信息。

成员名称说明
Running线程已启动,它未被阻塞,并且没有挂起的 System.Threading.ThreadAbortException。
StopRequested正在请求线程停止。 这仅用于内部。
SuspendRequested正在请求线程挂起
Background线程正作为后台线程执行(相对于前台线程而言)。 此状态可以通过设置 System.Threading.Thread.IsBackground 属性来控制。
Unstarted尚未对线程调用 System.Threading.Thread.Start 方法。
Stopped线程已停止。
WaitSleepJoin线程已被阻止。 这可能是因为:调用 System.Threading.Thread.Sleep(System.Int32) 或 System.Threading.Thread.Join、请求锁定(例如通过调用System.Threading.Monitor.Enter(System.Object) 或 System.Threading.Monitor.Wait(System.Object,System.Int32,System.Boolean))或等待线程同步对象(例如System.Threading.ManualResetEvent)。
Suspended线程已挂起
AbortRequested已对线程调用了 System.Threading.Thread.Abort(System.Object) 方法,但线程尚未收到试图终止它的挂起的 System.Threading.ThreadAbortException。
Aborted线程状态包括 System.Threading.ThreadState.AbortRequested 并且该线程现在已死,但其状态尚未更改为 System.Threading.ThreadState.Stopped。

3.1.2.6 ApartmentState

成员名称说明
STASystem.Threading.Thread 将创建并进入一个单线程单元。
MTASystem.Threading.Thread 将创建并进入一个多线程单元。
Unknown尚未设置 System.Threading.Thread.ApartmentState 属性。

当你想指定工程的启动窗口的时候,你需要在该窗口类中申明一个Main()方法,并为这个方法设置[STAThread]属性。
[STAThread]是Single Thread Apartment单线程套间的意思,是一种线程模型,用在程序的入口方法上
单元是进程内部的对象共享相同的线程访问要求的逻辑容器。 在同一单元中的所有对象可以都接收来自与单元中的任何线程的调用。 .NET Framework 不使用的单元,而托管的对象是负责本身以线程安全的方式使用的所有共享的资源。

由于 COM 类使用的单元,公共语言运行时将需要创建并初始化一个单元,在 COM 互操作的情况下调用 COM 对象时。 托管的线程可以创建并输入只允许一个线程,单线程单元 (STA) 或包含一个或多个线程的多线程的单元 (MTA)。 您可以控制通过设置创建的单元类型ApartmentState 属性的值之一的线程 ApartmentState 枚举。 给定的线程只能一次初始化 COM 单元,因为在首次调用到非托管代码之后无法更改公寓类型。

COM互操作强调提醒

博主是做地理信息开发的,地理信息开发中一大分支是ArcEngine开发,在ArcEngine底层中使用的COM技术

  • 开发过程中如果使用Thread操作时,使用Thread操作时会出现效率低,如使用IFeatureCursor遍历要素时,效率比同步慢5倍左右,这时设置Thread.SetApartmentState(ApartmentState.STA);效率可以达到正常
  • 如果存在多个线程,且有COM对象被主线程持有时,会出现卡死的情况

3.1.2.7 基于Thread封装一个回调


 private void DoSomeThinging()
 {
     String strName = "无参";
     Console.WriteLine($"操作开始,当前为{strName}操作,所属线程为:{Thread.CurrentThread.ManagedThreadId},运行时间为{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
     long lInfo = 1;
     for (int i = 0; i < 100_000_000; i++)
     {
         lInfo += i;
         double sum = 10;
         sum = sum + 5;
         sum = lInfo;
         sum = sum / 2 / 3 * 2.5;
     }
     Console.WriteLine($"操作完成,当前为{strName}操作,当前所属线程为:{Thread.CurrentThread.ManagedThreadId},运行时间为{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
 }

private void btnThreadCallback_Click(object sender,EventArgs e)
{
    Console.WriteLine($"Thread_ThreadStart 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 5; i++)
    {
        ThreadStart threadStart = new ThreadStart(() =>
          {
              DoSomeThinging();
          });
        Action action =()=> Console.WriteLine($"当前{Thread.CurrentThread.ManagedThreadId}调用完成");
        ThreadCallback(threadStart, action);
    }
    Console.WriteLine($"Thread_ThreadStart 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

private void ThreadCallback(ThreadStart threadStart,Action actionCallback)
{
    ThreadStart method = new ThreadStart(() =>
      {
          threadStart.Invoke();
          actionCallback.Invoke();
      });
    new Thread(method).Start();
}

在这里插入图片描述

3.1.2.7 基于Thread封装一个回调,并获取返回值

private void btnThreadCallbackReturnT_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnThreadCallbackReturnT_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");

    Func<int> func = () =>
    {
        Thread.Sleep(5000);
        return 22;
    };

    Func<int> funcReturn = ThreadWithReturn(func);

    Console.WriteLine("正在执行....");
    Console.WriteLine("正在执行....");
    Console.WriteLine("正在执行....");
    Console.WriteLine("正在执行....");


    int intValue = funcReturn.Invoke();

    Console.WriteLine($"值为{intValue}");

    Console.WriteLine("执行完毕");


    Console.WriteLine($"btnThreadCallbackReturnT_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

private Func<T> ThreadWithReturn<T>(Func<T> func)
{
    T t = default(T);
    ThreadStart method = new ThreadStart(() =>
    {
        t = func();
    });
    Thread thread = new Thread(method);
    thread.Start();
    return new Func<T>(() =>
    {
        thread.Join();
        return t;
    });
}

在这里插入图片描述

3.1.3 ThreadPool 线程池 诞生时间:.NetFramework2.0

  1. 线程池可以看做容纳线程的容器;
  2. 一个应用程序最多只能有一个线程池;
  3. ThreadPool静态类通过QueueUserWorkItem()方法将工作函数排入线程池;
  4. 每排入一个工作函数,就相当于请求创建一个线程;
  5. 如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用,就需要一个池子,保存多个这样的对象,需要用的时候从池子中获取;用完之后不用销毁,放回池子(享元模式)这样一方面可以节约资源提升性能,另一方面可以管控线程总数量,防止滥用。
  • 线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。

  • 如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。

在这里插入图片描述

  1. 当线程池被创建后,里面会创建5个空线程
  2. 当我们想线程池中排入一个任务后,就会有一个空线程接收该任务,然后运行起来,随着我们不断向线程池中排入任务,线程池中的空线程逐一接收任务并被执行。
  3. 随着任务任务的增加,在某一时刻任务数量会超出下限,当任务请求数量超出下限时,线程池并不会立即创建新线程,而是等待大约500毫秒,这么做的目的是看看在这段时间内是否有其他线程完成来接手这个请求,这样就可以避免因创建新线程而造成的消耗。如果过这段时间内没有线程完成任务,就创建一个新线程去执行新任务。
  4. 当任务数量超过下限后,每排入一个新任务,就会创建一个新线程,这段期间,任务和线程数量都持续增加,直到线程数量达到上限值位置
  5. 当线程数量达到上限时,继续增加任务,线程数量将不再增加。比如你向线程池中排入100个任务,则只有25个进入线程池(和上限相同),另外75个线程池外排队等待。当线程池中的某个线程完成任务后,并不会被终止,而是从等待队列中选择一个任务继续执行,这样就减少了因创建和销毁线程而消耗的时间。
  6. 当排入所有的任务后,随着线程池内的任务被逐步完成,线程池外部等候的任务被逐步调入线程池,任务的数量逐步减少,但线程的数量保持恒定,始终和上限值相同。
  7. 随着任务被逐步完成,总有某一个时刻,任务数量会小于上限值,这时线程池内多余的线程会在空闲2分钟后被释放并回收相关资源。线程数目逐步减少,直到达到下限值为止。
  8. 当任务数量减少到下限值时,线程池中的线程数目保持不变,其中一部分在执行任务,另一部分处于空运行状态
  9. 当素有任务都完成后,线程池恢复初始状态,运行5个空线程。

线程池缺点线程池的性能损耗优于线程(通过共享和回收线程的方式实现),但是:

  1. 线程池不支持线程的取消、完成、失败通知等交互性操作。
  2. 线程池不支持线程执行的先后次序排序。
  3. 不能设置池化线程(线程池内的线程)的Name,会增加代码调试难度。
  4. 池化线程通常都是后台线程,优先级为ThreadPriority.Normal。
  5. 池化线程阻塞会影响性能(阻塞会使CLR错误地认为它占用了大量CPU。CLR能够检测或补偿(往池中注入更多线程),但是这可能使线程池受到后续超负荷的印象。Task解决了这个问题)。
  6. 线程池使用的是全局队列,全局队列中的线程依旧会存在竞争共享资源的情况,从而影响性能(Task解决了这个问题方案是使用本地队列)。

由上述的论述可以看除线程池提高效率的关键时一个线程完成任务后可以继续为其他任务服务,这样就可以使用有限的几个固定线程轮流为大量的任务服务,从而减少因为频繁船舰和销毁线程锁造成的消耗。

3.1.3.1 ThreadPool创建多线程并启动

private void btnThreadPool_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnThreadPool_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    ThreadPool.QueueUserWorkItem(o => this.DoSomeThinging());
    ThreadPool.QueueUserWorkItem(o => this.DoSomeThinging());
    //方式一
    {
        ThreadPool.QueueUserWorkItem(n => DoSomeThinging("Test-ok"));
    }
    //方式二
    {
        WaitCallback waitCallback = new WaitCallback(DoSomeObjectThinging);
        ThreadPool.QueueUserWorkItem(n => waitCallback("WaitCallback"));//两者效果相同 ThreadPool.QueueUserWorkItem(waitCallback,"Test-ok");
    }
    //方式三
    {
        ParameterizedThreadStart parameterizedThreadStart = new ParameterizedThreadStart(DoSomeObjectThinging);
        ThreadPool.QueueUserWorkItem(n => parameterizedThreadStart("ParameterizedThreadStart"));
    }
    //方式四
    {
        TimerCallback timerCallback = new TimerCallback(DoSomeObjectThinging);
        ThreadPool.QueueUserWorkItem(n => timerCallback("TimerCallback"));
    }
    //方式五
    {
        Action<object> action = DoSomeObjectThinging;
        ThreadPool.QueueUserWorkItem(n => DoSomeObjectThinging("Action"));
    }
    //方式六
    ThreadPool.QueueUserWorkItem((o) =>
    {
        var msg = "lambda";
        Console.WriteLine("执行方法:{0}", msg);
    });
    Console.WriteLine($"btnThreadPool_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.3.2 ThreadPool 常用API

成员名称说明
QueueUserWorkItem启动线程池里的一个线程(工作者线程)
GetMinThreads检索线程池在新请求预测中能够按需创建的线程的最小数量
SetMinThreads设置线程池最少需要保留的线程数。
GetMaxThreads最多可用线程数,所有大于此数目的请求将保持排队状态,直到线程池线程由空闲。
SetMaxThreads设置线程池中的最大线程数(请求数超过此值则进入队列)。
GetAvailableThreads剩余空闲线程数。

线程池允许线程在多个CPU内核上调度任务,使多个线程能并发工作,从而高效率的使用系统资源,提升程序的吞吐性。

CLR线程池分为工作者线程与I/O线程两种:

工作者线程(workerThreads):负责管理CLR内部对象的运作,提供”运算能力“,所以通常用于计算密集(compute-bound)性操作。

I/O线程(completionPortThreads):主要用于与外部系统交换信息(如读取一个文件)和分发IOCP中的回调。

注意:线程池会预先缓存一些工作者线程因为创建新线程的代价比较昂贵。

private void btnThreadPoolAPI_Click(object sender, EventArgs e)
{
    ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);
    Console.WriteLine($"最大工作线程数={workerThreadsMax},最大IO线程数={completionPortThreadsMax}");
    ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);
    Console.WriteLine($"最小工作线程数={workerThreadsMin},最小IO线程数={completionPortThreadsMin}");
    ThreadPool.SetMaxThreads(123, 234);
    ThreadPool.SetMinThreads(4, 5);
    ThreadPool.GetMaxThreads(out workerThreadsMax, out completionPortThreadsMax);
    Console.WriteLine($"最大工作线程数={workerThreadsMax},最大IO线程数={completionPortThreadsMax}");
    ThreadPool.GetMinThreads(out workerThreadsMin, out completionPortThreadsMin);
    Console.WriteLine($"最小工作线程数={workerThreadsMin},最小IO线程数={completionPortThreadsMin}");
    //声明变量 (工作者线程计数  Io完成端口计数)
    int workerThreadsCount, completionPortThreadsCount;
    {
        ThreadPool.GetMinThreads(out workerThreadsCount, out completionPortThreadsCount);
        Console.WriteLine("最小工作线程数:{0},最小IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
    }
    {
        ThreadPool.GetMaxThreads(out workerThreadsCount, out completionPortThreadsCount);
        Console.WriteLine("最大工作线程数:{0},最大IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
    }
    ThreadPool.QueueUserWorkItem((o) =>
    {
        Console.WriteLine("占用1个池化线程");
    });
    {
        ThreadPool.GetAvailableThreads(out workerThreadsCount, out completionPortThreadsCount);
        Console.WriteLine("剩余工作线程数:{0},剩余IO线程数{1}", workerThreadsCount, completionPortThreadsCount);
    }
}

在这里插入图片描述

  1. 线程有内存开销,所以线程池内的线程过多而没有完全利用是对内存的一种浪费,所以需要对线程池限制最小线程数量。
  2. 线程池最大线程数是线程池最多可创建线程数,实际情况是线程池内的线程数是按需创建。
  3. 委托异步调用、Task、Parrallel、async/await 全部都是线程池的函数
  4. 直接new Thread不受线程池ThreadPool的限制(但是会占用线程池的线程数量)

3.1.3.3 执行上下文

private void btnThreadPoolContext_Click(object sender, EventArgs e)
{
    Console.WriteLine("将一些数据放到主函数线程的逻辑调用上下文中前");
    //将一些数据放到主函数线程的逻辑调用上下文中
    CallContext.LogicalSetData("Action", "Jonins");
    Console.WriteLine("初始化要由另一个线程做的一些事情,线程池线程能访问逻辑上下文数据前");
    //初始化要由另一个线程做的一些事情,线程池线程能访问逻辑上下文数据
    ThreadPool.QueueUserWorkItem(state => Console.WriteLine("辅助线程A:" + Thread.CurrentThread.ManagedThreadId + ";Action={0}", CallContext.LogicalGetData("Action")));
    Console.WriteLine("阻止主线程执行上下文流动前");
    //现在阻止主线程执行上下文流动
    ExecutionContext.SuppressFlow();
    //初始化要由另一个线程做的一些事情,线程池线程能访问逻辑上下文数据
    ThreadPool.QueueUserWorkItem(state => Console.WriteLine("辅助线程B:" + Thread.CurrentThread.ManagedThreadId + ";Action={0}", CallContext.LogicalGetData("Action")));
    Console.WriteLine("恢复主线程的执行上下文流动前");
    //恢复主线程的执行上下文流动,以避免使用更多的线程池线程
    ExecutionContext.RestoreFlow();
    Console.WriteLine("恢复主线程的执行上下文流动后");
}

在这里插入图片描述

每个线程都关联了一个执行上下文数据结构,执行上下文(execution context)包括:

  1. 安全设置(压缩栈、Thread的Principal属性、winodws身份)。
  2. 宿主设置(System.Threading.HostExecutionContextManager)。
  3. 逻辑调用上下文数据(System.Runtime.Remoting.Messaging.CallContext的LogicalGetData和LogicalSetData方法)。

3.1.3.4 ThreadPool线程的等待

private void btnThreadPoolWait_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnThreadPoolWait_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    ManualResetEvent mre = new ManualResetEvent(false);
    ThreadPool.QueueUserWorkItem(o =>
    {
        this.DoSomeThinging();
        mre.Set();
    });
    mre.WaitOne();
    Console.WriteLine("任务执行完成");
    Console.WriteLine($"btnThreadPoolWait_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.4 Task

           我个人认为Thread更加强大,API更加丰富,但是成也萧何,败也萧何,其Thread中如果无管控的使用,便会造成问题,Task是对Thread的进一步封装,并将其管控到ThreadPool线程池中,Task是.NetFramework3.0之后出现的,Task线程是基于线程池的,然后提供了非常丰富的API。

3.1.4.1多线程使用

 private void btnTask_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnTask_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    {
        Task task = new Task(()=>this.DoSomeThinging("btnTask_Click1"));
        task.Start();
    }

    {
        Task.Run(() => this.DoSomeThinging("btnTask_Click2"));
    }

    {
        TaskFactory taskFactory = Task.Factory;
        Task task = taskFactory.StartNew(() => this.DoSomeThinging("btnTask_Click3"));
    }
    Console.WriteLine($"btnTask_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.4.2多线程线程管理

  • 使用Task控制线程数量为8个
private void btnTaskThreadConut_Click(object sender, EventArgs e)
{
    ThreadPool.SetMinThreads(8, 8);
    //ThreadPool.SetMinThreads(20, 20);
    Console.WriteLine($"btnTaskThreadConut_Click 线程方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 25; i++)
    {
        Task.Run(() => this.DoSomeThinging("btnTaskThreadConut_Click"));
    }
    Console.WriteLine($"btnTaskThreadConut_Click 线程方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

  • 使用Task控制线程数量为20个
private void btnTaskThreadConut_Click(object sender, EventArgs e)
{
    //ThreadPool.SetMinThreads(8, 8);
    ThreadPool.SetMinThreads(20, 20);
    Console.WriteLine($"btnTaskThreadConut_Click 线程方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 25; i++)
    {
        Task.Run(() => this.DoSomeThinging("btnTaskThreadConut_Click"));
    }
    Console.WriteLine($"btnTaskThreadConut_Click 线程方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

  • 使用Taread控制线程数量为8个
private void btnThreadConut_Click(object sender, EventArgs e)
{
    ThreadPool.SetMinThreads(8, 8);
    Console.WriteLine($"btnTaskThreadConut_Click 线程方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 20; i++)
    {
        //创建无参的线程
        Thread thread = new Thread(new ThreadStart(DoSomeThinging));
        thread.Start();
    }
    Console.WriteLine($"btnTaskThreadConut_Click 线程方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

由上述3个案例我们得到以下结论

  1. Task线程受线程池控制,且采用的是3.1.3中对于线程池的论述,采用享元模式控制线程。
  2. Thread并不受线程池个数的限制。

3.1.4.3 Task线程API说明

//
// 摘要:
//     使用指定的操作初始化新的 System.Threading.Tasks.Task。
//
// 参数:
//   action:
//     表示要在任务中执行的代码的委托。
//
// 异常:
//   T:System.ArgumentNullException:
//     action 参数为 null。
public Task(Action action);
//
// 摘要:
//     用指定的操作和 System.Threading.CancellationToken 初始化新的 System.Threading.TasksTask。
//
// 参数:
//   action:
//     表示要在任务中执行的代码的委托。
//
//   cancellationToken:
//     新任务将观察的 System.Threading.CancellationToken。
//
// 异常:
//   T:System.ObjectDisposedException:
//     提供的 System.Threading.CancellationToken 已被释放。
//
//   T:System.ArgumentNullException:
//     action 参数为 null。
public Task(Action action, CancellationToken cancellationToken);
//
// 摘要:
//     使用指定的操作和创建选项初始化新的 System.Threading.Tasks.Task。
//
// 参数:
//   action:
//     表示要在任务中执行的代码的委托。
//
//   creationOptions:
//     用于自定义任务的行为的 System.Threading.Tasks.TaskCreationOptions。
//
// 异常:
//   T:System.ArgumentNullException:
//     action 参数为 null。
//
//   T:System.ArgumentOutOfRangeException:
//     creationOptions 参数为 System.Threading.Tasks.TaskCreationOptions 指定无效值。
public Task(Action action, TaskCreationOptions creationOptions);
//
// 摘要:
//     使用指定的操作和状态初始化新的 System.Threading.Tasks.Task。
//
// 参数:
//   action:
//     表示要在任务中执行的代码的委托。
//
//   state:
//     一个表示由该操作使用的数据的对象。
//
// 异常:
//   T:System.ArgumentNullException:
//     action 参数为 null。
public Task(Action<object> action, object state);
//
// 摘要:
//     使用指定的操作和创建选项初始化新的 System.Threading.Tasks.Task。
//
// 参数:
//   action:
//     表示要在任务中执行的代码的委托。
//
//   cancellationToken:
//     新任务将观察的 System.Threading.Tasks.TaskFactory.CancellationToken。
//
//   creationOptions:
//     用于自定义任务的行为的 System.Threading.Tasks.TaskCreationOptions。
//
// 异常:
//   T:System.ObjectDisposedException:
//     创建了 cancellationToken 的 System.Threading.CancellationTokenSource 已经被释放。
//
//   T:System.ArgumentNullException:
//     参数“action”为 null。
//
//   T:System.ArgumentOutOfRangeException:
//     creationOptions 参数为 System.Threading.Tasks.TaskCreationOptions 指定无效值。
public Task(Action action, CancellationToken cancellationToken, TaskCreationOptionscreationOptions);
//
// 摘要:
//     使用指定的操作、状态和选项初始化新的 System.Threading.Tasks.Task。
//
// 参数:
//   action:
//     表示要在任务中执行的代码的委托。
//
//   state:
//     一个表示由该操作使用的数据的对象。
//
//   cancellationToken:
//     新任务将观察的 System.Threading.Tasks.TaskFactory.CancellationToken。
//
// 异常:
//   T:System.ObjectDisposedException:
//     创建了 cancellationToken 的 System.Threading.CancellationTokenSource 已经被释放。
//
//   T:System.ArgumentNullException:
//     参数“action”为 null。
public Task(Action<object> action, object state, CancellationToken cancellationToken);
//
// 摘要:
//     使用指定的操作、状态和选项初始化新的 System.Threading.Tasks.Task。
//
// 参数:
//   action:
//     表示要在任务中执行的代码的委托。
//
//   state:
//     一个表示由该操作使用的数据的对象。
//
//   creationOptions:
//     用于自定义任务的行为的 System.Threading.Tasks.TaskCreationOptions。
//
// 异常:
//   T:System.ArgumentNullException:
//     action 参数为 null。
//
//   T:System.ArgumentOutOfRangeException:
//     creationOptions 参数为 System.Threading.Tasks.TaskCreationOptions 指定无效值。
public Task(Action<object> action, object state, TaskCreationOptions creationOptions);
//
// 摘要:
//     使用指定的操作、状态和选项初始化新的 System.Threading.Tasks.Task。
//
// 参数:
//   action:
//     表示要在任务中执行的代码的委托。
//
//   state:
//     一个表示由该操作使用的数据的对象。
//
//   cancellationToken:
//     新任务将观察的 System.Threading.Tasks.TaskFactory.CancellationToken..
//
//   creationOptions:
//     用于自定义任务的行为的 System.Threading.Tasks.TaskCreationOptions。
//
// 异常:
//   T:System.ObjectDisposedException:
//     创建了 cancellationToken 的 System.Threading.CancellationTokenSource 已经被释放。
//
//   T:System.ArgumentNullException:
//     参数“action”为 null。
//
//   T:System.ArgumentOutOfRangeException:
//     creationOptions 参数为 System.Threading.Tasks.TaskCreationOptions 指定无效值。
public Task(Action<object> action, object state, CancellationToken cancellationToken,TaskCreationOptions creationOptions);
private void btnTaskResult_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnTaskResult_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");

    Task<int> task = new Task<int>(() => {
        this.DoSomeThinging("btnTask_Click1");
        return 22;
    });
    
    task.Start();

    Console.WriteLine("结果获取前");
    Console.WriteLine(task.Result);
    Console.WriteLine("结果获取后");

    Console.WriteLine($"btnTaskResult_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.4.4 Thread.Sleep(2000)和Task.Delay(2000)区别

  • Thread.Sleep(2000)同步等待
  • Task.Delay(2000)异步等待

基于下述的例子,可以得到总结,为Delay开启了一个线程进行等待。

private void btnSleepAndDelay_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnSleepAndDelay_Click 线程方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        Console.WriteLine("Sleep之前");

        Thread.Sleep(2000);

        Console.WriteLine("Sleep之后");

        stopwatch.Stop();
        Console.WriteLine(stopwatch.ElapsedMilliseconds);
    }

    Console.WriteLine("=======================================================");

    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        Console.WriteLine("Delay之前");

        Task.Delay(2000).ContinueWith(t=> {
            stopwatch.Stop();
            Console.WriteLine(stopwatch.ElapsedMilliseconds);
            Console.WriteLine("Delay等待结束");
        });

        Console.WriteLine(stopwatch.ElapsedMilliseconds);
        Console.WriteLine("Delay之后");
    }
    Console.WriteLine($"btnSleepAndDelay_Click 线程方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.4.5 Task中的等待

  1. task.Wait() 等待任务完成,也可以限时等待 阻塞线程
  2. Task.WaitAny(Task[]) 等待任务集合中的任意一个完成,也可以限时等待 阻塞线程
  3. Task.WaitAll(Task[]) 等待任务集合中全部完成,也可以限时等待 阻塞线程
  4. TaskFactory.ContinueWhenAll(Task[],Action) 等待任务都完成之后的动作Action
  5. TaskFactory.ContinueWhenAll(Task[],Action) 等待任务集合中的任意一个完成之后的动作Action
private void btnTaskContinueWhenAll_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnTask_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    List<Task> listTask = new List<Task>();
    for (int i = 0; i < 5; i++)
    {
        Task task = new Task(() => this.DoSomeThinging("btnTask_Click1"));
        listTask.Add(task);
        task.Start();
    }
    Task.Factory.ContinueWhenAny(listTask.ToArray(),
        t =>
        {
            Console.WriteLine($"当前{Thread.CurrentThread.ManagedThreadId},所有线程执行完成");
        }
        ); 
    Task.Factory.ContinueWhenAll(listTask.ToArray(),
        t =>
        {
            Console.WriteLine($"当前{Thread.CurrentThread.ManagedThreadId},所有线程执行完成");
        }
        ); 
    Console.WriteLine($"btnTask_Click 异步方法总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.4 Parallel并行计算

3.1.4.1 Parallel 并行计算

以下会使用主线程参与计算,且在Invoke时会阻塞线程

private void btnParallel_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnParallel_Click 并行计算总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    {
        Console.WriteLine($"当前{Thread.CurrentThread.ManagedThreadId},btnParallel_Click并行计算总结测试");
        Parallel.Invoke(DoSomeThinging, DoSomeThinging, DoSomeThinging, DoSomeThinging);
        Console.WriteLine("Parallel.Invoke后");
    }
    Console.WriteLine($"Parallel.Invoke=======================================");
    {
        Console.WriteLine($"当前{Thread.CurrentThread.ManagedThreadId},btnParallel_Click并行计算总结测试");
        Parallel.For(0, 5, i => DoSomeThinging());
        Console.WriteLine("Parallel.Invoke后");
    }
    Console.WriteLine($"Parallel.For.Invoke=======================================");
    {
        Console.WriteLine($"当前{Thread.CurrentThread.ManagedThreadId},btnParallel_Click并行计算总结测试");
        ParallelOptions parallelOptions = new ParallelOptions();
        parallelOptions.MaxDegreeOfParallelism = 3;
        Parallel.For(0, 5, parallelOptions, i => DoSomeThinging());
        Console.WriteLine("Parallel.Invoke后");
    }
    Console.WriteLine($"btnParallel_Click 并行计算总结测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.5 线程异常

private void btnTaskCatch_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnParallel_Click 线程异常测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 5; i++)
    {
        Task task = new Task(() => {
            throw new Exception("线程自定义异常,不捕获");
        });
        task.Start();
    }
    Task.WaitAll();
    try
    {
        List<Task> listTask = new List<Task>();
        for (int i = 0; i < 5; i++)
        {
            Task task = new Task(() => {
                throw new Exception("线程自定义异常,外部捕获");
            });
            task.Start();
            listTask.Add(task);
        }
        Task.WaitAll(listTask.ToArray());
    }
    catch (AggregateException ex)
    {
        foreach (var item in ex.InnerExceptions)
        {
            Console.WriteLine(item.Message);
        }
    }

    //建议写法
    for (int i = 0; i < 5; i++)
    {
        Task task = new Task(() => {
            try
            {
                throw new Exception("线程自定义异常,内部捕获");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        });
        task.Start();
    }
    Console.WriteLine($"btnTaskCatch_Click 线程异常测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

       推荐写法为在线程内部做try…catch异常捕获,第一是因为线程出现异常后会被框架吞掉,确实异常,如第一段代码;第二往往可能是在每个线程有不同的处理,遵循单一原则应该由线程内部处理消化异常信息;最后往往在线程使用中,异常信息有时会是一个状态值,被其他线程复用。

3.1.6 线程取消

  1. Thread.About();
  2. Task不能从外部终止任务,只能在Task自身内部才可以终止任务。
private void btnTaskCancle_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnTaskCancle_Click 线程取消测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    CancellationTokenSource cts = new CancellationTokenSource();
    try
    {
        List<Task> listTask = new List<Task>();
        for (int i = 0; i < 40; i++)
        {
            int num = i;
            listTask.Add(Task.Run(() =>
            {
                Console.WriteLine($"任务{num}开始");
                if (cts.IsCancellationRequested)
                {
                    Console.WriteLine($"任务{num}取消执行");
                }
                else
                {
                    Thread.Sleep(2000);
                    if (num == 9 || num == 19 || num == 29 || num == 39 || num == 49)
                    {
                        cts.Cancel();
                    }
                    if (cts.IsCancellationRequested)
                    {
                        Console.WriteLine($"任务{num}取消完成");
                    }
                    else
                    {
                        Console.WriteLine($"任务{num}完成");
                    }
                }
            }, cts.Token));
        }
        Task.WaitAll(listTask.ToArray());
    }
    catch (AggregateException ex)
    {
        foreach (var item in ex.InnerExceptions)
        {
            Console.WriteLine(item.Message);
        }
    }
    Console.WriteLine($"btnTaskCancle_Click 线程取消测试++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.7 线程锁

3.1.7.1 线程临时变量

private void btnTaskTemporaryVariable_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnTaskCancle_Click 线程锁,临时变量++++++++++++++++++++++++++++++++++++++++++++++++++++++++");

    List<Task> listTask = new List<Task>();
    for (int i = 0; i < 5; i++)
    {
        listTask.Add(Task.Run(() =>
        {
            Console.WriteLine($"任务{i}开始");
        }));
    }
    Task.WaitAll(listTask.ToArray());

    Console.WriteLine("==========================================================");

    listTask.Clear();
    for (int i = 0; i < 5; i++)
    {
        int num = i;
        listTask.Add(Task.Run(() =>
        {
            Console.WriteLine($"任务{num}开始");
        }));
    }
    Task.WaitAll(listTask.ToArray());

    Console.WriteLine($"btnTaskCancle_Click 线程锁,临时变量++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.7.2 线程安全

3.1.7.2.1 线程不安全代码
private void btnThreadNotSafety_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnThreadNotSafety_Click 线程锁,临时变量++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    int num = 0;
    List<Task> listTask = new List<Task>();
    for (int i = 0; i < 1000; i++)
    {
        listTask.Add(Task.Run(() =>
        {
            num++;
        }));
    }
    Task.WaitAll(listTask.ToArray());
    Console.WriteLine($"最终值为:{num}");
    Console.WriteLine($"btnThreadNotSafety_Click 线程锁,临时变量++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

3.1.7.2.2 Lock多线程锁
private static readonly object Form_Lock=new object();
private void btnLockTask_Click(object sender, EventArgs e)
{
    Console.WriteLine($"btnLockTask_Click Lock线程锁++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    int num = 0;
    List<Task> listTask = new List<Task>();
    for (int i = 0; i < 1000; i++)
    {
        listTask.Add(Task.Run(() =>
        {
            //任意线程在进入此处代码是都会成为单线程队列管道串行执行
            lock (Form_Lock)
            {
                num++;
            }

            lock(obj)
            {

            }
    //等价于
            try
            {    
                  Monitor.Enter(obj)
            }
            catch()
            {}
            finally
            {
                  Monitor.Exit(obj)
            }

        }));
    }
    Task.WaitAll(listTask.ToArray());
    Console.WriteLine($"最终值为:{num}");
    Console.WriteLine($"btnLockTask_Click Lock线程锁++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}

在这里插入图片描述

ps:以下代码不会出现死锁:lock锁住的线程是其他线程的操作,自身操作不会死锁

private int iNum=0;

public void main()
{
    DoTest();    
}

private static readonly object Form_Lock=new object();

public void DoTest()
{
    lock(Form_Lock)   //lock(this)   效果一样,也不会死锁
    {
        iNum++;
        if(iNum<10)
        {
            DoTest();
        }
        else
        {
            Console.WriteLine($"结束");
        }
    }
}

3.1.7.3 线程安全的数据结构

命名空间System.Collections.Concurrent下的所有数据结构理论上应该都是线程安全的数据结构

3.1.7.4 解决多线程安全的方法

  1. 使用锁住代码,但是代码会变为串行
  2. 将引发并发的原因拆分解决,比如写数据到文本中时,我们可以将A部分写入到A。txt,B写入到B.txt中,最后将AB合并。

3.2 python中的多线程

前面说到一个进程内的多个线程可以并行运行在多个cpu内核上,但Python程序的线程不能运行在多个内核中,所有线程都只能在一个cpu内核中交替执行。因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL(Global interpreter Lock)锁,python线程在执行前必须先获得GIL锁,而一个进程中只有唯一的一个GIL锁,导致python线程不能实现并行。即使是100个线程在100个核的cpu上执行,真正能用到的也只有一个核。python中虽然可以使用多线程,但无法利用多核的性能。
在这里插入图片描述

解决方案:

重写python的解释器,实现一个不带GIL锁的解释器
对线程并行要求较高的程序,核心代码使用C,C++或java来编写
使用多进程来替代多线程,充分利用多核的性能。多个python进程各自有自己的GIL锁,互不影响。

4 多线程和多进程

多线程,前面已经论述了一大段,其便是在一个进程空间下,解放计算机资源。那么多进程便是启动多个进程空间(Windows下称为应用程序),从而在进程上执行。

两者的优劣

  1. 多进程可以避免多线程引发的问题,如内存问题,采用多进程可以避免因为一个任务导致整体崩溃的情况
  2. 多进程可以避免多数技术问题,如上述的python多线程,ArcEngine中COM技术栈的调用引发的效率慢的问题,我们可以采用多个进程提供效率
  3. 多进程启动速度慢,进而在算子较多的情况下可能导致由于频发启动进程从而导致效率低下,如MapReduce
  4. 多进程通信困难,内存共享困难,导致在数据交互上难度相交多线程更加困难

结语

程序员使用线程池更多的是使用线程池内的工作者线程进行逻辑编码。

相对于单独操作线程(Thread),线程池(ThreadPool)能够保证计算密集作业的临时过载不会引起CPU超负荷(激活的线程数量多于CPU内核数量,系统必须按时间片执行线程调度)。

超负荷会影响性能,因为划分时间片需要大量的上下文切换开销,并且使CPU缓存失效,而这些是处理器实现高效的必要调度。

CLR能够将任务进行排序,并且控制任务启动数量,从而避免线程池超负荷。CLR首先运行与硬件内核数量一样多的并发任务,然后通过爬山算法调整并发数量,保证程序切合最优性能曲线。

多进程可以使得系统鲁棒性更高,出错的时候容易找到错误的地方,而且不影响别的进程。所以在绝大多数的自动驾驶操作系统中,会使用多进程的发案,比如ROS,比如Apollo。如果是分布式,就用多进程。

多线程可以进行快速创建和数据共享,进程创建的开销很大,上下文切换开销也稍微大一些,频繁创建进程去响应服务器的请求会造成极大的开销,而且会增加响应时间,所以在进行服务器响应的时候会采用多线程。如果是多核处理用多线程。

其实说了这么多,在Linux里面,进程和线程的实现是一样的,只是两者可以使用的资源不一样。没想到吧~ 所以选择多线程和多进程也没有绝对的,要看业务场景以及所拥有的资源。

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:代码科技 设计师:Amelia_0503 返回首页

打赏作者

自己的九又四分之三站台

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值