C# 多线程

C# 多线程

基本概念

什么是线程

线程是能独立运行的最小单位,也是程序能够并发执行的一段指令序列

线程是进程的一部分,一个进程可以包含多个线程,这些线程共享进程的资源

进程有入口线程,可以创建更多的线程


为什么要多线程

重复任务希望同时进行(比如对于数组中的每个元素都进行相同且耗时的操作)

比如我处理一个数组的元素,每个都要耗时,如果我每处理一个再处理下一个,时间是n*m的

但是我如果同时进行处理,时间是m的

多个不同任务希望同时进行,互不干扰(比如有多个后台线程需要做轮询等操作)

比如我主线程有一个任务读取变换的数,然后我额外开一个后台线程去发送我读取的数

不然我读取变化的数进行发送,发送十分耗时,这将导致我读取不完全


什么是线程池

一组预先创建的线程,可以被重复使用来执行多个任务

一个线程的创建和销毁是十分耗时的,开销很大

当我们需要执行一个小任务时,直接找线程池要一个,做完再把线程还给他

避免频繁地创建和销毁线程,从而减少了线程创建和销毁的开销,提高了系统的性能和效率

异步编程默认使用线程池

通过异步编程可以更优雅的调用线程池,不需要我们自己去调用线程池的代码


什么是线程安全

线程安全

多个线程访问共享资源时,对共享资源的访问会导致数据不一致或产生不可预期的结果

csharp 复制代码
public class TODO {
    static int cnt = 0;
    const int total = 1000000;
    static void Main() {

        var thread1 = new Thread(foo);
        var thread2 = new Thread(foo);
        thread1.Start();
        thread2.Start();
        thread1.Join();
        thread2.Join();

        Console.WriteLine(cnt);

    }

    static void foo() {
        for(int i = 0; i < total; i++) {
            cnt++;
        }
    }
}

上述代码在两个进程同时访问一个数时,期待结果是total*2,但由于奇奇怪怪的原因,将会小于total

比如线程1和线程2同时拿到cnt,同时对cnt++,这将导致其中一个线程的++是被覆盖的

(汇编解释为,我取出cnt,自增然后赋值,他们同时赋值将导致本来自增两次的值只有一次生效)

简言之,同时发生导致少加一次

可以使用lock解决此问题

同步机制

用于协调和控制多个线程之间执行顺序和互斥访问共享资源

确保线程按照特定的顺序执行,避免竞态条件和数据不一致的问题

原子操作

在执行过程中不会被中断的操作。不可分割,要么完全执行,要么完全不执行,没有中间状态

在多线程环境下,原子操作能够保证数据的一致性和可靠性,避免出现竞态条件和数据竞争的问题

只需要一步就能完成的操作,不是指一行代码,而是对于底层来说,汇编啥的,也是一步就能完成

当然也可以用提供的函数来InterLocked.foo(ref elem)来实现原子操作


常用实现方式一

线程

new 一个 thread

线程池

使用thread pool 这个类型里面的方法

异步编程

async 和 await

复制代码
asynchronization n.异步化 异步,非同步化
考虑一下自带方法
csharp 复制代码
Parallel		For、ForEach、Invoke
PLINQ  			AsParallel、AsSequential、AsOrdered

即不需要那么底层的去实现



线程Thread

线程的创建

创建Thread实例,并传入ThreadStart委托

还可以配置线程,如是否为后台线程

调用Thread.Start方法,还可以传参


线程的终止

调用Thread.Join方法,等待线程的结束

意味着谁Join,我就要等谁结束了再继续别的事

简言之,用于等待一个线程结束

csharp 复制代码
public class TODO {
    static void Main() {
        var thread = new Thread((x) => {
            Console.WriteLine("Hello, {0}", x);
            for (int i = 0; i < 10; i++) {
                Thread.Sleep(500);
                Console.WriteLine("i = {0}", i);
            }
            Console.WriteLine("finished!");
        });

        Console.WriteLine("start");
        thread.Start("Bob");
        thread.Join();
        Console.WriteLine("over");

    }
}
调用Thread.Interrupt方法,中断线程的执行

会在相应线程中抛出ThreadInterruptedException

如果线程中包含一个while(true)循环

那么需要保证包含等待方法,如IO操作,Thread.Sleep等

如果没有这些,那while(true)会没有空来抛出异常

就小小的阻塞他一下 thread.Sleep(0);

不能用Abort

他会直接干掉这个线程,g

使用Abort方法来强制终止线程可能导致一些严重的问题,包括资源泄漏和不可预测的行为

较新版本的.NET中如果使用这个方法,会报PlatformNotSupportedException

推荐使用Thread.Interrupt或CancellationToken


线程的挂起与恢复

Thread. Suspend以及Thread.Resume

你的挂起可能让线程暂停在任意一种状态,这就见鬼了

较新版本的.NET中,这两个方法已经被标记为Obsolete,且调用会报错

推荐使用锁、信号量等方式实现这一逻辑



线程安全于同步机制

原子操作

interlocked


锁与信号量

自己找例子

lock & Monitor
Mutex
Semaphore
WaitHandle

ManualResetEvent
AutoResetEvent 用于生产者消费者模型

ReaderWriterLock

轻量型

SemaphoreSlim
ManualResetEventSlim
ReaderWriterLockSlim

不要自己造轮子

线程安全的单例

Lazy

线程安全的集合类型

ConcurrentBag、

ConcurrentStack、ConcurrentQueue、

ConcurrentDictionary

阻塞集合

BlockingCollection

通道

Channel

原子操作

Interlocked

周期任务

PeriodicTimer

相关推荐
Decay__2 小时前
05_MySQL 常用 SQL 大全 —— 诊断、管理、优化辅助 SQL 与三大系统库速查
运维·数据库·sql·mysql·架构
学长毕设5 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
zzu123zsw6 小时前
从臃肿的模板调用到声明式契约:深入 UniHttp 动态代理与多渠道生命周期扩展
java·spring boot·microsoft·unihttp
小徐不徐说7 小时前
C++ 接口与插件设计:从工厂注册到 Qt 动态插件
数据库·c++·qt·系统架构·程序设计
源码集结号7 小时前
云诊所门诊系统源码,基于SpringBoot+Vue.js架构,支持多租户设计与Docker容器化部署
java·spring boot·源码·医院信息系统·门诊系统·诊所系统
论文复现现场7 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
啃瓜子的松鼠8 小时前
泛微OAe9-后端二开异步调用
java
Decay__8 小时前
04_MySQL DBA 日常运维手册 —— 会话管理、锁与阻塞、事务监控、慢查询与日志巡检
运维·数据库·sql·mysql·架构
秋天的怀念_David8 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
外收内放9 小时前
Python基础语法练习题(73)
java·前端·python