关于多线程和异步的处理机制

昨天在B站看杨旭大佬讲 .Net Core的时候,发现Startup文件 里面有异步的使用

            app.Run(async (context) =>
            {
                var welecome = welcomeService.GetMessage();
                await context.Response.WriteAsync(welecome);
            });

之前就一直对异步编程一知半解的,索性就去搜了下关于异步的使用,没想到居然在B站找到一个关于多线程的视频

https://www.bilibili.com/video/av54102425?t=1501

很底层的解释了异步的处理机制

先说一下多线程:为什么会诞生线程:

在早期,当应用程序执行一个非常耗时的操作,用户只能等待这个操作执行完毕,期间你不能干其他的事情。就比如
因为一个意外异常,导致你的系统陷入了一个无限循环的操作中,这个时候你没办法对系统进行任何操作,只能重启,
这对于用户体验是非常差的。于是就推动着微软创造出了线程的概念。

现在当用户创建一个新进程,比如运行一个记事本或者其他应用程序的时候,每个进程都有自己的虚拟空间,所以现在内存被隔
离在线程中间,然后每一个进程都会有一个线程,系统会接受进程和线程将其放在CPU上面,应用程序会开始执行,但只只会执行一小段时间,在这之后,系统会将这个线程拿下来,将应用程序切换到上下文,然后将另外一个应用程序的进程线程放在CPU上跑,如此循环操作。

这样的话,上面例子中说到的无限循环的操作,就不可能占用所有的CPU,操作系统在线程之间来回切换。所以用户就可以按Crtl+Alt+Delete,打开任务管理器找到这个操作,然后直接干掉他就好了。

最初的线程只是为了让操作系统变得更加稳定。但是到了现在,每个电脑上都会有好几个CPU,也就是说,在相同的时间里,可以有更多的进程线程可以在多个CPU上同时运行。这就是我们提升应用程序性能的关键点。

这里有个要注意的地方:
在多个CPU的操作系统上,使用多线程无疑可以提升应用程序的性能,但是对于当个CPU,是无限的,甚至可能会起到反效果

而且当你拥有更多线程的时候,频繁的上下文切换会消耗更多的资源。

异步请求

这里是视频中的一个例子:
关于多线程和异步的处理机制_第1张图片

如图是一个典型的网站示例:

假设第一个浏览器发送请求给服务端,请求进入线程池,线程池唤醒一个线程进行处理服务端的一些代码,然后这个线程还需要再去请求另一个数据库服务器去获取数据,如果我们的请求是同步请求的话,线程将会被阻塞到那里,直到请求完成拿到数据。
这个时候第二个浏览器也同时发送请求,因为上个线程没有返回线程池,所以线程池又要新建一个线程去处理这次的请求。第三个浏览器同理。
而且如果数据服务器是支持并行的话,他们可以同时处理这么多个请求,那么这些数据都会同时返回给服务端,然后所有的线程都会被重新唤醒并开始运行。如果服务端只有一个CPU或者两个CPU的话,就会上面解释的那样,因为同时有三个线程在运行,而只有两个CPU,这就会引入上下文切换,这将会消耗更多资源。
而且当线程执行完毕都返回了线程池,所有的请求结果返回浏览器,并且在一段时间内没有其他操作时,他们会自动销毁,杀死线程也会影响性能。
经过测试如果上面服务端采用的是32位进程,在这里最多可以创建大约1500个线程,当同时请求的并发数超过1500个,系统将会抛出内存异常,这次的请求将会什么也拿不到!只能重新启动服务。

解决方案

而当我们使用了异步处理机制的时候,同样的场景:浏览器发送请求给服务端,请求进入线程池,线程池创建一个线程进行处理服务端的一些代码,然后异步的请求数据库服务器,因为请求是异步的这时线程不会被阻塞,它将会直接返回线程池,然后第二个请求进来了,这个时候上一个线程已经返回了线程池,因此不用再重新创建一个线程。这样问题就解决了

在数据库服务器处理完了数据之后,它会返回相应数据并记住设备驱动程序,并将这些响应放入线程池,然后线程池将会依次将数据返回相应客户端(这里我的理解是,在异步请求的时候,每个请求会被打上相应的标记,然后数据返回线程池之后,会根据相应的标记返回给相应的客户端即浏览器)

因此如果现在有超过1500个请求进来,也可以顺利的处理完这些请求。
如果我们有8个CPU,也就是说我们可以同时拥有8个线程在cpu上跑(系统会尽力避免上下文切换),所以我们同时可以处理8个请求。线程池在运行时会自动的算出来这些。

视频里还有介绍线程死锁出现的原理(我还不是很理解),以及一些避免的方法ConfigureAwait(false)

你可能感兴趣的:(.Net,知识碎片)