当前位置: 首页 > article >正文

.NET/C#⾯试题汇总系列:多线程

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

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

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

lock 关键字:

用于确保当多个线程同时访问共享资源时,不会发生数据竞争(race condition)或数据不一致的问题。

lock 语句通过获取给定对象的互斥锁来同步代码块,以确保一次只有一个线程可以执行该代码块。

  • 示例 : 线程安全的集合操作
public class ThreadSafeList<T>
{
    private readonly List<T> _list = new List<T>();
    private readonly object _lockObject = new object();

    public void Add(T item)
    {
        lock (_lockObject)
        {
            _list.Add(item);
        }
    }

    public void Remove(T item)
    {
        lock (_lockObject)
        {
            _list.Remove(item);
        }
    }

    public List<T> GetAll()
    {
        lock (_lockObject)
        {
            return new List<T>(_list);
        }
    }
}

在这个例子中,我们创建了一个线程安全的列表类 ThreadSafeList<T>,其中 Add 和 Remove 方法使用 lock 来确保对列表的修改是线程安全的。GetAll 方法也使用 lock 来确保在返回列表的副本时,列表的状态是一致的。

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

  1. 定义与区别‌:

    • 进程‌(Process)是系统进行资源分配和调度的一个独立单元,是操作系统结构的基础。它是应用程序的一次动态执行过程,是程序代码及其数据在运行时的一个实例。它拥有独立的内存空间,包含一组系统资源(如代码、数据和打开的文件等)。
    • 线程‌(Thread)是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的独立运行的单位。一个进程可以拥有多个线程,这些线程共享该进程的所有资源(如内存和打开的文件),但每个线程都拥有自己独立的执行栈和程序计数器。
  2. 资源与开销‌:

    • 进程拥有独立的内存空间和系统资源,因此进程间的通信相对复杂,开销也较大。
    • 线程共享进程的资源,因此线程间的通信相对简单,开销也较小。
  3. 独立性‌:

    • 进程拥有更高的独立性,一个进程的崩溃通常不会影响到其他进程。
    • 线程则不然,一个线程的崩溃可能会导致整个进程的崩溃(取决于线程的异常处理机制)。

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

这个需要针对硬件平台,公式为单个进程能访问的最⼤内存量=2的处理器位数次⽅/2,⽐如通常情况下, 32位处理器下,单个进程所能访问的最⼤内存量为:232 /2 = 2G 。

单个进程能访问的最⼤内存量是最⼤ 虚拟内存的1/2,因为要分配给操作系统⼀半虚拟内存。

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

有⽤

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

  • using() 语法的用途

using 语句在C#中非常有用,特别是在处理实现了IDisposable接口的对象时。它确保了在代码块执行完毕后,会自动调用对象的Dispose方法,以释放非托管资源(如文件句柄、数据库连接等)。这不仅简化了资源管理,还避免了资源泄露的风险。

  • 什么是IDisposable

IDisposable是.NET中定义的一个接口,它包含一个Dispose方法。当一个类实现了IDisposable接口时,就意味着这个类拥有非托管资源,需要在不再需要时显式释放。Dispose方法通常用于释放这些非托管资源,并可能还包含对托管资源的清理逻辑(如关闭文件流、数据库连接等)。

通过使用using语句,我们可以确保即使发生异常,Dispose方法也会被调用,从而安全地释放资源。

例如:

using (var fileStream = new FileStream("example.txt", FileMode.OpenOrCreate))
{
    // 使用 fileStream 进行操作
}
// fileStream 的 Dispose 方法会在此处自动调用

FileStream类实现了IDisposable接口,因此我们可以使用using语句来确保fileStream在使用完毕后会被正确关闭和释放资源。

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

  • 前台线程(Foreground Thread)‌:在.NET中,默认创建的线程都是前台线程。只要应用程序中有任何前台线程在运行,应用程序就会保持运行状态。只有当所有的前台线程都终止时,应用程序才会结束。

  • 后台线程(Background Thread)‌:后台线程不会影响应用程序的终止。一旦所有的前台线程都终止,无论后台线程是否还在运行,应用程序都会自动结束。后台线程通常用于处理那些非关键的、可以异步执行的任务比如数据加载文件操作等。

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状态机的实现和⼯作机制是什么?

在.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有区别吗?

  1. 抽象层次‌:Task 提供了比 Thread 更高的抽象层次。它代表了异步操作,可以很容易地与C#的异步编程模式(async/await)集成。Thread 则更底层,直接表示操作系统线程。

  2. 资源管理‌:Task 池管理了任务的执行,可以更有效地重用线程,减少线程创建和销毁的开销。相比之下,直接使用 Thread 需要程序员手动管理线程的创建、执行和销毁。

  3. 异步编程支持‌:Task 天然支持C#的异步编程模式,使得编写异步代码更加简单和直观。而 Thread 需要通过额外的机制(如回调函数、轮询等)来实现类似的功能。

11.多线程有什么⽤?

  1. 提高CPU利用率:多线程使得在等待某些任务完成时,如I/O操作或用户输入,CPU可以转而执行其他线程的任务,从而大大提高了CPU的利用率。
  2. 提升系统性能:通过并行处理多个任务,多线程可以显著提高系统的处理能力和性能,尤其是在多核处理器系统中更为明显。
  3. 改善用户体验:在图形用户界面(GUI)应用中,多线程可以实现界面的响应式设计,即使后台正在进行耗时的操作,用户界面也能保持活跃,提升用户体验。
  4. 简化资源共享:同一进程下的所有线程共享进程的资源,如内存空间、文件句柄等,这使得不同任务之间的协调操作与数据交互更加简单高效。
  5. 实现复杂任务:对于需要同时处理多个独立但相关任务的场景,多线程提供了一个有效的解决方案。例如,在服务器端并发处理用户请求时,每个请求可以在一个独立的线程中处理,互不干扰,提高了服务的响应速度和效率。
  6. 优化资源分配:在多线程编程中,可以通过线程同步机制来控制对共享资源的访问,防止数据竞争和死锁的发生,确保系统的稳定性和可靠性。
  7. 增强程序灵活性:多线程技术使得开发者能够更灵活地安排程序的执行流程,通过创建和管理不同的线程,可以有效地应对各种复杂的程序需求。
  8. 支持实时系统:在需要快速响应外部事件的应用中,如实时数据处理或游戏开发,多线程技术可以提供必要的并发执行能力,以满足严格的时间要求。

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

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

static AutoResetEvent oddReady = new AutoResetEvent(false);
static AutoResetEvent evenReady = new AutoResetEvent(true);//偶数线程先开始
static void Main(string[] args)
{
    Thread oddThread = new Thread(PrintOddNumbers);
    Thread evenThread = new Thread(PrintEvenNumbers);

    oddThread.Start();
    evenThread.Start();

    oddThread.Join();
    evenThread.Join();

    Console.WriteLine("打印完成。");
}
static void PrintOddNumbers()
{
    for (int i = 1; i <= 10; i+=2) { 
        evenReady.WaitOne();// 等待偶数线程完成
        Console.WriteLine(i);
        oddReady.Set(); // 通知奇数线程可以运行
    }
}
static void PrintEvenNumbers()
{
    for (int i = 0; i <= 100; i += 2)
    {
        oddReady.WaitOne(); // 等待奇数线程完成
        Console.WriteLine(i);
        evenReady.Set(); // 通知偶数线程可以运行
    }
}

AutoResetEvent 是 C# 中的一个类,用于实现线程同步。它属于 System.Threading 命名空间。

AutoResetEvent 有两种状态:有信号(true)和无信号(false)。

当一个线程调用 WaitOne() 方法时,如果事件处于无信号状态,那么该线程将被阻塞,直到另一个线程调用 Set() 方法将事件设置为有信号状态

一旦事件被设置为有信号状态,WaitOne() 方法将返回,并且事件将自动重置为无信号状态。

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

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

解决⽅法⽐较多的

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


http://www.kler.cn/news/306023.html

相关文章:

  • 【有啥问啥】自动提示词工程(Automatic Prompt Engineering, APE):深入解析与技术应用
  • Spring security 动态权限管理(基于数据库)
  • 多源BFS的模板以及练习题(多源BFS)
  • `character_set_server` 和 `collation_server`
  • nvm安装并配置全局缓存文件
  • 【webpack4系列】webpack初识与构建工具发展(一)
  • 【GO语言】Go语言详解与应用场景分析,与Java的对比及优缺点
  • CSP组T1怪物
  • 升级VMware
  • 视频监控摄像头国标GB28181配置参数逐条解析
  • UE5安卓项目打包安装
  • Rust 控制流
  • NarratoAI利用AI大模型,一键解说并剪辑视频
  • SQL优化(二)统计信息
  • linux手册翻译 addr2line
  • Grafana 汉化
  • 顺序栈讲解
  • C语言 | Leetcode C语言题解之第406题根据身高重建队列
  • ICPC网络赛 以及ACM训练总结
  • 计算架构模式之接口高可用
  • OpenAI发布o1,首个具备‘推理’能力的模型
  • JavaScript事件处理和常用对象
  • Git+Jenkins 基本使用(Basic Usage of Git+Jenkins)
  • 【C++】STL--string(上)
  • QEMU的时间
  • uniapp的苹果全屏播放再退出会导致页面字体变大解决方法
  • 「数组」十大排序:精讲与分析(C++)
  • C++: 二叉树进阶面试题
  • Leetcode 验证回文串
  • kettle从入门到精通 第八十五课 ETL之kettle kettle中javascript步骤调用外部javascript/js文件