.NET/C#⾯试题汇总系列:多线程【面试必须掌握的一项知识】

PS:小编之前毕业找工作面试的公司,基本上每家公司都有提问到多线程,而多线程在大学里基本上都是没有涉及,或者涉及不深,又或者平时的自主练习基本没有涉及这块的内容,因此这个多线程系列是面试前必须要掌握的,临时抱佛脚也是可以的(就目前来说,多线程在大公司里面都是封装好的,基本上就是学习使用的问题,但是多线程的基本知识还是要了解的)

1.根据线程安全的相关知识,分析以下代码,当调⽤test⽅法时i>10时是否会引起死锁?并简要说明理由。

 public void test(int i)
 {
    
    
	lock(this)
 	{
    
    
		 if (i>10)
		{
    
    
			 i--;
			 test(i);
		 }
	 }
 }

不会发⽣死锁,(但有⼀点int是按值传递的,所以每次改变的都只是⼀个副本,因此不会出现死锁。但如果把int换做⼀个object,那么死锁会发⽣)

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

线程(Thread)与进程(Process)⼆者都定义了某种边界,不同的是进程定义的是应⽤程序与应⽤程序之间的边界,不同的进程之间不能共享代码和数据空间,⽽线程定义的是代码执⾏堆栈和执⾏上下⽂的边界。⼀个进程可以包括若⼲个线程,同时创建多个线程来完成某项任务,便是多线程。
⽽同⼀进程中的不同线程共享代码和数据空间。⽤⼀个⽐喻来说,如果⼀个家庭代表⼀个进程,在家庭内部,各个成员就是线程,家庭中的每个成员都有义务对家庭的财富进⾏积累,同时也有权利对家庭财富进⾏消费,当⾯对⼀个任务的时候,家庭也可以派出⼏个成员来协同完成,⽽家庭之外的⼈则没有办法直接消费不属于⾃⼰家庭的财产。

3.Windows单个进程所能访问的最⼤内存量是多少?它与系统的最⼤虚拟内存⼀样吗?这对于系统设计有什么影响?

这个需要针对硬件平台,公式为单个进程能访问的最⼤内存量=2的处理器位数次⽅/2,⽐如通常情况下,32位处理器下,单个进程所能访问的最⼤内存量为:232 /2 = 2G 。单个进程能访问的最⼤内存量是最⼤虚拟内存的1/2,因为要分配给操作系统⼀半虚拟内存。

4.using() 语法有⽤吗?什么是IDisposable?

有⽤,实现了IDisposiable的类在using中创建,using结束后会⾃定调⽤该对象的Dispose⽅法,释放资源。

5.前台线程和后台线程有什么区别?

通过将 Thread.IsBackground 属性设置为 true,就可以将线程指定为后台线程
前台线程: 应⽤必须结束掉所有的前台线程才能结束程序,只要有⼀个前台线程没退出进程就不会⾃动退出,当然线程是依附在进程上的,所以你直接把进程KO掉了的话⾃然所有前台线程也会退出。
后台线程: 进程可以不考虑后台直接⾃动退出,进程⾃动退出后所有的后台线程也会⾃动销毁。

6.什么是互斥?

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

7.如何查看和设置线程池的上下限?

线程池的线程数是有限制的,通常情况下,我们⽆需修改默认的配置。但在⼀些场合,我们可能需要了解线程池的上下限和剩余的线程数。线程池作为⼀个缓冲池,有着其上下限。在通常情况下,当线程池中的线程数⼩于线程池设置的下限时,线程池会设法创建新的线程,⽽当线程池中的线程数⼤于线程池设置的上限时,线程池将销毁多余的线程。
PS:在.NET Framework 4.0中,每个CPU默认的⼯作者线程数量最⼤值为250个,最⼩值为2个。⽽IO线程的默认最⼤值为1000个,最⼩值为2个。
在.NET中,通过 ThreadPool 类型提供的5个静态⽅法可以获取和设置线程池的上限和下限,同时它还额外地提供了⼀个⽅法来让程序员获知当前可⽤的线程数量,下⾯是这五个⽅法的签名:
① static void GetMaxThreads(out int workerThreads, out int completionPortThreads)
② static void GetMinThreads(out int workerThreads, out int completionPortThreads)
③ static bool SetMaxThreads(int workerThreads, int completionPortThreads)
④ static bool SetMinThreads(int workerThreads, int completionPortThreads)
⑤ static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)

8. Task状态机的实现和⼯作机制是什么?

CPS全称是Continuation Passing Style,在.NET中,它会⾃动编译为: 1. 将所有引⽤的局部变量做成闭包,放到⼀个隐藏的状态机的类中; 2. 将所有的await展开成⼀个状态号,有⼏个await就有⼏个状态号; 3. 每次执⾏完⼀个状态,都重复回调状态机的MoveNext⽅法,同时指定下⼀个状态号; 4.
MoveNext⽅法还需处理线程和异常等问题。

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

从状态机的⻆度出发,await的本质是调⽤Task.GetAwaiter()的UnsafeOnCompleted(Action)回调,并指定下⼀个状态号。
从多线程的⻆度出发,如果await的Task需要在新的线程上执⾏,该状态机的MoveNext()⽅法会⽴即返回,此时,主线程被释放出来了,然后在UnsafeOnCompleted回调的action指定的线程上下⽂中继续MoveNext()和下⼀个状态的代码。
⽽相⽐之下,GetResult()就是在当前线程上⽴即等待Task的完成,在Task完成前,当前线程不会释放。注意:Task也可能不⼀定在新的线程上执⾏,此时⽤GetResult()或者await就只有会不会创建状态机的区别了。

10.Task和Thread有区别吗?

Task和Thread都能创建⽤多线程的⽅式执⾏代码,但它们有较⼤的区别。Task较新,发布于.NET 4.5,能结合新的async/await代码模型写代码,它不⽌能创建新线程,还能使⽤线程池(默认)、单线程等⽅式编程,在UI编程领域,Task还能⾃动返回UI线程上下⽂,还提供了许多便利API以管理多个Task。

11.多线程有什么⽤?

(1)发挥多核CPU的优势
随着⼯业的进步,现在的笔记本、台式机乃⾄商⽤的应⽤服务器⾄少也都是双核的,4核、8核甚⾄16核的也都不少⻅,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的”多线程”那是假的多线程,同⼀时间处理器只会处理⼀段逻辑,只不过线程之间切换得⽐较快,看着像多个线程”同时”运⾏罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时⼯作,多线程,可以真正发挥出多核CPU的优势来,达到充分利⽤CPU的⽬的。
(2)防⽌阻塞
从程序运⾏效率的⻆度来看,单核CPU不但不会发挥出多线程的优势,反⽽会因为在单核CPU上运⾏多线程导致线程上下⽂的切换,⽽降低程序整体的效率。但是单核CPU我们还是要应⽤多线程,就是为了防⽌阻塞。试想,如果单核CPU使⽤单线程,那么只要这个线程阻塞了,⽐⽅说远程读取某个数据吧,对端迟迟未返回⼜没有设置超时时间,那么你的整个程序在数据返回回来之前就停⽌运⾏了。多线程可以防⽌这个问题,多条线程同时运⾏,哪怕⼀条线程的代码执⾏读取数据阻塞,也不会影响其它任务的执⾏。
(3)便于建模
这是另外⼀个没有这么明显的优点了。假设有⼀个⼤的任务A,单线程编程,那么就要考虑很多,建⽴整个程序模型⽐较麻烦。但是如果把这个⼤的任务A分解成⼏个⼩任务,任务B、任务C、任务D,分别建⽴程序模型,并通过多线程分别运⾏这⼏个任务,那就简单很多了。

12. 两个线程交替打印0~100的奇偶数

这道题就是说有两个线程,⼀个名为偶数线程,⼀个名为奇数线程,偶数线程只打印偶数,奇数线程只打印奇数,两个线程按顺序交替打印。

1 publicclassThreadExample
2 {
    
    
3 ///<summary>
4 ///两个线程交替打印0~100的奇偶数
5 ///</summary>
6 public static void PrintOddEvenNumber
7 {
    
    
8 varwork = newTheadWorkTest;
9 varthread1 = newThread(work.PrintOddNumer) {
    
     Name = "奇数线程"};
10 varthread2 = newThread(work.PrintEvenNumber) {
    
     Name = "偶数线
程"};
11 thread1.Start;
12 thread2.Start;
13 }
14 }
15
16 publicclassTheadWorkTest
17 {
    
    
18
19 privatestaticreadonlyAutoResetEvent oddAre = newAutoResetEvent(
false);
20
21 privatestaticreadonlyAutoResetEvent evenAre = newAutoResetEvent
( false);
22
23 publicvoidPrintOddNumer
24 {
    
    
25 oddAre.WaitOne;
26 for( var0; i < 100; i++ )
27 {
    
    
28 if(i % 2!= 1) continue;
29 Console.WriteLine($"{
      
      Thread.CurrentThread.Name}{
      
      i}");
30 evenAre.Set;
31 oddAre.WaitOne;
32 }
33 }
34
35 publicvoidPrintEvenNumber
36 {
    
    
37 for( vari = 0; i < 100; i++ )
38 {
    
    
39 if(i % 2!= 0) continue;
40 Console.WriteLine($"{
      
      Thread.CurrentThread.Name}{
      
      i}");
41 oddAre.Set;
42 evenAre.WaitOne;
43 }
44 }
45 }

13.为什么GUI不⽀持跨线程调⽤?有什么解决⽅法?

因为GUI应⽤程序引⼊了⼀个特殊的线程处理模型,为了保证UI控件的线程安全,这个线程处理模型不允许
其他⼦线程跨线程访问UI元素。解决⽅法⽐较多的:

  • 利⽤UI控件提供的⽅法,Winform是控件的Invoke⽅法,WPF中是控件的Dispatcher.Invoke⽅法;
  • 使⽤BackgroundWorker;
  • 使⽤GUI线程处理模型的同步上下⽂SynchronizationContext来提交UI更新操作

14.说说常⽤的锁,lock是⼀种什么样的锁?

常⽤的如如SemaphoreSlim、ManualResetEventSlim、Monitor、ReadWriteLockSlim,lock是⼀个混合锁,其实质是Monitor

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

lock的锁对象要求为⼀个引⽤类型。她可以锁定值类型,但值类型会被装箱,每次装箱后的对象都不⼀样,会导致锁定⽆效。
对于lock锁,锁定的这个对象参数才是关键,这个参数的同步索引块指针会指向⼀个真正的锁(同步块),这个锁(同步块)会被复⽤。

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

多线程是实现异步的主要⽅式之⼀,异步并不等同于多线程。实现异步的⽅式还有很多,⽐如利⽤硬件的特性、使⽤进程或线程等。
在.NET中就有很多的异步编程⽀持,⽐如很多地⽅都有Begin、End 的⽅法,就是⼀种异步编程⽀持,她内部有些是利⽤多线程,有些是利⽤硬件的特性来实现的异步编程。

17.线程池的有点和不⾜?

优点:减⼩线程创建和销毁的开销,可以复⽤线程;也从⽽减少了线程上下⽂切换的性能损失;在GC回收时,较少的线程更有利于GC的回收效率。缺点:线程池⽆法对⼀个线程有更多的精确的控制,如了解其运⾏状态等;不能设置线程的优先级;加⼊到线程池的任务(⽅法)不能有返回值;对于需要⻓期运⾏的任务就不适合线程池。

18.Mutex和lock有什么不同?⼀般⽤哪⼀种⽐较好?

Mutex是⼀个基于内核模式的互斥锁,⽀持锁的递归调⽤,⽽Lock是⼀个混合锁,⼀般建议使⽤Lock更好,因为lock的性能更好。

19.⽤双检锁实现⼀个单例模式Singleton。

1 public static class Singleton<T> where T : class,new()
2 {
    
    
3 private static T _Instance;
4 private static object _lockObj = new object();
5
6 /// <summary>
7 /// 获取单例对象的实例
8 /// </summary>
9 public static T GetInstance()
10 {
    
    
11 if (_Instance != null) return _Instance;
12 lock (_lockObj)
13 {
    
    
14 if (_Instance == null)
15 {
    
    
16 var temp = Activator.CreateInstance<T>();
17 System.Threading.Interlocked.Exchange(ref _In
stance, temp);
18 }
19 }
20 return _Instance;
21 }
22 }

20.Thread 类有哪些常⽤的属性和⽅法?

属性:

  • CurrentContext:获取线程正在其中执⾏的当前上下⽂。
  • CurrentCulture:获取或设置当前线程的区域性。
  • CurrentPrincipal:获取或设置线程的当前负责⼈(对基于⻆⾊的安全性⽽⾔)。
  • CurrentThread:获取当前正在运⾏的线程。
  • CurrentUICulture:获取或设置资源管理器使⽤的当前区域性以便在运⾏时查找区域性特定的资源。
  • IsBackground:获取或设置⼀个值,该值指示某个线程是否为后台线程。
  • Priority:获取或设置⼀个值,该值指示线程的调度优先级。
  • ThreadState:获取⼀个值,该值包含当前线程的状态。

⽅法:
public void Abort()
在调⽤此⽅法的线程上引发 ThreadAbortException,以开始终⽌此线程的过程。调⽤此⽅法通常会终⽌线程。

public static void ResetAbort()//取消为当前线程请求的 Abort。
public void Start() 开始⼀个线程。
public static void Sleep( int millisecondsTimeout )//让线程暂停⼀段时间。
 public static bool Yield()//导致调⽤线程执⾏准备好在当前处理器上运⾏的另⼀个线程。由操作系统选择要执⾏的线程。

猜你喜欢

转载自blog.csdn.net/weixin_45091053/article/details/127165804