51mee - AI智能招聘平台Logo
模拟面试题目大全招聘中心会员专区

在.NET环境中,使用lock关键字实现线程安全的共享资源访问,请说明lock的原理(如Monitor Enter/Exit)以及如何避免死锁(如避免嵌套锁、遵循加锁顺序)。

微软Software Engineer Intern (Neurodiversity Hiring Program*)难度:中等

答案

1) 【一句话结论】
在.NET中,使用lock实现线程安全的核心是依赖Monitor的Enter/Exit机制控制共享资源访问,需遵循避免死锁原则(如避免嵌套锁、遵循加锁顺序),同时需优化锁粒度(缩小保护范围)和减少持有时间(降低阻塞),并理解锁的性能影响(如自旋锁在高负载下的开销)。

2) 【原理/概念讲解】
.NET的lock是同步原语,底层由Monitor封装操作系统互斥锁。执行lock语句时,会自动调用Monitor.Enter()进入临界区,此时Monitor会先尝试自旋(循环检查锁是否可用,默认5次,减少上下文切换开销),若自旋失败则将线程加入等待队列(按FIFO顺序等待锁释放)。当线程退出lock块时,调用Monitor.Exit()释放锁,唤醒等待队列中的第一个线程。此外,Monitor还支持优先级继承机制,若高优先级线程等待低优先级线程持有的锁,会临时提升低优先级线程的优先级,避免低优先级线程阻塞高优先级线程。可以类比“共享会议室”:会议室是共享资源,钥匙是锁,进入前必须先拿到钥匙(Monitor.Enter),离开时归还(Monitor.Exit),同一时间只有一个线程在会议室操作,保证数据安全。Monitor自动管理线程的等待和唤醒,无需手动处理。

3) 【对比与适用场景】

特性/要点说明
定义C#中的同步块,基于Monitor实现,用于保护共享资源访问
原理调用Monitor.Enter进入临界区(含默认5次自旋+等待队列调度),Monitor.Exit退出,自动管理优先级继承
使用场景保护共享变量、对象、方法等,需互斥访问的情况(如计数器、队列操作)
注意点1. 避免嵌套锁(锁内再锁,导致死锁);2. 遵循加锁顺序(如按A→B顺序加锁,避免循环等待);3. 锁粒度优化(缩小锁范围,如只锁关键变量,提高并发性能);4. 减少持有时间(减少阻塞,提升吞吐量,如异步操作中快速释放锁)

4) 【示例】
伪代码示例(共享计数器操作):

public class SafeCounter
{
    private int _count = 0;
    private readonly object _lock = new object(); // 锁对象

    public void Increment()
    {
        lock (_lock) // 进入临界区,保护计数器递增
        {
            _count++;
        }
    }

    public int GetCount()
    {
        lock (_lock) // 进入临界区,保护读取操作
        {
            return _count;
        }
    }
}

多个线程调用Increment或GetCount时,通过lock保证每次只有一个线程执行关键操作,避免数据竞争。若不使用lock,多个线程同时递增会导致计数器值错误(如两个线程同时读取_count为0,各自加1后写回,最终计数器为1而非2)。

5) 【面试口播版答案】
面试官您好,关于.NET中使用lock实现线程安全,核心是通过Monitor的Enter/Exit机制控制共享资源访问。具体来说,lock会自动调用Monitor.Enter()进入临界区,Monitor.Exit()退出,确保同一时间只有一个线程访问被保护的资源。为了避免死锁,关键是要避免嵌套锁(比如锁内再锁)和遵循加锁顺序(比如多个锁A、B,始终按A→B的顺序加锁)。另外,还要注意锁的粒度,比如如果只需要保护某个变量,而不是整个对象,可以缩小锁范围,避免影响其他方法的并发性能。长时间持有锁也会阻塞其他线程,降低系统吞吐量,所以应尽量减少锁的持有时间,比如在lock内快速执行关键操作,或者考虑异步操作中快速释放锁。

6) 【追问清单】

  • 问:如何检测死锁?
    回答要点:死锁检测复杂,通常通过代码逻辑避免(如加锁顺序、避免嵌套锁),或使用工具(如Windows的Process Explorer查看线程的等待状态,判断是否有线程在等待锁,从而识别死锁)。
  • 问:lock和Mutex有什么区别?
    回答要点:lock是C#语法糖,基于Monitor实现,用于同一进程内线程同步;Mutex是操作系统级互斥对象,可用于跨进程同步,但更复杂,需要手动管理,且性能较低。
  • 问:如果共享资源是集合(如List),如何用lock保护?
    回答要点:直接用lock保护对集合的所有操作,比如遍历时也要加锁(因为遍历可能涉及修改,如Remove),否则可能导致并发问题,比如一个线程在遍历,另一个线程在移除元素,导致集合状态不一致。
  • 问:有没有比lock更高效的同步方式?
    回答要点:对于简单原子操作(如计数器递增),可使用Interlocked类(如Interlocked.Increment)避免锁开销;对于细粒度控制,可考虑信号量(Semaphore)或条件变量(ConditionVariable),但适用场景不同。

7) 【常见坑/雷区】

  • 嵌套锁:在lock块内再次使用lock,会导致死锁(外层锁未释放,内层锁无法获取,形成循环等待)。
  • 加锁顺序错误:多个锁A和B,若线程1先锁A再锁B,线程2先锁B再锁A,会导致循环等待死锁。
  • 锁粒度过大:锁住整个对象或方法,影响并发性能,应缩小锁范围(如只锁关键变量)。
  • 锁持有时间过长:长时间持有锁会阻塞其他线程,降低吞吐量,应减少持有时间。
  • 忽略Monitor的等待队列和自旋锁机制:导致对锁的底层理解不深,无法解释性能优化点(如自旋锁在高负载下的开销)。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1