异步VS多线程
前段时间面试的时候被问到了异步和多线程的区别,回答的模棱两可,今天花时间系统的了解了下,如有错误,欢迎斧正~
异步与多线程是在并发编程中两个比较关键的概念, 很容易弄混淆:
异步编程(Asynchronous)
概念:异步编程是并发编程的一种形式,即在程序运行逻辑中,一部分语句可以独立于主程序而执行。
理解:异步相对于同步而言,同步的模式很容易理解,假设一块程序只有十步逻辑,那么这段逻辑中每步代码都要等待前一步代码执行完成才会执行,换句话说就是主程序流是从上往下依次顺序执行的,如果遇到一个步骤,常见的是我们对后端一个请求或一次数据访问,这个操作往往耗时很久(这个操作我们称之为一个阻断操作),整个页面就会停在那里没有响应。为了提高页面的体验度,异步请求应运而生。
使用目的:为了提高用户体验,优化页面加载。
关键字:在C#中,异步编程通过关键字async和await来达成。
多线程编程(Multithreading)
概念:多线程就是CPU利用多个线程来并发地运行多段逻辑。
理解:在计算机科学中,一个线程指的是在程序中一段连续的逻辑控制流。在业务很复杂的时候,一个线程无法满足现有业务需求,多线程编程就应运而生。
使用目的:为了最大限度地利用多核CPU这个硬件条件来达到多个线程同时执行逻辑。
关键字:在C#中,System.Threading命名空间下有很多类用来进行多线程编程。
异步VS多线程
一个常见的概念混淆就是吧异步和多线程当成一回事,其实不一样。异步侧重于任务的执行顺序,而多线程则是关于多个线程如何并发执行。应该说多线程是实现异步的常用手段,但不能说他们是一回事。即便是只有一个线程的情况下。我们仍然可以实现异步。
举例说明两者的区别:
首先来看异步编程的代码:
public static async Task FirstAsync() { Console.WriteLine($"第一个异步方法开始,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); await Task.Delay(2000); Console.WriteLine($"第一个异步方法结束,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); } public static async Task SecondAsync() { Console.WriteLine($"第二个异步方法开始,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); await Task.Delay(2000); Console.WriteLine($"第二个异步方法结束,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); } public static async Task ThirdAsync() { Console.WriteLine($"第三个异步方法开始,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); await Task.Delay(2000); Console.WriteLine($"第三个异步方法结束,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); }
我们这里定义了三个异步方法,在每个异步方法中,我们在任务开始执行的时候在控制台打印出线程ID,然后通过延时两秒钟来模拟业务逻辑,最后我们依然打印出线程ID。
然后,添加一个异步方法来调用上面的异步方法:
public static async Task ExecuteAysnc() { var firstAsync = FirstAsync(); var secondAsync = SecondAsync(); var thirdAsync = ThirdAsync(); await Task.WhenAll(firstAsync, secondAsync, thirdAsync); }
最后在Main方法中调用执行方法:
public static async Task ExecuteAysnc() { var firstAsync = FirstAsync(); var secondAsync = SecondAsync(); var thirdAsync = ThirdAsync(); await Task.WhenAll(firstAsync, secondAsync, thirdAsync); }
运行结果如下:
从运行结果可以看出:所有三个异步方法都是在线程ID为1的线程上运行的,但是经过2秒钟的睡眠,当再次执行异步任务的时候,每个任务又被别的线程执行起来,这里显示为4、7、5.
为什么会发生这样的事情呢?原因就在于当线程发现关键字await的时候,该线程就会从当前逻辑释放而返回给线程池。一旦睡眠结束,一个新的线程(4、7、5)就重新被分配来执行三个任务。
同样地,我们再看看多线程的代码示例,与上面的例子不同,我们去除了关键字async
public static void FirstMethod() { Console.WriteLine($"第一个方法开始,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(2000); Console.WriteLine($"第一个方法结束,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); } public static void SecondMethod() { Console.WriteLine($"第二个方法开始,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(2000); Console.WriteLine($"第二个方法结束,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); } public static void ThirdMethod() { Console.WriteLine($"第三个方法开始,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(2000); Console.WriteLine($"第三个方法结束,运行的线程ID为:{Thread.CurrentThread.ManagedThreadId}"); }
逻辑和异步方法的一样,都是在开始的时候打印线程ID,2秒的业务逻辑操作之后再打印当前线程ID,同样,我们创建一个方法,再这个方法里,开启三个线程来启动这三个任务:
public static void ExecuteMultithreading() { Thread firstThread = new Thread(() => FirstMethod()); Thread secondThread = new Thread(() => SecondMethod()); Thread thirdThread = new Thread(() => ThirdMethod()); firstThread.Start(); secondThread.Start(); thirdThread.Start(); }
main函数调用执行方法:
static void Main(string[] args) { Multithreading.ExecuteMultithreading(); Console.ReadLine(); }
运行结果如下:
从打印结果可以看出,同一个方法,开始和结束的线程ID保持一致。再多线程的例子里,我们显示地分配了线程,该线程直到做完对应的任务才会结束,即时逻辑当中有一段时间的业务逻辑。
使用场景:
现在我们明白了异步和多线程的差别,下面我们可以得出:
异步编程适用场景:
- 我们的程序逻辑中存在阻断操作。
- 我们的应用有一个线程池并且有垂直扩展的需求。
多线程编程适用场景:
- 我们的程序逻辑存在相互独立的任务,任务与任务之间不相互干扰。
- 我们存在多个可用的CPU内核数。
总结:
异步编程和多线程编程的区别在于侧重点不同,异步编程侧重于任务的执行顺序,而多线程编程则是关于多个线程如何并发执行最大限度利用CPU,两者对于提高程序可用性和性能方面起到了关键性的作用。