lock 关键字和 Monitor 类都是在 C# 中用于实现互斥锁(Mutex)的机制,用于确保多个线程之间的同步访问共享资源(主要目的是防止多个线程同时修改共享资源,从而避免数据不一致和竞态条件)其中lock关键字主要用于简化对 Monitor 类的使用。lock 语句在进入块时获得指定对象的互斥锁,并在离开块时释放锁。若无需要更精细控制锁的情况,使用lock关键字即可。
使用案例:
abstract class CounterBase
{
public abstract void Increment();
public abstract void Decrement();
}
class CounterWithLock:CounterBase
{
private readonly object _syncRoot = new object();
public int Count { get; private set;}
public override void Increment()
{
lock (_syncRoot)
Count++;
}
public override void Decrement()
{
lock (_syncRoot)
Count--;
}
}
class Counter : CounterBase
{
public int Count { get; private set;}
public override void Increment()
{
Count++;
}
public override void Decrement()
{
Count--;
}
这里主要定义了几个类Counter和CounterWithLock,其中后者对某些资源用lock关键字添加了锁。
接下来在Main函数里进行如下试验:
static void Main(string[] args)
{
//不合理计数
WriteLine("Incorrect counter");
var c = new Counter();
var t1 = new Thread(() => TestCounter(c));
var t2 = new Thread(() => TestCounter(c));
var t3 = new Thread(() => TestCounter(c));
t1.Start();
t2.Start();
t3.Start();
//主线程等待三个线程执行完毕
t1.Join();
t2.Join();
t3.Join();
WriteLine($"Total count: {c.Count}");
WriteLine("----------------------------------");
//合理计数
WriteLine("Correct counter");
var d = new CounterWithLock();
var t4 = new Thread(() => TestCounter(d));
var t5 = new Thread(() => TestCounter(d));
var t6 = new Thread(() => TestCounter(d));
t4.Start();
t5.Start();
t6.Start();
//主线程等待三个线程执行完毕
t4.Join();
t5.Join();
t6.Join();
WriteLine($"Total count: {d.Count}");
}
static void TestCounter(CounterBase c)
{
for (int i = 0; i < 10000000; i++)
{
c.Increment();
c.Decrement();
}
}
调试结果如下:
可以看到Counter类因为不存在线程安全,导致Count值并不稳定。多个线程可能会取出同一个Count值接着进行同样的操作并写回,导致可能递增操作和递减操作的数量不匹配,这种情形被称为竞争条件,竞争条件是多线程环境中非常常见的导致错误的原因。为了确保不会发生以上情形,确保当有线程进行尝试改变Count值的时候,其他进程必须等到直到当前进程完成操作。我们可以使用lock关键字来实现这种行为,如CounterWithLock类所示。