C#.NET面试题汇总系列四:多线程
0. 参考文档
1. 描述线程与进程的区别?
线程(Thread)与进程(Process)二者都定义了某种边界,不同的是进程定义的是应用程序与应用程序之间的边界,不同的进程之间不能共享代码和数据空间,而线程定义的是代码执行堆栈和执行上下文的边界
一个进程可以包括若干个线程,同时创建多个线程来完成某项任务,便是多线程
2. 什么是互斥?
当多个线程访问同一个全局变量,或者同一个资源(比如打印机)的时候,需要进行线程间的互斥操作来保证访问的安全性
3. Task状态机的实现和工作机制是什么?
CPS全称是Continuation Passing Style,在.NET中,它会自动编译为:
- 将所有引用的局部变量做成闭包,放到一个隐藏的状态机的类中
- 将所有的await展开成一个状态号,有几个await就有几个状态号
- 每次执行完一个状态,都重复回调状态机的MoveNext方法,同时指定下一个状态号
- 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来访问或修改变量
其次是利用线程上下文