在进行.NET开发时,经常会遇见如何保持线程同步的情况。在众多的线程同步的可选方式中,加锁无疑是最为常用的。如果仅仅是基于方法级别的线程同步,使用System.Runtime.CompilerServices.MethodImplAttribute无疑是最为简洁的一种方式。MethodImplAttribute可以用于instance method,也可以用于static method。当在某个方法上标注了MethodImplAttribute,并指定MethodImplOptions.Synchronized参数,可以确保在不同线程中运行的该方式以同步的方式运行。
查阅MSDN的说明:
The method can be executed by only one thread at a time. Static methods lock on the type, whereas instance methods lock on the instance. Only one thread can execute in any of the instance functions, and only one thread can execute in any of a class's static functions. 这个方法一次只能执行一个线程。静态方法锁定类型,而实例方法锁定实例。只有一个线程可以在任何一个实例函数中执行,而且只有一个线程可以在任何一个类的静态函数中执行。
可以看出:
- [MethodImplAttribute(MethodImplOptions.Synchronized)]仍然是采用加锁的机制实现线程的同步。
- 如果它被应用到instance method,相当于对当前实例加锁。
- 如果它被应用到static method,相当于当前类型加锁
可参考文章:
[MethodImpl(MethodImplOptions.Synchronized)]、lock(this)与lock(typeof(...))
- lock机制
关键字lock的作用是锁定某一代码块,让同一时间只有一个线程访问该代码块。
lock(X)
{
//需要锁定的代码....
}
那么为什么上面这段话能够锁定代码?其中的奥妙就是X这个对象,事实上X是任意一种引用类型,它在这儿起的作用就是任何线程执行到lock(X)时候,X需要独享才能运行下面的代码,若假定现在有3个线程A,B,C都执行到了lock(X)而ABC因为此时都占有X,这时ABC就要停下来排个队,一个一个使用X,从而起到在下面的代码块内只有一个线程在运行(因为此时只有一个线程独享X,其余两个在排队),所以这个X必须是所有要执行临界区域代码进程必须共有的一个资源,从而起到抑制线程的作用。
lock最需要注意的一个问题就是线程死锁,MSDN上列出了3个典型问题:
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
- 如果实例可以被公共访问,将出现 lock (this) 问题。
- 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
- 由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。
最佳做法是定义 private 对象来锁定, 或 private shared 对象变量来保护所有实例所共有的数据。
- lock的内涵(Monitor类)
另外,使用lock来实现C#线程同步,在C#编译器编译lock语句时,将其编译成了调用Monitor类。一条lock语句会被编译成调用Monitor的Enter和Exit方法。Monitor在 System.Threading命名空中。lock的功能就相当于直接调用Monitor的Entry方法,所不同的是,lock方法在结束后,会自动解除锁定,当然,在IL中是调用了Monitor的Exit方法,但在C#程序中,看起来是自动解锁的,这类似于C#中的using语句,可以自动释放数据库等资源。但如果直接在C#源程序中使用Monitor类,就必须调用Exit方法来显式地解除锁定。
示例:
Monitor.Enter(lockObj);
try
{
// 代码
}
catch(Exception e)
{
// 异常处理代码
}
finally
{
Monitor.Exit(lockObj); // 解除锁定
}
Exit方法最后在finally里调用,这样无论在方法在发生异常、返回还是正常执行,都会执行到finally,并调用Exit方法解除锁定。
Monitor类不仅可以完全取代lock语句,还可以使用TryEntry方法设置一个锁定超时。
示例:
if(Monitor.TryEntry(lockObj, 1000))
{
try
{
}
finally
{
Monitor.Exit(lockObj);
}
}
else
{
// 超时后的处理代码
}
设置锁定超时时间为1秒,即在1秒中后,lockObj还未被解锁,TryEntry方法就会返回false,如果在1秒之内,lockObj被解锁,TryEntry返回true。这种方法对于避免死锁提供了一种不错的思路。