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

相关推荐
Escalating_xu16 分钟前
【C 语言】数据在内存中的存储:补码、大小端、整型陷阱与 IEEE 754 全解析
java·c语言·网络
灯澜忆梦36 分钟前
【面向对象编程C++】| 基础语法
java·c++·算法
禾小西40 分钟前
Redis 数据结构:快速的 Redis 有哪些慢操作?
数据结构·数据库·redis
一个风轻云淡1 小时前
GCC 和 GDB命令简单解读
java·linux·前端
省钱兄--zs1 小时前
西安24小时自助健身房解决方案实战:系统开发与部署全流程指南
java·spring boot·系统架构·intellij-idea·需求分析
数据库小学妹1 小时前
数据共享交换平台选型:交换方式对比与避坑指南
数据库·信创·数据同步·数据交换平台·政务数据共享·数据共享交换平台·数据库底座
资料库011 小时前
Linux 8个常见运维故障排查
java·linux·运维
要开心吖ZSH1 小时前
MySQL 慢 SQL 排查操作手册-个人笔记版
java·笔记·sql·mysql·慢查询
Joy T3 小时前
Spring AI 接入已有 Java 项目的三种架构设计
java·人工智能·springai·ai入门·chatclient·ai service·ai能力接入