首页 游戏 软件 资讯 排行榜 专题
首页
编程语言
C#怎么使用lock线程锁_C# lock和Monitor线程安全教程【进阶】

C#怎么使用lock线程锁_C# lock和Monitor线程安全教程【进阶】

热心网友
61
转载
2026-05-05

C#如何正确使用lock线程锁?C# lock与Monitor线程安全进阶实战指南

C#怎么使用lock线程锁_C# lock和Monitor线程安全教程【进阶】

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

首先明确一个核心原则:lock 关键字并非解决所有并发问题的万能钥匙。它只有在正确选择锁对象、精确划定临界区范围并规避常见误区时,才能有效保障线程安全。简而言之,在日常开发的大多数场景中,使用 lock 足以应对;但当你需要实现线程等待、条件通知、超时控制等高级同步机制时,就必须深入理解并切换到 Monitor 类提供的底层方法

为什么不能使用 new object() 或 this 作为 lock 的锁对象?

初学者常犯的一个典型错误,是在方法内部临时创建 new object() 实例作为锁,或者为图方便直接使用 lock(this)。这两种做法本质上都无法实现线程同步,因为锁对象不具备唯一性,线程之间无法形成有效的互斥访问。

  • new object():每次调用都会生成全新的对象实例,不同线程锁定的是完全不同的对象,导致锁机制完全失效。
  • this:指向当前实例的公开引用,外部代码也可能对其进行锁定,极易引发难以调试的死锁或意外的逻辑冲突。
  • 字符串字面量:例如 lock(“myLock”),由于 .NET 的字符串驻留机制,相同的字符串可能在应用程序域内被共享,从而带来意想不到的锁竞争范围扩大风险。

那么,正确的锁对象声明方式是什么?最佳实践是定义一个 private readonly object 类型的字段。对于需要跨实例同步的静态资源,强烈建议使用 static readonly(静态只读)修饰,以确保锁对象的生命周期与所要保护的共享资源完全匹配。参考以下示例:

private static readonly object _syncRoot = new object();
public void UpdateCounter() {
    lock (_syncRoot) {
        _counter++;
    }
}

lock 关键字与 Monitor.Enter/Exit 的等价关系及核心区别

从语法层面看,lock 非常简洁,但其本质是 C# 编译器提供的语法糖。编译器会将其转换为标准的 try-finally 代码块,并调用 Monitor.Enter(..., ref lockTaken) 方法。自 .NET Framework 4.0 起,这个 ref bool 参数变得至关重要——它专门用于处理 ThreadAbortException 等极端异步中断场景,确保锁在任何情况下都能被可靠释放,避免资源泄漏。

  • lock 的优势:自动保障异常安全,编译器生成的代码确保锁最终被释放,开发者无法绕过此机制,降低了出错概率。
  • Monitor.Enter 的灵活性:它允许你通过检查 lockTaken 标志来灵活控制流程,为实现“尝试获取锁”等高级模式提供了基础。
  • 关键功能差异:如果你需要为锁的获取操作设置一个超时时间(例如最多等待100毫秒),那么必须使用 Monitor.TryEnter(obj, 100) 方法,这是 lock 关键字本身无法实现的功能。

以下代码演示了如何使用 Monitor.TryEnter 实现带超时控制的锁获取,其逻辑与 lock 等效,但赋予了程序更强的健壮性和控制力:

bool lockTaken = false;
try {
    if (Monitor.TryEnter(_syncRoot, 100)) {
        lockTaken = true;
        _counter++;
    } else {
        // 获取锁超时,可执行降级策略或记录告警日志
        throw new TimeoutException(“Failed to acquire lock within 100ms”);
    }
} finally {
    if (lockTaken) Monitor.Exit(_syncRoot);
}

为什么 Monitor.Wait/Pulse 不能与 lock 关键字混合使用?

许多开发者误以为在 lock 代码块内调用 Monitor.Wait 可以“智能地”释放锁并进入等待状态,结果往往导致程序死锁或抛出 SynchronizationLockException 异常。根本原因在于:Wait 方法要求调用线程必须已经持有目标对象的锁,并且它会原子性地执行“释放锁”和“进入对象等待队列”两个操作。而 lock 块在退出时会自动调用 Monitor.Exit,这个时机与 Wait 的释放机制相冲突,破坏了同步的原子性

  • 必须显式管理锁:要正确配合使用 WaitPulse(或 PulseAll),就必须放弃 lock 关键字,转而使用 Monitor.EnterMonitor.Exit 进行显式的锁生命周期管理。
  • Wait 的调用前提:只能在当前线程已成功获取锁之后调用,否则将立即抛出异常。
  • Pulse 的信号特性:它仅通知目标对象等待队列中的单个线程(若有),而不会唤醒正在运行的线程;如果此时等待队列为空,该通知信号将被直接丢弃。

在经典的生产者-消费者模式中,消费者的 Get 方法必须采用如下显式写法(无法使用 lock 语法糖):

public T Get() {
    Monitor.Enter(_syncRoot);
    try {
        while (_queue.Count == 0) {
            Monitor.Wait(_syncRoot); // 原子性地释放锁并进入等待状态
        }
        return _queue.Dequeue();
    } finally {
        Monitor.Exit(_syncRoot);
    }
}

锁的性能影响与高并发场景下的替代方案选择

需要明确的是,锁操作本身的开销很小,真正的性能损耗来源于“锁竞争”。当大量线程频繁争抢同一把锁时,lock 会导致线程串行化执行,反而降低 CPU 的利用效率。因此,在设计并发方案时,首先应评估:当前场景是否真的需要独占访问?

  • 读多写少场景:如果业务以高频读取为主,偶尔伴随写入,那么 ReaderWriterLockSlim 的性能远胜于简单的 lock,它允许多个线程并发读,写入时独占。
  • 简单的原子操作:如果仅仅是保护一个整数的递增、递减或比较交换,使用 Interlocked.Increment(ref _counter) 等原子操作可以实现真正的无锁化,性能比使用锁高出数个数量级。
  • 并发集合类:对于队列、字典等集合操作,应优先考虑 System.Collections.Concurrent 命名空间下的 ConcurrentQueueConcurrentDictionary 等线程安全集合,它们内部采用了精巧的无锁算法或细粒度锁,性能更优。
  • 跨进程同步需求lockMonitor 仅适用于单个进程内的线程同步。若需实现跨进程的同步,必须使用操作系统原语,如 Mutex(互斥体)或命名 Semaphore

另一个至关重要的优化点是锁的粒度控制。切忌将整个方法体都用 lock 包裹。应当精确锁定那些真正访问共享状态的关键代码行。例如,在写入日志前进行的字符串拼接、时间格式化等非共享计算,完全可以在锁外部预先完成,这能显著缩短锁的持有时间,大幅提升系统的整体并发吞吐量。

来源:https://www.php.cn/faq/2334169.html
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

相关攻略

c#如何绘制图形_c#绘制图形的正确用法与注意事项
编程语言
c#如何绘制图形_c#绘制图形的正确用法与注意事项

C 绘图避坑指南:从Graphics来源到DPI适配的实战要点 在C 中进行图形绘制,一个看似简单的DrawRectangle背后,往往藏着好几个“坑”。Graphics对象不能直接new,否则要么直接报错,要么静默失败——所有绘图操作都必须基于合法的来源。这可以说是入门绘图的第一条铁律。 Grap

热心网友
05.05
VSCode怎么搭建Unity 3D的C#脚本编写环境并解决找不到引用的问题
编程语言
VSCode怎么搭建Unity 3D的C#脚本编写环境并解决找不到引用的问题

VSCode怎么搭建Unity 3D的C 脚本编写环境并解决找不到引用的问题 在Unity开发中,用VSCode写C 脚本时遇到“找不到引用”的红色波浪线,这事儿确实挺让人头疼的。别急,这通常不是代码逻辑问题,而是开发环境之间的“沟通”出了岔子。下面咱们就来逐一拆解最常见的几个原因和对应的解决方案。

热心网友
05.04
C#如何使用Record类型_C#不可变数据模型特性解析【极简】
编程语言
C#如何使用Record类型_C#不可变数据模型特性解析【极简】

C Record类型:不可变数据容器的正确打开方式 先明确一个核心认知:C 中的Record类型,本质上是一个“省心”的不可变数据容器。它不是什么更高级的class,而是编译器帮你自动生成值相等性、ToString、GetHashCode以及with表达式的语法糖。用对了,它能帮你省掉80%的数据

热心网友
05.03
C#如何获取硬件信息_C# WMI读取CPU与硬盘序列号【进阶】
编程语言
C#如何获取硬件信息_C# WMI读取CPU与硬盘序列号【进阶】

WMI无法稳定读取现代CPU与NVMe硬盘序列号?问题不在代码,而在硬件与系统本身 一个常见的开发误区是:用WMI读取CPU和硬盘序列号,结果发现拿不到、拿不准或者拿到一堆乱码。问题往往不在于你的代码写错了,而是系统或固件层面,压根就没把这个“身份证号”暴露给你。 为什么 Win32_Process

热心网友
05.02
C#怎么防止UI线程假死_C#耗时操作放入后台线程更新UI【核心】
编程语言
C#怎么防止UI线程假死_C#耗时操作放入后台线程更新UI【核心】

C 怎么防止UI线程假死_C 耗时操作放入后台线程更新UI【核心】 耗时操作必须离开 UI 线程,否则假死不可避免 —— 这不是优化建议,而是 WinForms WPF 的运行铁律。 为什么直接在 Button_Click 里调用 Thread Sleep 就卡死? 道理其实很简单:UI 线程身兼数

热心网友
05.01

最新APP

宝宝过生日
宝宝过生日
应用辅助 04-07
台球世界
台球世界
体育竞技 04-07
解绳子
解绳子
休闲益智 04-07
骑兵冲突
骑兵冲突
棋牌策略 04-07
三国真龙传
三国真龙传
角色扮演 04-07

热门推荐

冬季防火标语
职业与学业
冬季防火标语

构筑消防安全“防火墙”工程 提升全社会火灾防控综合能力 消防安全绝非一句空洞的口号,它直接关系到千家万户的生命财产安全,是社会稳定与经济发展的坚实保障。全面提升社会火灾防控水平,是一项需要全民参与、持续发力的系统性工程。以下汇集自不同领域的防火警示与实用提醒,为我们提供了直观而深刻的行动指南。 森林

热心网友
05.05
防火宣传标语(80条)
职业与学业
防火宣传标语(80条)

防火宣传标语(1-20) 1 全民总动员,防火保安全。 2 全民护林、人人防火。 3 一人把关一处安,众人防火稳如山。 4 时时注意森林防火、人人重视森林防火。 5 森林防火记心上,人人护林理应当。 6 山田年年耕、防火天天讲。 7 保护消防设施,维护消防安全。 8 入山不带烟、野外

热心网友
05.05
森林防火标语手抄报图片文案
职业与学业
森林防火标语手抄报图片文案

森林防火标语手抄报图片文案 “坚持生态效益、经济效益、社会效益相结合,突出生态效益。”这句话点明了现代林业发展的核心。如今信息传播触手可及,我们每天都能接触到海量内容,其中那些简洁有力、直击人心的句子,往往最能留下深刻印象。你是否也有收集和分享精彩语句的习惯?下面整理的这份森林防火标语集锦,或许能为

热心网友
05.05
欧交易所最新版app下载安装地址2025版
web3.0
欧交易所最新版app下载安装地址2025版

欧交易所作为全球领先的数字资产服务平台,为广大用户提供多样化的数字产品交易与金融服务。其官方应用程序设计友好,操作便捷,致力于为用户创造一个安全、稳定的交易环境。 这份指南将手把手带你完成欧交易所2025最新版App的官方下载与安装。文内提供的链接直达官方渠道,确保你的每一步操作都安全可靠。 下载教

热心网友
05.05
森林防火标语大全图片文案34句
职业与学业
森林防火标语大全图片文案34句

森林防火标语大全图片文案【篇1】 一棵树木长成参天大树,需要历经数十年的风雨洗礼,成长过程极为不易。请务必牢记,切勿让任何火源进入林区,共同守护这片绿色。 我们关心天下大事,更应心系家园安全,用行动联通守护的责任。 清明祭祖,如今更倡导以鲜花、植树等文明、环保的方式寄托哀思,摒弃焚烧纸钱旧俗,让清明

热心网友
05.05