8. C#多线程基础概念

文章目录

      • 一. 目标
      • 二. 技能介绍
        • ① 进程和线程
        • ② 为什么需要多线程
        • ③ 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语句块中,来保证线程同步和保证对象的状态的一致性

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

F5G城市光网,助力“一网通城”筑基数字中国

《淮南子》中说&#xff0c;“临河而羡鱼&#xff0c;不如归家织网”。 这句话在后世比喻为做任何事情都需要提前做好准备&#xff0c;有了合适的工具&#xff0c;牢固的基础&#xff0c;各种难题也会迎刃而解。 如今&#xff0c;数字中国发展建设如火如荼&#xff0c;各项任务…

C语言 | Leetcode C语言题解之第119题杨辉三角II

题目&#xff1a; 题解&#xff1a; int* getRow(int rowIndex, int* returnSize) {*returnSize rowIndex 1;int* row malloc(sizeof(int) * (*returnSize));row[0] 1;for (int i 1; i < rowIndex; i) {row[i] 1LL * row[i - 1] * (rowIndex - i 1) / i;}return row…

排序-快速排序

前言 本期主角 是这个小老头 图灵奖得主&#xff0c; 美国国家科学院外籍院士&#xff0c; 美国国家工程院外籍院士&#xff0c; 英国皇家工程院院士&#xff0c; 英国皇家学会院士 鼓掌&#x1f44f;&#x1f44f;&#x1f44f; 感觉这个小老头很叼噢(确实很叼) 从标…

MQTT.FX的使用

背景 在如今物联网的时代下&#xff0c;诞生了许多的物联网产品&#xff0c;这些产品通过BLE、WIFI、4G等各种各样的通信方式讲数据传输到各种各样的平台。 除了各个公司私有的云平台外&#xff0c;更多的初学者会接触到腾讯云、阿里云之类的平台。设备接入方式也有着多种多样…

react基础学习 JSX

JSX的测试网站 Babel Babel 可以测试代码的效果 JSX实现map列表 注意 key不一样&#xff08;使用遍历的时候&#xff09; 简单条件渲染 复杂条件渲染 绑定事件 function App() {const colorse (e)>{console.log("测试点击",e);}const colorse1 (name)>{…

数仓建模—指标体系指标拆解和选取

数仓建模—指标拆解和选取 第一节指标体系初识介绍了什么是指标体系 第二节指标体系分类分级和评价管理介绍了指标体系管理相关的,也就是指标体系的分级分类 这一节我们看一下指标体系的拆解和指标选取,这里我们先说指标选取,其实在整个企业的数字化建设过程中我们其实最…

vuInhub靶场实战系列-DC-6实战

免责声明 本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关。 目录 免责声明前言一、环境配置二、信息收集2.1 主机发现2.1.1 nmap扫描存活主机2.1.2 arp-scan扫描存活主机 2.2 端口扫描2.3 指纹识别2.3.1 尝试指纹识别2.3.…

2024050302-重学 Java 设计模式《实战享元模式》

重学 Java 设计模式&#xff1a;实战享元模式「基于Redis秒杀&#xff0c;提供活动与库存信息查询场景」 一、前言 程序员&#x1f468;‍&#x1f4bb;‍的上下文是什么&#xff1f; 很多时候一大部分编程开发的人员都只是关注于功能的实现&#xff0c;只要自己把这部分需求…

现代控制中可控性的Gramian判据

知乎三角猫frank对于这块内容写的非常好&#xff0c;但这个输入的构造还是很难过于没头没尾 数学好的人&#xff0c;可能看一眼根据形式就能推出gramian的构造&#xff0c;但对我这种比较钻牛角尖的人&#xff0c;我就想有一个逻辑链条——gramian是怎么被构造出来的&#xff1…

eNSP学习——配置RIPv2认证

目录 主要命令 原理概述 实验目的 实验内容 实验拓扑 实验编址 实验步骤 1、基本配置 2、搭建RIP网络 3、模拟网络攻击 4、配置RIPv2简单验证 5、配置RIPv2 MD5密文验证 需要eNSP各种配置命令的点击链接自取&#xff1a;华为&#xff45;NSP各种设备配置命令大全PD…

区块链游戏(链游)安全防御:抵御攻击的策略与实践

一、引言 区块链游戏&#xff0c;或称为链游&#xff0c;近年来随着区块链技术的普及而迅速崛起。然而&#xff0c;如同其他任何在线平台一样&#xff0c;链游也面临着各种安全威胁。本文将探讨链游可能遭遇的攻击类型以及如何通过有效的策略和技术手段进行防御。 二、链游可…

如何手动批准内核扩展 Tuxera NTFS for mac内核扩展需要批准 内核扩展怎么打开

在了解如何手动批准内核扩展之前&#xff0c;我们应该先了解什么叫做内核扩展。内核扩展又被称为KEXT&#xff0c;通过它可以实现macOS系统与软件组件之间的交互&#xff0c;例如磁盘管理、任务管理和内存管理等等。 kext 是内核扩展&#xff08;Kernel Extension&#xff09;…

[ue5]建模场景学习笔记(2)——用vectornoise降低重复率

1.问题分析&#xff1a; 利用改uv的方式降低重复率并不理想&#xff0c;在一定程度上的确能够达到降低重复率的效果&#xff0c;但远看仍然有较清晰的重复效果&#xff0c;尝试优化一下。 2.操作实现&#xff1a; 1.首先先看一下修改后的效果&#xff1a; 这是未修改前&#…

arco disign 封装数值范围组件

实现效果: 环境:vue3 arco disign vue a_input_number 实现代码: NumRange.vue <template> <span><a-input-numberv-model"minValue"style"width: 45%"v-bind"options"input"minInput"/><span:style"{…

Vue3中的常见组件通信之mitt

Vue3中的常见组件通信之mitt 概述 ​ 在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。 组件关系传递方式父传子1. props2. v-model3. $refs…

JMeter的基本使用

JMeter的基本使用三步骤&#xff1a;1.添加线程、2.添加请求、3.添加查询结果的内容 如果需要添加token请求头来验证&#xff0c;则需要再加上一步骤&#xff1a;添加请求头 1.线程 添加线程的方式 主要修改者三个属性值 Number of Threads&#xff1a;并发线程数 Ramp-up…

转转回收业务策略中心的实践

1 背景 回收业务发展日益壮大&#xff0c;我们在邮寄、上门、门店三大履约模式下的业务逻辑日益复杂。同样都是在做回收这一个业务&#xff0c;即便履约方式不同&#xff0c;也有很多业务概念是一致的。为了避免各个业务闷头造轮子&#xff0c;同时又能拉齐三端的业务标准&…

王学岗鸿蒙开发(北向)——————(二)TS基本语法详解

1&#xff0c;Ts(TypeScript)语法相当于JAVAScript类型&#xff0c;鸿蒙arkTs是基于TS语言的,当然artTs也融合了其它的语言。 2&#xff0c;本篇文章是基于n9版本。注意,有些语法是已经不能用的。 3&#xff0c; 4&#xff0c;变量:用来存储数据,数字字母组成&#xff0c;数字不…

Java线程本地变量ThreadLocal

ThreadLocal ThreadLocal有什么用 通常情况下&#xff0c;我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢&#xff1f; JDK中的ThreadLocal类正是为了解决这样的问题&#xff0c;ThreadLocal类主要解决的就是让每…

关于yolov8识别滑块关键点

1&#xff0c;images,annotations创建 IMAGES&#xff1a;放图片材料的 ANNTATIONS&#xff1a;放labelImg标记的xml文件 2&#xff0c;labels,txt怎么来的 labels &#xff1a;可以手动创建&#xff0c;里面还配置了train,val,test文件夹。可手动&#xff08;以下代码中没有写…