news 2026/7/26 3:53:27

简单来讲讲C#中的锁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
简单来讲讲C#中的锁

简单来讲讲C#中的锁

在多线程编程中,锁是一种用于控制多个线程对共享资源访问的机制。C#作为一门支持多线程的编程语言,提供了多种锁的实现方式,帮助开发者避免数据竞争、死锁等问题。本文将从基础概念讲起,逐步深入到高级用法,并通过完整代码示例让你轻松掌握C#中的锁。## 什么是锁?为什么需要锁?当多个线程同时访问共享数据(如全局变量、文件、数据库连接等)时,可能会发生“竞争条件”(Race Condition)。例如,两个线程同时向一个整数变量加1,由于操作不是原子的,最终结果可能比预期小。锁的作用就是确保同一时间内只有一个线程能访问被保护的代码段,从而保证数据的一致性。锁的基本原理是:当一个线程试图进入被锁保护的代码块时,它必须先获得锁;如果锁已被其他线程持有,则该线程会等待,直到锁被释放。## 基础锁:lock语句C#中最简单的锁是lock语句。它基于Monitor类实现,用于同步对共享资源的访问。### 示例1:使用lock保护共享变量csharpusing System;using System.Threading;class Program{ // 共享资源:一个计数器 private static int counter = 0; // 锁对象:必须是一个引用类型,不能为null private static readonly object lockObject = new object(); static void Main() { // 创建两个线程同时递增计数器 Thread t1 = new Thread(IncrementCounter); Thread t2 = new Thread(IncrementCounter); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine($"最终计数器值: {counter}"); // 预期输出10000 } static void IncrementCounter() { for (int i = 0; i < 5000; i++) { // 使用lock确保原子操作 lock (lockObject) { counter++; } } }}代码说明:-lockObject是一个专用对象,用于锁定。它必须是引用类型(如object),且通常声明为static readonly以确保唯一性。- 在lock块内,counter++是原子化的,不会被其他线程中断。- 如果没有lock,两个线程同时执行counter++可能导致结果小于10000。###lock的注意事项- 不要锁值类型(如int),因为它们会装箱,导致每次锁的对象不同。- 不要锁字符串,因为字符串可能被池化,导致意外共享。- 锁的范围应尽可能小,避免长时间持有锁。## 进阶用法:Monitorlock语句是Monitor类的简化语法。Monitor提供了更灵活的锁控制,例如超时和手动等待。### 示例2:使用Monitor实现超时锁csharpusing System;using System.Threading;class Program{ private static readonly object lockObject = new object(); private static int sharedData = 0; static void Main() { Thread t1 = new Thread(TryEnterLock); Thread t2 = new Thread(TryEnterLock); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine($"最终sharedData: {sharedData}"); } static void TryEnterLock() { bool lockTaken = false; try { // 尝试在1秒内获取锁,如果超时则放弃 Monitor.TryEnter(lockObject, TimeSpan.FromSeconds(1), ref lockTaken); if (lockTaken) { Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 获得了锁"); // 模拟耗时操作 Thread.Sleep(500); sharedData++; Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 完成了操作"); } else { Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 获取锁超时"); } } finally { if (lockTaken) { Monitor.Exit(lockObject); } } }}代码说明:-Monitor.TryEnter允许设置超时时间,避免线程无限等待。- 使用try-finally确保锁总是被释放,即使发生异常。- 此例中,一个线程获得锁后睡眠500毫秒,另一个线程尝试获取锁但超时(因为总超时1秒,而锁持有时间500毫秒,实际可能成功,但设计上展示了超时机制)。## 高级用法:ReaderWriterLockSlim当读操作远多于写操作时,使用lock会导致所有读取线程互相阻塞,影响性能。ReaderWriterLockSlim允许多个线程同时读取,但写操作独占。### 示例3:使用ReaderWriterLockSlim优化读写csharpusing System;using System.Threading;class Program{ private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(); private static int sharedData = 0; static void Main() { // 创建3个读线程和2个写线程 for (int i = 0; i < 3; i++) { int threadId = i; new Thread(() => Reader(threadId)).Start(); } for (int i = 0; i < 2; i++) { int threadId = i; new Thread(() => Writer(threadId)).Start(); } Thread.Sleep(2000); // 等待线程执行 } static void Reader(int id) { rwLock.EnterReadLock(); try { Console.WriteLine($"读取线程{id} 读取到值: {sharedData}"); Thread.Sleep(100); // 模拟读取耗时 } finally { rwLock.ExitReadLock(); } } static void Writer(int id) { rwLock.EnterWriteLock(); try { sharedData++; Console.WriteLine($"写入线程{id} 写入新值: {sharedData}"); Thread.Sleep(200); // 模拟写入耗时 } finally { rwLock.ExitWriteLock(); } }}代码说明:- 多个读线程可以同时进入读锁,不会互相阻塞。- 写线程需要独占写锁,此时所有读线程和其他写线程都会等待。- 这种锁适用于缓存、配置等读多写少的场景。## 高级用法:SpinLock``SpinLock是一种轻量级锁,适用于锁持有时间极短的场景。它不会让线程进入休眠(即不进行上下文切换),而是让线程自旋等待,从而减少开销。### 示例4:使用SpinLock````csharpusing System;using System.Threading;class Program{ private static SpinLock spinLock = new SpinLock(); private static int counter = 0; static void Main() { Thread t1 = new Thread(Increment); Thread t2 = new Thread(Increment); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine($"最终counter: {counter}"); } static void Increment() { bool lockTaken = false; for (int i = 0; i < 10000; i++) { try { spinLock.Enter(ref lockTaken); counter++; } finally { if (lockTaken) spinLock.Exit(); lockTaken = false; } } }}```**代码说明:**-SpinLock需要显式调用EnterExit,且必须传递ref bool参数。- 由于自旋会占用CPU时间,不适合长时间等待的场景(如I/O操作)。- 在.NET Core/.NET 5+中,SpinLock性能较好,但需谨慎使用。## 总结C#中的锁是多线程编程的核心工具。从简单的lock语句到高级的ReaderWriterLockSlimSpinLock,每种锁都有其适用场景:- **lock**:最常用,适合大多数同步需求,语法简洁。- **Monitor**:提供超时等高级控制,适合需要避免死锁的场景。- **ReaderWriterLockSlim**:读多写少场景下的性能优化。- **SpinLock**:极短等待时间下的轻量级方案。使用锁时,务必遵循“最小化锁范围”原则,避免死锁(如多个锁嵌套时注意顺序),并优先考虑更高级的同步原语(如ConcurrentDictionarySemaphoreSlim` 等)。掌握这些锁的用法,你就能在C#中构建稳定高效的多线程程序了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/26 3:51:25

深入解析MCAN Message RAM配置与LIN SCI模式实战避坑指南

1. 项目概述与核心价值在汽车电子和工业控制领域&#xff0c;控制器局域网&#xff08;CAN&#xff09;和本地互联网络&#xff08;LIN&#xff09;是构建可靠、低成本分布式系统的两大基石。前者以其高可靠性和实时性&#xff0c;成为动力总成、底盘控制等关键系统的首选&…

作者头像 李华
网站建设 2026/7/26 3:46:23

Android ROM解包终极指南:支持10+格式的一键解包工具

Android ROM解包终极指南&#xff1a;支持10格式的一键解包工具 【免费下载链接】unpackandroidrom 爬虫解包 Android ROM 项目地址: https://gitcode.com/gh_mirrors/un/unpackandroidrom 在Android开发和ROM定制领域&#xff0c;面对五花八门的固件格式&#xff0c;您…

作者头像 李华
网站建设 2026/7/26 3:43:56

Linux命令行参数与环境变量解析及内存管理实践

1. Linux命令行参数与环境变量解析在Linux系统编程中&#xff0c;命令行参数和环境变量是程序与操作系统交互的重要桥梁。每次我们在终端输入命令时&#xff0c;比如ls -l /home&#xff0c;其实都是在传递命令行参数。而环境变量则像是程序的"背景设置"&#xff0c;…

作者头像 李华
网站建设 2026/7/26 3:43:34

DyberPet桌面宠物框架:你的数字伙伴养成指南 [特殊字符]

DyberPet桌面宠物框架&#xff1a;你的数字伙伴养成指南 &#x1f43e; 【免费下载链接】DyberPet Desktop Cyber Pet Framework based on PySide6 项目地址: https://gitcode.com/GitHub_Trending/dy/DyberPet 你是否曾梦想过让心爱的动漫角色常驻桌面&#xff0c;与你…

作者头像 李华