对于稍微有点经验的.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<!--CRLF-->
2: { <!--CRLF-->
3: public void Execute()<!--CRLF-->
4: { <!--CRLF-->
5: Console.WriteLine("Excute at {0}", DateTime.Now);<!--CRLF-->
6: Thread.Sleep(5000); <!--CRLF-->
7: } <!--CRLF-->
8: } <!--CRLF-->
在入口Main方法中,创建SyncHelper对象,通过一个System.Threading.Timer对象实现每隔1s调用该对象的Execute方法:
1: class Program<!--CRLF-->
2: { <!--CRLF-->
3: static void Main(string[] args)<!--CRLF-->
4: { <!--CRLF-->
5: SyncHelper helper = new SyncHelper();<!--CRLF-->
6: Timer timer = new Timer(<!--CRLF-->
7: delegate<!--CRLF-->
8: { <!--CRLF-->
9: helper.Execute(); <!--CRLF-->
10: }, null, 0, 1000);<!--CRLF-->
11: <!--CRLF-->
12: Console.Read(); <!--CRLF-->
13: <!--CRLF-->
14: } <!--CRLF-->
15: } <!--CRLF-->
16: <!--CRLF-->
由于Timer对象采用异步的方式进行调用,所以虽然Execute方法的执行时间是5s,但是该方法仍然是每隔1s被执行一次。这一点从最终执行的结果可以看出:
为了让同一个SyncHelper对象的Execute方法同步执行,我们在Execute方法上添加了如下一个MethodImplAttribute:
1: [MethodImpl(MethodImplOptions.Synchronized)] <!--CRLF-->
2: public void Execute()<!--CRLF-->
3: { <!--CRLF-->
4: Console.WriteLine("Excute at {0}", DateTime.Now);<!--CRLF-->
5: Thread.Sleep(5000); <!--CRLF-->
6: } <!--CRLF-->
从如下的输出结果我们可以看出Execute方法是以同步的方式执行的,因为两次执行的间隔正式Execute方法执行的时间:
在一开始我们提出的结论中,我们提到“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到instance method,相当于对当前实例加锁”。说得直白一点:[MethodImplAttribute(MethodImplOptions.Synchronized)] = lock(this)。我们可以通过下面的实验验证这一点。为此,在SyncHelper中定义了一个方法LockMyself。在此方法中对自身加锁,并持续5s中,并答应加锁和解锁的时间。
1: public void LockMyself()<!--CRLF-->
2: { <!--CRLF-->
3: lock (this)<!--CRLF-->
4: { <!--CRLF-->
5: Console.WriteLine("Lock myself at {0}", DateTime.Now);<!--CRLF-->
6: Thread.Sleep(5000); <!--CRLF-->
7: Console.WriteLine("Unlock myself at {0}", DateTime.Now);<!--CRLF-->
8: } <!--CRLF-->
9: } <!--CRLF-->
我们在Main()中以异步的方式(通过创建新的线程的方式)调用该方法:
1: static void Main(string[] args)<!--CRLF-->
2: { <!--CRLF-->
3: SyncHelper helper = new SyncHelper();<!--CRLF-->
4: <!--CRLF-->
5: Thread thread = new Thread(<!--CRLF-->
6: delegate()<!--CRLF-->
7: { <!--CRLF-->
8: <!--CRLF-->
9: helper.LockMyself(); <!--CRLF-->
10: <!--CRLF-->
11: }); <!--CRLF-->
12: thread.Start(); <!--CRLF-->
13: Timer timer = new Timer(<!--CRLF-->
14: delegate<!--CRLF-->
15: { <!--CRLF-->
16: helper.Execute(); <!--CRLF-->
17: }, null, 0, 1000);<!--CRLF-->
18: <!--CRLF-->
19: Console.Read(); <!--CRLF-->
20: } <!--CRLF-->
结合我们的第二个结论想想最终的输出会是如何。由于LockMyself方法是在另一个线程中执行,我们可以简单讲该方法的执行和Execute的第一个次执行看作是同时的。但是MethodImplAttribute(MethodImplOptions.Synchronized)]果真是通过lock(this)的方式实现的话,Execute必须在等待LockMyself方法执行结束将对自身的锁释放后才能得以执行。也就是说LockMyself和第一次Execute方法的执行应该相差5s。而输出的结果证实了这点:
三、基于static method的线程同步
讨论完再instance method上添加MethodImplAttribute(MethodImplOptions.Synchronized)]的情况,我们相同的方式来讨论倘若一样的MethodImplAttribute被应用到static方法,又会使怎样的结果。
我们先将Execute方法上的MethodImplAttribute注释掉,并将其改为static方法:
1: //[MethodImpl(MethodImplOptions.Synchronized)]<!--CRLF-->
2: public static void Execute()<!--CRLF-->
3: { <!--CRLF-->
4: Console.WriteLine("Excute at {0}", DateTime.Now);<!--CRLF-->
5: Thread.Sleep(5000); <!--CRLF-->
6: } <!--CRLF-->
在Main方法中,通过Timer调用该static方法:
1: static void Main(string[] args)<!--CRLF-->
2: { <!--CRLF-->
3: Timer timer = new Timer(<!--CRLF-->
4: delegate<!--CRLF-->
5: { <!--CRLF-->
6: SyncHelper.Execute(); <!--CRLF-->
7: }, null, 0, 1000);<!--CRLF-->
8: <!--CRLF-->
9: Console.Read(); <!--CRLF-->
10: } <!--CRLF-->
毫无疑问,Execute方法将以1s的间隔异步地执行,最终的输出结果如下:
然后我们将对[MethodImpl(MethodImplOptions.Synchronized)]的注释取消:
1: [MethodImpl(MethodImplOptions.Synchronized)] <!--CRLF-->
2: public static void Execute()<!--CRLF-->
3: { <!--CRLF-->
4: Console.WriteLine("Excute at {0}", DateTime.Now);<!--CRLF-->
5: Thread.Sleep(5000); <!--CRLF-->
6: } <!--CRLF-->
最终的输出结果证实了Execute将会按照我们期望的那样以同步的方式执行,执行的间隔正是方法执行的时间:
我们回顾一下第三个结论:“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到static method,相当于当前类型加锁”。为了验证这个结论,在SyncHelper中添加了一个新的static方法:LockType。该方法对SyncHelper tpye加锁,并持续5s中,在加锁和解锁是打印出当前时间:
1: public static void LockType()<!--CRLF-->
2: { <!--CRLF-->
3: lock (typeof(SyncHelper))<!--CRLF-->
4: { <!--CRLF-->
5: Console.WriteLine("Lock SyncHelper type at {0}", DateTime.Now);<!--CRLF-->
6: Thread.Sleep(5000); <!--CRLF-->
7: Console.WriteLine("Unlock SyncHelper type at {0}", DateTime.Now);<!--CRLF-->
8: } <!--CRLF-->
9: } <!--CRLF-->
在Main中,像验证instance method一样,创建新的线程执行LockType方法:
1: static void Main(string[] args)<!--CRLF-->
2: { <!--CRLF-->
3: Thread thread = new Thread(<!--CRLF-->
4: delegate()<!--CRLF-->
5: { <!--CRLF-->
6: SyncHelper.LockType(); <!--CRLF-->
本文探讨了.NET中使用MethodImplAttribute实现线程同步的方法。详细分析了MethodImplAttribute(MethodImplOptions.Synchronized)在实例方法和静态方法上的应用效果,并通过实验验证了其与lock关键字的关系。





6539

被折叠的 条评论
为什么被折叠?



