lock
关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。
lock (thisLock)
{
// Critical code section.
}
lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
C# 中的 Lock 语句通过隐式使用 Monitor 来提供同步功能。lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。
误区:
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。 常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
- 如果实例可以被公共访问,将出现 lock (this) 问题。
- 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
- 由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock(“myLock”) 问题。
最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。
作用:
在多线程中,为了使数据保持一致性,可以使用Monitor类、Lock和Mutex类来同步多线程的执行,但是常用的比较简单的则是,使用lock要对数据或是访问数据的函数加锁。
在进行web开发的时候,若你没有用到多线程,但是对于一些共用的方法,特别是静态方法,有的时候也要加锁,因为这种B/S结构本身也是多线程的。如果多个IE同时调用一个静态方法,就会产生冲突。
警示:尽管lock可以保证数据和逻辑的一致性,但是不能滥用,因为lock本身也是一种资源消耗。
小实例、新发现
下面我通过一个小例子来介绍lock的用法,在这个小例子中,我们可以悟出一些新的道理,具体的实例代码如下:
using System.Threading;
namespace TestLock
{
public class Work
{
private volatile bool requestStop = false;
private object locker = new object();
private object locker2 = new object();
/// <summary>
/// 写操作
/// </summary>
public void Write()
{
lock (locker)
{
while (!requestStop)
{
Console.WriteLine("this Program is now Writing!");
for (int i = 0; i < 10; i++)
{
Console.WriteLine("The write result is {0}", i);
}
}
}
}
/// <summary>
/// 读操作
/// </summary>
public void Read()
{
lock (locker)
{
while (!requestStop)
{
Console.WriteLine("this Program is Now Reading!");
for (int i = 0; i < 10; i++)
{
Console.WriteLine("The read result is {0}", i);
}
}
}
}
/// <summary>
/// 终止信号
/// </summary>
public void RequestStop()
{
requestStop = true;
}
}
class Program
{
static void Main(string[] args)
{
Work worker = new Work();
Thread wThread = new Thread(new ThreadStart(worker.Write));
Thread rThread = new Thread(new ThreadStart(worker.Read));
wThread.Start();
rThread.Start();
Thread.Sleep(1000);
worker.RequestStop();
wThread.Join();
rThread.Join();
Console.WriteLine("All works have been done!");
Console.ReadKey();
}
}
}
这是最开始的设计,操作类有两个具体的操作:Write()和Read(),但是只要换下锁的对象、锁的位置,将会出现不同结果。
1、现在使用了锁,而且锁的对象是相同的。其结果很费解:只有写操作。这也说明了如果锁对象相同,会直接影响到另一个操作。如果将Read()的锁改为locker2则不会出现这种情况。
2、同一个锁,但是锁在while内,代码如下:
输出结果:
3、不同锁对象,如果将Read()的锁对象改为locker2,如下:
结论:
如果锁对象相同,操作之间互相影响,能够某一操作的完整,一个操作只能等待另一个操作完成之后才能执行,Write()和Read()不会出现掺杂,如上图1、2所示。
如果锁对象不同,或者不加锁,则两个操作的执行是相互对立的,Write()和Read()的结果出现掺杂,如3所示
总结:
1、如果两个操作是相互影响的,比如读写一个文件,只能允许一个执行,则锁对象应该相同。
因为lock隐含是Monitor, Monitor类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。
2、如果两个操作是互不相干的,则其锁对象应该不同,如若采用同一个锁,将直接影响其它操作的执行。
在我们开发过程中,经常为了省事,而只创建一个锁对象,还有的在基类创建一个锁,由子类共用,这是在系统架构中采用工厂模式,经常出现的误区。如果是互不相干的操作,一个操作的执行必须等待另一个操作结束之后才能执行,必然受到了该锁的影响,大大降低了系统的性能,有时候会造成死锁。
3、lock本身也有系统损耗。
lock本身也需要利用资源,所以不必要的锁会降低系统的性能。在这个试验里,加了锁和不加锁,执行的结果不一样,加了锁输出的结果会缩短。这个你自己也可以写一个小例子进行测试。所以使用锁一定要慎重,不能滥用。