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 这个类型里面的方法

异步编程

asyncawait

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.InterruptCancellationToken


线程的挂起与恢复

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

相关推荐
弗拉唐14 分钟前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi771 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
BestandW1shEs1 小时前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师1 小时前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球1 小时前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...1 小时前
表的操作(MySQL)
数据库·mysql·表的操作
哥谭居民00011 小时前
MySQL的权限管理机制--授权表
数据库
少说多做3431 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀1 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员