8. C#多线程基础概念

news2024/11/18 20:30:31

文章目录

      • 一. 目标
      • 二. 技能介绍
        • ① 进程和线程
        • ② 为什么需要多线程
        • ③ C#实现多线程的方式
        • ④ 线程的操作(创建_终止_挂起_恢复)

一. 目标

  1. 进程和线程基本概念
  2. 为什么需要多线程?
  3. C#实现多线程的方式?
  4. 线程Thread的创建,终止,挂起和恢复?

二. 技能介绍

① 进程和线程
  • 什么是进程(Process)
  1. 一个正在运行的程序实例就是一个进程,拥有独立的内存空间和资源.
  2. 每个进程都在自己的内存空间内运行,相互之间不直接共享内存,进程间通信一般需要一些机制,比如进程间通信IPC.
  3. 每一个进程都有自己的一个主线程,而这个主线程是程序的入口点,它可以创建其他线程来执行不同的任务
  • 什么是线程(Thread)
  1. 线程是进程内的一个执行单元,也是操作系统可执行的最小单元.
  2. 一个进程中的多个线程共享进程的资源,它们之间可以共享数据.
  3. 线程在程序中是可以并行执行的.
  • 多进程

多进程指的是多个独立运行的进程,每个进程都有自己的内存控件,独立执行任务,相互之间不会收到影响.
多进程可以提高系统的并行性和稳定性,但是进程间的通信开销比较大

  • 多线程

多线程是指在同一个进程内同时执行多个线程.线程之间可以更方便地共享数据和通信,适用于需要高度协作和共享资源的任务.多线程可以提高程序的响应速度和资源利用率

② 为什么需要多线程

在软件开发中,我们可能会遇到下面这些需求:

  • 1. 图像用户界面GUI应用程序

主线程UI线程需要保持响应性,所以在执行耗时操作的的时候,要创建新的线程去操作,如果用主线程去执行耗时任务,界面将会出现卡顿,就影响了用户使用体验.

  • 2. 网络编程

在网络编程中,常常需要同时处理多个网络请求或者连接.使用多线程可以让程序更高效处理这些请求,避免阻塞主线程

  • 3. 并行计算

对于需要大量计算的任务,如数据处理,图像处理等,通过使用多线程可以充分利用多核处理器,加快任务完成速度

总结

  1. 提高效率
  2. 提高响应速度
  3. 充分利用多喝处理器
③ C#实现多线程的方式
  • 使用Thread类

语法

Thread thread = new Thread(()={});

例子

#region 1. 使用Thread创建线程

Thread thread = new Thread(() =>
{
    Console.WriteLine("我是线程1,我采用的是Thread(lambda=>{}) 匿名表达式的方式");
});
thread.Start();
// thread线程启动之后,会继续往下执行,不会阻塞线程

#endregion
  • 使用Task类

语法

Task task = Task.Run(()=> {})

例子

Task task = Task.Run(() =>
{
    Console.WriteLine("我是线程2,我采用的是Task(lambda=>{}) 匿名表达式的方式");
});
// Task.Wait()方法用于等待任务的完成,在调用该方法之后,当前现场会被阻塞,直到任务执行完成为止
task.Wait();
  • 使用ThreadPool类

语法

ThreadPool.QueueUserWorkItem((state)=>{})

例子

ThreadPool.QueueUserWorkItem((state) =>
{
    Console.WriteLine("我是线程3,我采用的是ThreadPool方式");
});
// 这种方式创建的线程是线程池创建线程,是不会阻塞主线程的,主线程会继续往下执行.
  • 使用Async/Await异步编程

例子

#region 4. 使用Async/Await

await Task.Run(() =>
{
    Console.WriteLine("我是线程4,我采用的额Async/Await的方式");
});

#endregion
  • 使用Parallel.For方法

用于并行执行一个for循环,可以在多个线程中同时处理循环中的元素,可以在单独的线程上执行for循环中的数据和后面的计算表达式

例子:

Parallel.For(0, 10, i =>
{
    Console.WriteLine($"当前数据i = {i},线程Id = {Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(1000);
});
  • 使用Parallel.ForEach方法

Parallel.ForEach方法用于遍历一个集合,在多个线程中同时处理集合中的元素

例子

var numbers = Enumerable.Range(0, 10);
Parallel.ForEach(numbers, num =>
{
    Console.WriteLine($"处理的数据 = {num},处理线程 = {Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(200);
});
  • Parallel.Invoke 方法

Parallel.Invoke 方法用于并行执行多个操作,可以在多个线程中同时执行这些操作.什么意思呢,就是可以在Parallel.Invoke方法中传递多个方法(或者是匿名方法)作为参数,然后去并行执行这些方法

#region 7. Parallel.Invoke

Parallel.Invoke(
    () => { Console.WriteLine($"函数1,线程Id = {Thread.CurrentThread.ManagedThreadId}"); },
    () => { Console.WriteLine($"函数2,线程Id = {Thread.CurrentThread.ManagedThreadId}"); },
    () => { Console.WriteLine($"函数3,线程Id = {Thread.CurrentThread.ManagedThreadId}"); },
    () => { Console.WriteLine($"函数4,线程Id = {Thread.CurrentThread.ManagedThreadId}"); },
    () => { Console.WriteLine($"函数5,线程Id = {Thread.CurrentThread.ManagedThreadId}"); },
    () => { Console.WriteLine($"函数6,线程Id = {Thread.CurrentThread.ManagedThreadId}"); }
    );

#endregion
  • PLINQ的AsParallel方法

AsParallel方法用于将LINQ查询转换为并行查询,实现并行处理查询结果

例子:

#region 8. AsParallel 方法用于将LINQ查询转换为并行查询,实现并行处理查询结果

var numbers02 = Enumerable.Range(10, 20);
var result = numbers.AsParallel().Where(num =>
{
    Console.WriteLine($"数据num: {num},所在线程ID: {Thread.CurrentThread.ManagedThreadId}");
    return num % 2 == 0;
}).ToList();

#endregion
  • PLINQ的AsSequential 和 AsOrdered 方法

AsSequential 方法用于将并行查询转换为顺序查询,以保留查询结果的顺序性.
AsOrdered 方法用不指定查询结果的顺序行,确保结果按照源数据的顺序返回.

在一开始接触这两个方法的时候,我是迷惑的,为什么一会顺序,一会又并行,他们之间到底有什么区别呢?
AsSequential() 它的意思就是将后续的操作采用顺序处理,而不是继续并行执行,什么意思呢,就是比如有一个
操作要使用AsParallel()进行并行计算,但是后续的操作又要使用顺序执行,这个时候就要使用AsSequential()了.

AsOrdered()保证并行处理的结果按照输入数据的顺序排列,并不影响操作的并行执行.而AsSequential()将后续的操作转换为按顺序执行,但是不影响之前的并行处理,仅影响后续操作的执行顺序.

var nubers03 = Enumerable.Range(0, 10);
var result02 = nubers03.AsParallel().AsSequential().Where(num =>
{
    Console.WriteLine($"数据num03: {num},所在线程ID: {Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(1000);
    return num % 2 == 0;
}).ToList();

上面这个例子在运行的时候,就是说按照顺序1秒打印一条日志,所以可以看出来在使用AsSequential()的时候是按照顺序执行的

#region 10. AsOrdered 和 AsSequential

var numbers04 = Enumerable.Range(0, 10);
var queryOrdered = numbers04.AsParallel()
    .AsOrdered()
    .Select(num => num * num)
    .Where(num =>
    {
        Thread.Sleep(1000);
        Console.WriteLine($"数据num04: {num},所在线程ID: {Thread.CurrentThread.ManagedThreadId}");
        return num % 2 == 0;
    }).ToList();
Console.WriteLine($"QueryOrderd: {string.Join(',', queryOrdered)}");
#endregion

在这里插入图片描述
然后AsOrdered()的执行结果可以看出来,它其实也是并行执行的,并且不能保证哪个数据先执行,只是它的结果是按照输入数据的顺序来进行生成的.

④ 线程的操作(创建_终止_挂起_恢复)
  • 线程的创建

1. 无参创建Thread,通过构造方法(委托)
2. 有参创建Thread,通过构造方法(有参委托)

Thread thread01 = new Thread(DoThread01);
Thread thread02 = new Thread(DoThread02);

thread01.Start();
// 有参线程传递参数的方式
thread02.Start("Hello World!");
void DoThread01()
{
    Console.WriteLine("我是无参线程1,我正在运行!");
}
void DoThread02(object? obj)
{
    Console.WriteLine($"我是有参数的线程2,我的参数是{obj ?? "Null"},我正在运行.");
}
  • 线程等待阻塞

方法Join()

Join()方法的意思就是创建Join的线程会阻塞创建线程的执行,直到Join线程执行完毕,创建线程才会继续往下执行.
假如主线程创建了线程A,然后A.Join(),意思就是A会阻塞主线程的执行,主线程会等待A线程执行完毕之后才会继续往下执行.
如果没有A.join()主线程会继续往下执行,不会阻塞

Thread thread = new Thread(() =>
{
    for (int i = 0; i < 20; i++)
    {
        Console.WriteLine($"线程Id: {Thread.CurrentThread.ManagedThreadId},执行For循环的 第 {i + 1} 次");
        Thread.Sleep(100);
    }
});
thread.Start();
thread.Join();
Console.WriteLine("主线程结束!");
  • 线程终止

Interrupt()终止线程

  1. Interrupt()方法用于中断线程的阻塞状态,引发ThreadInterruptedException异常,需要去捕获这个异常
  2. Interrrupt()方法需要终止的线程有类似IO或者是Sleep这种阻塞操作才可以,如果没有,比如写一个While(True)死循环,然后里面都是计算,这样Interrupt()被调用的时候,线程是没有事件去响应的,所以对中断的线程是有要求的
Thread thread = new Thread(() =>
{
    int startIndex = 1;
    try
    {
        while (true)
        {
            Console.WriteLine($"线程: {Thread.CurrentThread.ManagedThreadId} 正在运行,第 {startIndex++} 次.. ");
            Thread.Sleep(200);
        }
    }
    catch (ThreadInterruptedException)
    {
        Console.WriteLine($"线程: {Thread.CurrentThread.ManagedThreadId} 被终止");
    }
});
thread.Start();
Thread.Sleep(1000);
thread.Interrupt();
Console.WriteLine("主线程结束.");

现在加入我们将线程里面的Thread.Sleep(200)去除掉,那么会发现我们根本就无法终止这个线程(这里也是可以终止线程的,因为打印也是IO操作,所以我们把打印也去除掉,就来个运算将startIndex++)

Thread thread = new Thread(() =>
{
    int startIndex = 1;
    try
    {
        while (true)
        {
           	startIndex++;
        }
    }
    catch (ThreadInterruptedException)
    {
        Console.WriteLine($"线程: {Thread.CurrentThread.ManagedThreadId} 被终止");
    }
});
thread.Start();
Thread.Sleep(1000);
thread.Interrupt();
Console.WriteLine("主线程结束.");

About()终止线程

为什么不推荐使用About()来终止线程?

  1. 使用About()来终止线程可能导致一些严重的问题,可能导致线程处于不确定状态.
  2. 在新版本的.NET版本中,About()方法别调用的时候可能会引发异常
  • 线程的挂起和恢复

之前的.NET中,使用SuspentResume方法用于挂起和恢复线程,但是这两个方法已经被标记为已过时,主要原因就是这些方法可能会导致线程死锁,死活锁等问题,推荐使用Monitor类的WaitPulse方法实现线程的挂起和恢复功能.Wait用于将当前线程挂起,
Pulse方法用于唤醒被挂起的线程.这种方式更加的安全,避免线程死锁问题.

bool IsPause = false;
object lockObj = new object();
Thread thread = new Thread(() =>
{
    lock (lockObj)
    {
        int startIndex = 1;
        while (true)
        {
            if (IsPause)
            {
                Monitor.Wait(lockObj, 2000);
            }
            Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId}正在运行,index = {startIndex++}");
            Thread.Sleep(100);
        }
    }

});
thread.Start();
Thread.Sleep(1000);
IsPause = true;
Thread.Sleep(2000);
IsPause = false;
lock (lockObj)
{
    Monitor.Pulse(lockObj);
}

注意一点就是在使用Monitor.Wait()方法和Monitor.Pulse()方法的时候要在lock语句块中,来保证线程同步和保证对象的状态的一致性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1792079.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中信证券:A股下半年将迎来年度级别上涨行情的起点

中信证券认为&#xff0c; 过去3年压制A股表现的经济动能转换&#xff0c;资本市场生态&#xff0c;中美战略博弈这三大叙事都将迎来重大拐点&#xff0c;随着政策、价格、外部三类信号逐步验证&#xff0c;2024年下半年A股市场将迎来年度级别上涨行情的起点 过去3年压制A股表…

Flutter 验证码输入框

前言&#xff1a; 验证码输入框很常见&#xff1a;处理不好 bug也会比较多 想实现方法很多&#xff0c;这里列举一种完美方式&#xff0c;完美兼容 软键盘粘贴方式 效果如下&#xff1a; 之前使用 uniapp 的方式实现过一次 两种方式&#xff08;原理相同&#xff09;&#xff1…

GLM-4-9B性能究竟如何?

GLM-4-9B 开源系列模型 前言 自 2023 年 3 月 14 日 ChatGLM-6B 开源以来&#xff0c;GLM 系列模型受到广泛认可。特别是在 ChatGLM3-6B 开源后&#xff0c;针对让小模型能够拥有更为强大的能力这一目标&#xff0c;GLM 技术团队展开了诸多的探索性工作。历经将近半年的探索历程…

为什么要做与运算?网关如何和ip做与运算?

在计算机网络中&#xff0c;“与运算”是一个基本而重要的概念&#xff0c;尤其在IP地址和子网掩码的处理中起着关键作用。本文将解释为什么要进行与运算&#xff0c;以及网关如何和IP地址进行与运算。 为什么要做与运算&#xff1f; 1. 确定网络地址 与运算&#xff08;AND…

PhpSpreadsheet表格导出

个人笔记记录 使用PhpSpreadsheet 导出excel。 多重表头生成excel 表 //读取数据库public function demo1(){// 连接数据库$config Config::get(databaseedc);$db Db::connect($config);$data $db->name("xxxx")->alias(a)->field(main_header, sub_hea…

在 Win系统安装 Ubuntu20.04子系统 WSL2 (默认是C盘,第7步开始迁移到D盘,也可以不迁移)

1、简介 WSL在Windows 10上原生运行Linux二进制可执行文件&#xff0c;不用单独安装虚拟机。 WSL2是WSL的第二个版本&#xff0c;提供了与WSL相比的显著性能改进和完全的系统呼叫兼容性。通过运行Linux内核在一个轻量级虚拟机&#xff08;VM&#xff09;中实现。 2、安装 电…

SAP PP学习笔记14 - MTS(Make-to-Stock) 按库存生产(策略10),以及生产计划的概要

上面讲了SAP里面的基础知识&#xff0c;BOM&#xff0c;作业手顺&#xff08;工艺路线&#xff09;&#xff0c;作业区&#xff08;工作中心&#xff09;&#xff0c;MRP&#xff0c;MPS等概念&#xff0c;现在该到用的时候了。 SAP PP学习笔记07 - 简单BOM&#xff0c;派生BO…

将单列数据帧转换成多列数据帧

文章目录 1. 查看数据文件2. 读取数据文件得到单例数据帧3. 将单列数据帧转换成多列数据帧 在本次实战中&#xff0c;我们的目标是将存储在HDFS上的以逗号分隔的文本文件student.txt转换为结构化的Spark DataFrame。首先&#xff0c;使用spark.read.text读取文件&#xff0c;得…

金融行业数字化上云及信创改造过程中的一些问题及解决方案|合集①

Q&#xff1a;对金融机构来讲&#xff0c;什么是一切业务运行的前提&#xff1f; 金融机构的业务连续性对系统的稳定性要求极高。任何系统故障都可能导致严重的业务中断和经济损失。因此&#xff0c;金融机构需要IT基础架构能够提供高稳定性的服务&#xff0c;确保业务的连续运…

[沫忘录]MySQL InnoDB引擎

[沫忘录]MySQL InnoDB引擎 逻辑存储结构 InnoDB采用 “表、段&#xff0c;区&#xff0c; 页、行” 这样的层级结构进行存储。 **表空间(tablespace)**ibd文件&#xff0c;主要用于存储记录、索引等数据&#xff0c;一个mysql实例可有多个表空间&#xff0c;甚至能通过innodb…

.NET周刊【6月第1期 2024-06-02】

国内文章 一文带你了解.NET能做什么&#xff1f; https://www.cnblogs.com/Can-daydayup/p/18214473 .NET是一个免费、开源、跨平台的开发平台框架&#xff0c;广泛应用于桌面、Web、移动、云服务、游戏、物联网、大数据和人工智能等领域开发。它支持C#、Visual Basic、F#等…

SSL代码签名最佳实践

代码签名就是软件发布者使用全球可信的证书颁发机构CA颁发的代码签名证书对软件代码进行签名&#xff0c;由此来验证软件开发者的真实身份&#xff0c;确保软件代码的完整性和可信任性。然而&#xff0c;攻击者一直试图渗透代码签名&#xff0c;意将恶意软件嵌入可信代码中。由…

finalshell刚连上就断,这个参数你注意到了吗

在实际应用中可能一不下心弄错一个参数就会让你的finalshell刚连上就断&#xff0c;如下图所示。 1、进入ssh目录下&#xff0c;修改ssh_config文件 2、修改UseDNS no,并把前面的#去掉。 注&#xff1a;如果在ssh_config文件见不到UseDNS yes ,可以打开sshd_config,他们是在…

视频怎么压缩变小?推荐三个压缩方法

视频怎么压缩变小&#xff1f;在数字时代&#xff0c;视频已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着视频质量的提升&#xff0c;视频文件的大小也随之增加&#xff0c;这给存储和分享带来了不小的挑战。幸运的是&#xff0c;市面上有许多视频压缩软件…

Java学习笔记 集合的使用

在实际的项目中开发过程中&#xff0c;会有很多的对象&#xff0c;如何高效、方便的管理这些对象&#xff0c;是影响程序性能与可维护性的重要环节。在Java语言中为这个问题提供了一套完美的解决方案&#xff0c;也就是接下来要介绍的集合框架。 1.1 集合框架的结构 从Collect…

什么是 Batch Normalization 批标准化和全连接层

Batch Normalization 神经元在经过激活函数之后会处于饱和状态&#xff0c;无论后续怎么变化都不会再起作用。 每一层都会进行batch normalization的处理&#xff01; without normalization 会导致数据分布再饱和区 全连接层&#xff1a; 全连接层(fully connected layers&a…

DPDK基础组件一(mbuf、ring、pktmbuf_pool)

一、rte_mbuf 此部分转自:https://zhuanlan.zhihu.com/p/616314276 1.mbuf结构 mbuf是报文中的描素的结构体,是整个转发过程中最核心的数据结构之一。主要针对于mbuf的常用API与基本原理做一个简单的介绍。 mbuf:报文内存存储结构,存储在mempool中mempool:使用环形缓冲…

【kubernetes】k8s集群中的ingress(对外服务)规则详解

目录 一、Ingress 简介 1.1service的作用 1.2外部访问方案 (四种&#xff09;&#x1f339;&#x1f339;&#x1f339; 部署externalIPs 1.3Ingress 是什么 二、Ingress 组成&#x1f339;&#x1f339;&#x1f339; 三、Ingress 工作原理&#x1f431;&#x1f…

一维时间序列信号的小波时间散射变换(MATLAB 2021)

小波散射变换的目的在于获取第一层次的特征信息&#xff0c;即免疫平移、轻微形变的信息。而低通的滤波器能够获取输入信号的概貌&#xff0c;获取反映其整体大尺度特征的信息&#xff0c;以图像为例&#xff0c;由低通滤波器选取的信号对于图像的平移、伸缩、旋转等局部变化有…

【QT5】<总览二> QT信号槽、对象树及样式表

文章目录 前言 一、QT信号与槽 1. 信号槽连接模型 2. 信号槽介绍 3. 自定义信号槽 二、不使用UI文件编程 三、QT的对象树 四、添加资源文件 五、样式表的使用 六、QSS文件的使用 前言 承接【QT5】&#xff1c;总览一&#xff1e; QT环境搭建、快捷键及编程规范。若存…