C#.NET面试题汇总系列四:多线程

0. 参考文档

1. 描述线程与进程的区别?

线程(Thread)与进程(Process)二者都定义了某种边界,不同的是进程定义的是应用程序与应用程序之间的边界,不同的进程之间不能共享代码和数据空间,而线程定义的是代码执行堆栈和执行上下文的边界

一个进程可以包括若干个线程,同时创建多个线程来完成某项任务,便是多线程

2. 什么是互斥?

当多个线程访问同一个全局变量,或者同一个资源(比如打印机)的时候,需要进行线程间的互斥操作来保证访问的安全性

3. Task状态机的实现和工作机制是什么?

CPS全称是Continuation Passing Style,在.NET中,它会自动编译为:

  1. 将所有引用的局部变量做成闭包,放到一个隐藏的状态机的类中
  2. 将所有的await展开成一个状态号,有几个await就有几个状态号
  3. 每次执行完一个状态,都重复回调状态机的MoveNext方法,同时指定下一个状态号
  4. MoveNext方法还需处理线程和异常等问题

4. await的作用和原理,并说明和GetResult()有什么区别?

从状态机的角度出发,await的本质是调用Task.GetAwaiter()的UnsafeOnCompleted(Action)回调,并指定下一个状态号

从多线程的角度出发,如果await的Task需要在新的线程上执行,该状态机的MoveNext()方法会立即返回,此时,主线程被释放出来了,然后在UnsafeOnCompleted回调的action指定的线程上下文中继续MoveNext()和下一个状态的代码

而相比之下,GetResult()就是在当前线程上立即等待Task的完成,在Task完成前,当前线程不会释放

注意:Task也可能不一定在新的线程上执行,此时用GetResult()或者await就只有会不会创建状态机的区别了

5. Task和Thread有区别吗?

Task和Thread都能创建用多线程的方式执行代码,但它们有较大的区别

Task较新,发布于.NET 4.5,能结合新的async/await代码模型写代码,它不止能创建新线程,还能使用线程池(默认)、单线程等方式编程,在UI编程领域,Task还能自动返回UI线程上下文,还提供了许多便利API以管理多个Task

6. 多线程有什么用?

发挥多核CPU的优势,防止阻塞

7. 说说常用的锁,lock是一种什么样的锁?

常用的如 SemaphoreSlim、ManualResetEventSlim、Monitor、ReadWriteLockSlim

lock是一个混合锁,其实质是 Monitor

8. lock为什么要锁定一个参数(可否为值类型?)参数有什么要求?

lock的锁对象要求为一个引用类型,可以锁定值类型,但值类型会被装箱,每次装箱后的对象都不一样,会导致锁定无效

对于lock锁,锁定的这个对象参数才是关键,这个参数的同步索引块指针会指向一个真正的锁(同步块),这个锁(同步块)会被复用

9. 多线程和异步的区别和联系?

多线程是实现异步的主要方式之一,异步并不等同于多线程

实现异步的方式还有很多,比如利用硬件的特性、使用进程或纤程等

在.NET中就有很多的异步编程支持,比如很多地方都有Begin、End 的方法,就是一种异步编程支持,内部有些是利用多线程,有些是利用硬件的特性来实现的异步编程

10. 线程池的优点和不足?

优点:减小线程创建和销毁的开销,可以复用线程,也从而减少了线程上下文切换的性能损失,在GC回收时,较少的线程更有利于GC的回收效率

缺点:线程池无法对一个线程有更多的精确的控制,如了解其运行状态等;不能设置线程的优先级;加入到线程池的任务(方法)不能有返回值;对于需要长期运行的任务就不适合线程池

11. Mutex和lock有什么不同?一般用哪一种比较好?

Mutex是一个基于内核模式的互斥锁,支持锁的递归调用

Lock是一个混合锁,一般建议使用Lock更好,因为lock的性能更好

12. 如何实现线程间通信?

最简单的就是定义静态变量,然后在各个线程之间通过lock来访问或修改变量

其次是利用线程上下文

posted @ 2022-03-01 13:48  位永光  阅读(3735)  评论(0编辑  收藏  举报