C# Lock关键词及多线程锁
在这篇文章中,我们学习理解lock关键词、monitor类、mutex互斥锁和semaphore信号量的应用。
所有的这些类,如lock/monitor/mutex/semaphore,在多线程应用中,提供了一种同步机制来保护共享的代码或资源。
C# lock关键词
C#关键词:在C#中,锁lock是一种同步机制,允许在同一时间只允许一个线程访问指定的代码或区域。在多线程环境中,lock主要用于在读写公共变量的时候,执行排它锁(exclusive locks)来避免产生不一致的结果。
一般来说,lock用于关键代码段(critical section),那里同一时间内只允许一个线程访问资源,其它线程是锁住的,无法获得互斥锁,必须要等待互斥锁的释放。
lock关键词的语法
为指定代码段获取互斥锁(mutual-exclusion)的lock语法如下:
Lock (expression) { statement block }
1 //syntax to use the lock keyword 2 lock (obj) 3 { 4 // Critical code section 5 }
在C#中,lock关键词确保同一时间只有一个线程在运行。当另一个线程已经在运行的时候,lock关键词会阻止其它线程进入关键代码段。
在下面的示例中,我们在关键代码段中使用lock关键词。lock只允许同一时间内一个线程进入并执行代码。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 7 namespace TheLock 8 { 9 class Program 10 { 11 static readonly object _lock = new object(); 12 static void Main(string[] args) 13 { 14 Thread thread1 = new Thread(PrintCharacter); 15 Thread thread2 = new Thread(PrintCharacter); 16 17 thread1.Start(); 18 thread2.Start(); 19 20 Console.ReadLine(); 21 } 22 23 public static void PrintCharacter() 24 { 25 string strArray = "Hello World!"; 26 lock (_lock) 27 { 28 for (int i = 0; i < strArray.Length; i++) 29 { 30 Console.Write(strArray[i]); 31 Thread.Sleep(TimeSpan.FromSeconds(1)); 32 } 33 } 34 Console.Write(" "); 35 } 36 } 37 }
在上面 的代码中,我们在主方法main中创建了两个独立线程thread1和thread2,同时调用静态方法“PrintCharacter”。
这里,我们在变量"_lock"上使用lock关键词在 for 循环语句上获得排斥锁。
它只允许一个线程进入关键代码段去执行代码。
运行结果见上图。从上面 的结果可以看到,lock语句等待第一个线程释放了“_lock”后,才让第二个线程执行此段代码。
没有Lock关键词的多线程示例
在下面的示例中,多线程在没有lock的情况下同时执行关键代码段的代码。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 7 namespace TheLock 8 { 9 class Program 10 { 11 static readonly object _lock = new object(); 12 static void Main(string[] args) 13 { 14 Thread thread1 = new Thread(PrintCharacter); 15 Thread thread2 = new Thread(PrintCharacter); 16 17 thread1.Start(); 18 thread2.Start(); 19 20 Console.ReadLine(); 21 } 22 23 public static void PrintCharacter() 24 { 25 string strArray = "Hello World!"; 26 //lock (_lock) 27 //{ 28 for (int i = 0; i < strArray.Length; i++) 29 { 30 Console.Write(strArray[i]); 31 Thread.Sleep(TimeSpan.FromSeconds(1)); 32 } 33 //} 34 Console.WriteLine(); 35 } 36 } 37 }
在上面结果中,我们看到thread1和thread2同时执行了相同的代码段。
这就是为什么,当我们在多线程环境中读写公共变量的时候,我们要用lock执行排它锁来避免产生不一致的结果。
在C#中,lock语句内部包含Monitor.Enter方法和Monitor.Exit方法,还有try/finally语句块。
lock = Monitor.Enter + Monitor.Exit + try/finally
在上面 的项目中,我们用object实例做为lock语句的参数。但是,如果我们在lock语句中不是使用object类型,而是使用值类型会发生什么呢。很不幸,C#编译器将会抛出编译错误信息,如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 7 namespace TheLock 8 { 9 class Program 10 { 11 static int _lock ; 12 static void Main(string[] args) 13 { 14 Thread thread1 = new Thread(PrintCharacter); 15 Thread thread2 = new Thread(PrintCharacter); 16 17 thread1.Start(); 18 thread2.Start(); 19 20 Console.ReadLine(); 21 } 22 23 public static void PrintCharacter() 24 { 25 string strArray = "Hello World!"; 26 lock (_lock) 27 { 28 for (int i = 0; i < strArray.Length; i++) 29 { 30 Console.Write(strArray[i]); 31 Thread.Sleep(TimeSpan.FromSeconds(1)); 32 } 33 } 34 Console.WriteLine(); 35 } 36 } 37 }
‘int’ is not a reference type as required by the lock statement.
翻译:“int”不是lock语句要求的引用类型
避免在下列情况下使用lock关键词
避免lock关键词用于值类型;
避免将“this”用于lock表达式;
不要锁定的类型
1、避免lock关键词用于值类型;
如果编译器允许lock值类型,每次都会lock到值类型的不同装箱副本,最后也没有lock到任何东西。结果就是,lock认为值类型是很多不同的对象。lock关键词用于引用类型而不是值类型,否则,编译器会报错。
2、避免将“this”用于lock表达式;
使用私有引用类型变量,而不是this,在多线程等待开启相同对象的情况下可以避免死锁。使用this做为lock语句参数是个不好的习惯,因为它会阻止整个对象,其它的代码也会被阻塞,没有理由地等待执行,还可能导致运行性能问题。
使用this会导致很多问题,因为实例是公开可访问的,会被其它很多进程访问。
3、避免在string对象上使用lock关键词
避免在string对象上使用lock关键词是因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。没有足够的知识储备,会导致死锁。
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器