.Net的并行与异步

什么是并行?

通过计算机多CPU内核让多个进程或多个线程可以同步执行的操作就是并行操作。

理解进程,线程的概念

进程:进程是资源分配的单位,每个进程是一个应用程序的实例。每个进程是相互独立的,有自己的内存空间,一个进程可以有多个线程,并且它们共享进程分配到的资源。进程之间的通信(IPC)。一个进程挂掉不会使其他进程受到干扰,而一个前台线程挂掉会使进程也挂掉。
线程:每个线程具有计划优先级并维护系统用于保存线程执行暂停时线程上下文的一组结构。 线程上下文包含线程顺畅继续执行所需的全部信息,包括线程的一组 CPU 寄存器和堆栈。线程是CPU在进程内切换的单位,每个CPU核在同一时刻,只能执行一个线程。一个线程如果受到阻塞,并不会影响其他线程的执行。由于CPU有时间分片的概念,所以在多线程执行时,可以极大的条运行效率。CPU的切换保存了线程的上下文。
对于单CPU多核,进程是并发执行,线程是并行执行。
对于单CPU单核,进程是并发执行,线程也是并发执行。
对于多CPU多核,进程是并行执行,线程也是并行执行。
每个CPU调度进程中的线程,在自己的核心上执行对应线程。
非抢占调度算法:进程一直执行直至被阻塞或自动释放CPU。
抢占式调度算法:(unix)让进程占用CPU某个时间的最大值时,当到达最大时间时还未完成就会被挂起,执行下一个时间分片。(windows)让进程独占CPU执行,如果存在优先级更高的进程则挂起当前调度,切换进程,内部会在线程执行完之后或者被挂起,重新计算线程优先级。也是时间分片的形式(2ms),Thread.Sleep(0)中断线程占用cpu。

所以我们为了更好的让CPU核心都不空闲(并发),多线程是个不错的选择。
默认情况下,.NET 程序由单个线程(通常称为主线程)启动。 但是,它可以创建其他线程,以与主线程并行或同时执行代码。 这些线程通常称为工作线程。

.Net中的多线程

System.Threading和System.Threading.Tasks
平常使用线程的方式:Thread和Task

  • Thread常用用法和知识点
  1. Thread只能接受一个object参数或无参,并且返回值是void类型的函数。
  2. Thread.Start()开启一个线程。
  3. Thread.IsBackground开启一个后台线程,即主线程结束,后台线程也会结束。
  4. Thread默认创建的是前台线程,主线程结束时,必须等待前台线程也运行结束,当前进程才会结束。
  5. Thread.Join()将一个线程加入到某个调用线程中,让其等待完成,再继续执行调用线程。
  6. Thread.Abort()终止一个线程。
  7. Thread.Interrupt()终止处于休眠状态(WaitSleepJoin)的线程。
  • ThreadPool知识:
  1. 每个进程都有一个线程池,可以通过GetMaxThread和SetMaxThread读取和修改线程池线程数大小。
  2. ThreadPool会自己调整内部的线程数,来达到资源的有效利用。
  3. System.Threading.Tasks和PLinq默认都是用ThreadPool。
  4. 线程池为每个进程提供一个全局的FIFO(先进先出)的工作队列,每次调用QueueUserWorkItem时则添加任务到此队列,内部通过ConcurrentQueue类实现。顶级任务放在全局队列里面,而在任务上下文中创建的嵌套任务或子任务被放置在线程的本地队列中,线程执行任务时,优先访问本地队列LIFO(后进先出),当本地队列执行完成后,则去全局队列中查看,然后再查看其他线程的本地队列,如果找到可执行任务,则从尾部以FIFO(先进先出)的顺序执行,保证本地队列的数据位置。
  • Task常用用法和知识点
  1. Task.Factory.StartNew()支持有返回值或无返回值的函数,并且参数是object类型或无参。
  2. Task.Run支持无参的有返回值或无返回值的函数。
  3. Task.Factory.StartNew(前者)和Task.Run(后者)区别:后者是简化版的前者,它们两种都可以创建任务,只不过前者有更多的可选参数选择,通过设置TaskCreationOptions可以让任务有多种方式运行(例如长时间的运行,不会被线程池回收)。而Task.Run创建出来的人物是TaskCreationOptions.DenyChildAttach(作为子任务执行)。后者更加轻量。
  • Task和Thread区别
  1. Task默认是创建任务,然后等待线程池中线程执行任务,而Thread是创建独立线程,对于资源(线程)的利用来说,Task更合适。
  2. Task有更多的扩展方法可供使用,更好的控制任务的执行。

.Net中的锁

既然提到了多线程,就不得不再提到锁。而C#中包含的锁有:Monitor(混合锁),Mutex(互斥锁),Interlock(原子锁),ReaderWriterLockSlim(读写锁),SpinLock(自旋锁),SemaphoreSlim(信号锁),AutoResetEvent(事件锁)。

  • Monitor

提供了限制访问代码块的功能,通常称为临界区,当有线程拥有对象的锁的时候,任何其他线程都无法获取该锁。
用法如下:

/// <summary>
/// 排它锁
/// </summary>
static void MonitorTest()
{
   
    var taskA = Task.Run<string>(MonitorFunc);
    var taskB = Task.Run<string>(MonitorFunc);
    Console.WriteLine($"TaskA:{
     taskA.Result}");
    Console.WriteLine($"TaskB:{
     taskB.Result}");
}

static string MonitorFunc()
{
   
    Monitor.Enter(lockObj);
    try
    {
   
        if (CurrentNums > 0)
        {
   
            CurrentNums--;
            return "购买成功,库存充足";
        }
        else
        {
   
            return "库存不足了";
        }
    }
    finally
    {
   
        Monitor.Exit(lockObj);
    }
}

我们平常用的lock实际上是对Monitor的封装,lock代码块里面的内容被封装在try语句里面。

需要注意的是,我们在锁的时候,尽量使用引用类型对象(除string类型外),也不要使用值类型。因为值类型每次在Enter和Exit的时候都会进行一次装箱操作,会导致指向的不是同一个变量,会发生错误。string类型,.net内部会将string类型放到一个池子里,可能多个变量指向同一份引用,如果对string类型进行操作的话,就会出现和预期结果不一致的情况,如果用没有用过的字符串,则不会。

  • Mutex

Mutex是一个同步基元,该基元仅向一个线程授予对共享资源的独占访问权限。 如果线程获取互斥体,则将获取该互斥体的第二个线程将挂起,直到第一个线程释放该互斥体才能具有访问权限。还可在进程中控制同步基元。
用法如下:

private static bool flag = true;
private static Mutex mut = new Mutex(true, "TestName", out flag);
private static Mutex mutT = new Mutex();
/// <summary>
/// 互斥锁,它能对进程之间
/// </summary>
static void MutexTest()
{
   
    if(!flag)
    {
   
        Console.WriteLine("只能开启一个客户端");
    }
    var taskA = Task.Run(MutexFunc);
    var taskB = Task.Run(MutexFunc);
    Task.WaitAll(taskA, taskB);

}

static void MutexFunc()
{
   
    mutT.WaitOne();

    if (CurrentNums == 1)
    {
   
        for (var i = 0; i <= 10; i++)
        {
   
            Console.WriteLine($"{
     i}");
        }
    }
    else
    {
   
        Console.WriteLine("不能执行了");
    }
    CurrentNums--;
    mutT.ReleaseMutex();

}

Mutex分为本地互斥体(未命名的,仅存在于当前进程中)和系统互斥体(命名的,多个进程公用)。我们可以通过本地互斥体达到Monitor的锁效果,而系统互斥体的一个用法:控制一个服务器内只能开启一个相同的客户端。相比Monitor来说,它并不轻量,以系统资源为代价,性能不太好,除非需要进程同步资源时,考虑使用它。

  • Interlock

针对值类型做增量或减量操作,因为通常情况下对于值类型,如果多个线程同时进行操作时,可能会导致变量与最终预期结果不一致的情况,所以这时候可以通过这个类,达到变量原子操作。
用法如下:

/// <summary>
/// 值变量锁
/// </summary>
static void InterlockedTest()
{
   
    var taskA = Task.Run(InterlockedFunc);
    var taskB = Task.Run(InterlockedFunc);
    Task.WaitAll(taskA, taskB);
    Console.WriteLine($"{
     CurrentNums}");

}

static void InterlockedFunc()
{
   
    for(var i 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值