[原创][MethodImpl(MethodImplOptions.Synchronized)]、lock(this)与lock(typeof(...))

本文探讨了.NET中使用MethodImplAttribute实现线程同步的方法,并对比了lock关键字的不同应用场景。通过实例验证MethodImplAttribute与lock(this)及lock(typeof(...))的等价性。

对于稍微有点经验的.NET开发人员来说,倘若被问及如何保持线程同步,我想很多人都能说好好几种。在众多的线程同步的可选方式中,加锁无疑是最为常用的。如果仅仅是基于方法级别的线程同步,使用System.Runtime.CompilerServices.MethodImplAttribute无疑是最为简洁的一种方式。MethodImplAttribute可以用于instance method,也可以用于static method。当在某个方法上标注了MethodImplAttribute,并指定MethodImplOptions.Synchronized参数,可以确保在不同线程中运行的该方式以同步的方式运行。我们几天来讨论MethodImplAttribute(MethodImplOptions.Synchronized)和lock的关系。

一、提出结论

在进行讨论之前,我先提出下面3个结论:

  • [MethodImplAttribute(MethodImplOptions.Synchronized)]仍然采用加锁的机制实现线程的同步。
  • 如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到instance method,相当于对当前实例加锁。
  • 如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到static method,相当于当前类型加锁

二、基于instance method的线程同步

为了验证我们上面提出的结论,我作了一个小小的例子。在一个console application中定义了一个class:SyncHelper,其中定义了一个方法Execute。打印出方法执行的时间,并休眠当前线程模拟一个耗时的操作:

   1: class SyncHelper
   2: {
   3:     public void Execute()
   4:     {
   5:         Console.WriteLine("Excute at {0}", DateTime.Now);
   6:         Thread.Sleep(5000);
   7:     }
   8: } 

在入口Main方法中,创建SyncHelper对象,通过一个System.Threading.Timer对象实现每隔1s调用该对象的Execute方法:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         SyncHelper helper = new SyncHelper();
   6:         Timer timer = new Timer(
   7:         delegate
   8:         {
   9:             helper.Execute();
  10:         }, null, 0, 1000); 
  11:  
  12:         Console.Read(); 
  13:  
  14:     }
  15: } 
  16:  

由于Timer对象采用异步的方式进行调用,所以虽然Execute方法的执行时间是5s,但是该方法仍然是每隔1s被执行一次。这一点从最终执行的结果可以看出:

image

为了让同一个SyncHelper对象的Execute方法同步执行,我们在Execute方法上添加了如下一个MethodImplAttribute:

   1: [MethodImpl(MethodImplOptions.Synchronized)]
   2: public void Execute()
   3: {
   4:     Console.WriteLine("Excute at {0}", DateTime.Now);
   5:     Thread.Sleep(5000);
   6: } 

从如下的输出结果我们可以看出Execute方法是以同步的方式执行的,因为两次执行的间隔正式Execute方法执行的时间:

image

在一开始我们提出的结论中,我们提到“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到instance method,相当于对当前实例加锁”。说得直白一点:[MethodImplAttribute(MethodImplOptions.Synchronized)] = lock(this)。我们可以通过下面的实验验证这一点。为此,在SyncHelper中定义了一个方法LockMyself。在此方法中对自身加锁,并持续5s中,并答应加锁和解锁的时间。

   1: public void LockMyself()
   2: {
   3:    lock (this)
   4:     {
   5:         Console.WriteLine("Lock myself at {0}", DateTime.Now);
   6:         Thread.Sleep(5000);
   7:         Console.WriteLine("Unlock myself at {0}", DateTime.Now);
   8:     }
   9: } 

我们在Main()中以异步的方式(通过创建新的线程的方式)调用该方法:

   1: static void Main(string[] args)
   2: {
   3:     SyncHelper helper = new SyncHelper();
   4:  
   5:     Thread thread = new Thread(
   6:         delegate()
   7:         {            
   8:  
   9:              helper.LockMyself();
  10:  
  11:         });
  12:     thread.Start();
  13:     Timer timer = new Timer(
  14:     delegate
  15:     {
  16:         helper.Execute();
  17:     }, null, 0, 1000); 
  18:  
  19:     Console.Read();
  20: } 

结合我们的第二个结论想想最终的输出会是如何。由于LockMyself方法是在另一个线程中执行,我们可以简单讲该方法的执行和Execute的第一个次执行看作是同时的。但是MethodImplAttribute(MethodImplOptions.Synchronized)]果真是通过lock(this)的方式实现的话,Execute必须在等待LockMyself方法执行结束将对自身的锁释放后才能得以执行。也就是说LockMyself和第一次Execute方法的执行应该相差5s。而输出的结果证实了这点:

image

三、基于static method的线程同步

讨论完再instance method上添加MethodImplAttribute(MethodImplOptions.Synchronized)]的情况,我们相同的方式来讨论倘若一样的MethodImplAttribute被应用到static方法,又会使怎样的结果。

我们先将Execute方法上的MethodImplAttribute注释掉,并将其改为static方法:

   1: //[MethodImpl(MethodImplOptions.Synchronized)]
   2: public static void Execute()
   3: {
   4:     Console.WriteLine("Excute at {0}", DateTime.Now);
   5:     Thread.Sleep(5000);
   6: } 

在Main方法中,通过Timer调用该static方法:

   1: static void Main(string[] args)
   2: {
   3:     Timer timer = new Timer(
   4:     delegate
   5:     {
   6:         SyncHelper.Execute();
   7:     }, null, 0, 1000); 
   8:  
   9:     Console.Read();
  10: } 

毫无疑问,Execute方法将以1s的间隔异步地执行,最终的输出结果如下:

image

然后我们将对[MethodImpl(MethodImplOptions.Synchronized)]的注释取消:

   1: [MethodImpl(MethodImplOptions.Synchronized)]
   2: public static void Execute()
   3: {
   4:     Console.WriteLine("Excute at {0}", DateTime.Now);
   5:     Thread.Sleep(5000);
   6: } 

最终的输出结果证实了Execute将会按照我们期望的那样以同步的方式执行,执行的间隔正是方法执行的时间:

image

我们回顾一下第三个结论:“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到static method,相当于当前类型加锁”。为了验证这个结论,在SyncHelper中添加了一个新的static方法:LockType。该方法对SyncHelper tpye加锁,并持续5s中,在加锁和解锁是打印出当前时间:

   1: public static void LockType()
   2: {
   3:     lock (typeof(SyncHelper))
   4:     {
   5:         Console.WriteLine("Lock SyncHelper type at {0}", DateTime.Now);
   6:         Thread.Sleep(5000);
   7:         Console.WriteLine("Unlock SyncHelper type at {0}", DateTime.Now);
   8:     }
   9: } 

在Main中,像验证instance method一样,创建新的线程执行LockType方法:

   1: static void Main(string[] args)
   2: {
   3:     Thread thread = new Thread(
   4:         delegate()
   5:         {
   6:             SyncHelper.LockType();
   7:         });
   8:     thread.Start(); 
   9:  
  10:     Timer timer = new Timer(
  11:     delegate
  12:     {
  13:         SyncHelper.Execute();
  14:     }, null, 0, 1000); 
  15:  
  16:     Console.Read();
  17: } 
  18:  

如果基于static method的[MethodImplAttribute(MethodImplOptions.Synchronized)]是通过对Type进行加锁实现。那么通过Timer轮询的第一个Execute方法需要在LockType方法执行完成将对SyncHelper type的锁释放后才能执行。所以如果上述的结论成立,将会有下面的输出:

image

四、总结

对于加锁来说,锁的粒度的选择显得至关重要。在不同的场景中需要选择不同粒度的锁。如果选择错误往往会对性能造成很到的影响,严重时还会引起死锁。就拿[MethodImplAttribute(MethodImplOptions.Synchronized)]来说,如果开发人员对它的实现机制不了解,很有可能使它lock(this)或者lock(typeof(…))并存,造成方法得不到及时地执行。

最后说一句题外话,因为字符串驻留机制的存在,切忌对string进行加锁。


<think>我们正在讨论C#中的线程同步方法。根据引用[1]到[4]的内容,线程同步是为了协调多个线程对共享资源的访问,避免数据不一致和竞态条件。下面我将详细介绍C#中实现线程同步的几种主要方法,并参考之前的引用内容。 引用[1]提到线程间数据同步的必要性,引用[2]展示了使用`MethodImplOptions.Synchronized`实现方法同步,引用[3]和[4]则分别介绍了使用lock语句和锁的概念。 因此,我将重点介绍以下线程同步方法: 1. lock关键字(互斥锁) 2. MethodImplOptions.Synchronized特性(方法同步) 3. Monitor类(lock的底层实现) 4. Mutex(互斥体,可用于跨进程同步) 5. Semaphore(信号量,控制同时访问的线程数) 6. 其他同步机制如ReaderWriterLockSlim、AutoResetEvent/ManualResetEvent等 注意:每个代码示例都需要使用正确的语法,并标注引用来源(如果引用中提到的话)。由于引用中并没有提供所有方法的示例,因此我将结合引用和C#标准知识来编写。 另外,根据要求,我们需要在回答末尾生成相关问题。 现在开始组织回答: </think>### C# 中实现线程同步的方法 线程同步是协调多线程访问共享资源的关键机制,防止并发操作导致的数据损坏或逻辑错误。以下是 C# 中常用的线程同步方法,结合引用内容详细说明: --- #### 1. **`lock` 关键字(互斥锁)** 通过独占访问确保代码块原子性执行,是最常用的同步机制[^3][^4]: ```csharp private static readonly object _lockObj = new object(); // 同步对象(必须引用类型) void CriticalSection() { lock (_lockObj) // 进入锁 { // 共享资源操作(如修改集合) sharedList.Add(DateTime.Now); } // 自动释放锁 } ``` - **原理**:基于 `Monitor.Enter()` 和 `Monitor.Exit()` 实现,确保同一时刻仅一个线程执行临界区代码。 - **适用场景**:简单共享变量的读写、集合操作。 - **注意**:同步对象需声明为 `private readonly` 避免外部干扰[^4]。 --- #### 2. **`MethodImplOptions.Synchronized` 特性** 标记方法自动实现实例级或类级同步[^2]: ```csharp [MethodImpl(MethodImplOptions.Synchronized)] public static void Add(List<DateTime> list) { list.Add(DateTime.Now); // 自动同步 } ``` - **实例方法**:锁定当前对象实例(等效 `lock(this)`)。 - **静态方法**:锁定类型对象(等效 `lock(typeof(Class))`)。 - **局限**:可能因锁定范围过大导致性能问题,通常优先使用 `lock`[^2]。 --- #### 3. **`Monitor` 类(高级锁控制)** `lock` 的底层实现,提供更灵活的锁管理: ```csharp Monitor.Enter(_lockObj); try { // 临界区代码 } finally { Monitor.Exit(_lockObj); // 确保释放锁 } ``` - **优势**:支持超时控制(`Monitor.TryEnter(timeout)`),避免死锁。 - **扩展功能**:配合 `Monitor.Wait()` 和 `Monitor.Pulse()` 实现线程等待/唤醒。 --- #### 4. **信号量(`Semaphore` / `SemaphoreSlim`)** 控制并发访问线程数量,适用于资源池场景[^3][^4]: ```csharp private static SemaphoreSlim _semaphore = new SemaphoreSlim(3); // 允许3个线程并发 void AccessResource() { _semaphore.Wait(); // 获取信号量 try { // 受保护操作 } finally { _semaphore.Release(); // 释放信号量 } } ``` - **对比互斥锁**:互斥锁是信号量容量为1的特例($n=1$)。 - **轻量版**:`SemaphoreSlim` 性能优于 `Semaphore`(进程内使用)。 --- #### 5. **其他同步机制** - **`Mutex`(跨进程同步)**: 系统级互斥锁,支持跨进程同步。 - **`ReaderWriterLockSlim`**: 区分读写锁,允许多线程读、单线程写,提升读密集型性能。 - **同步事件(`AutoResetEvent`/`ManualResetEvent`)**: 通过信号机制控制线程执行顺序。 --- ### 方法选择建议 1. **简单临界区**:优先使用 `lock`(简洁安全)[^3][^4]。 2. **细粒度控制**:选择 `Monitor` 或 `ReaderWriterLockSlim`。 3. **资源池限制**:采用 `SemaphoreSlim`。 4. **跨进程同步**:使用 `Mutex`。 > 关键原则:锁定范围尽量小,避免嵌套锁以防死锁。例如在集合操作中,锁定整个集合比锁定单个元素更安全[^1][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值