应用场景

lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

lock语句根本使用的就是​​Monitor.Enter​​和​​Monitor.Exit​​,也就是说lock(this)时执行Monitor.Enter(this),大括号结束时执行Monitor.Exit(this)。

应用场景:经常会应用于防止多线程操作导致公用变量值出现不确定的异常,用于确保操作的安全性,比如抢购。

锁等于“行为可以预期”,不锁等于“行为不可预期”。

抢购举例

假设有商品库存为10,共有1000名顾客在几乎同一时间进行抢购。

不加lock的写法:

Parallel.For(0, customerCount, (i) =>
{
TryToBuyGoods(customerCount);
});

加lock的写法:(实际上转为单线程)

Parallel.For(0, customerCount, (i) =>
{
lock (inStockLock)
{
TryToBuyGoods(customerCount);
}
});

加Monitor的写法:(实际上转为单线程)

Parallel.For(0, customerCount, (i) =>
{
bool lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(inStockLock, ref lockWasTaken);
TryToBuyGoods(customerCount);
}
finally
{
if (lockWasTaken)
System.Threading.Monitor.Exit(inStockLock);
}
});
private void TryToBuyGoods(int customerCount)
{
var buyCustomer = Customers[random.Next(0, customerCount)];
var sleepTime = random.Next(1000, 10000);
if (inStock > 0)
{
//模拟购物业务逻辑处理时间(占用资源)
Thread.Sleep(sleepTime);
inStock--;
//上述流程是先处理一系列业务逻辑,后减库存,在不加lock的情况下会出现超卖现象
//建议的逻辑是,先减库存-处理业务逻辑-后续库存不足,恢复库存,并提示用户“购买失败”
Console.WriteLine($"顾客{buyCustomer.Name}购买了一件商品");
}
//Console.WriteLine($"当前库存:{inStock}");
}

结果如下:

lock与Monitor_示例代码

lock与Monitor_示例代码_02

lock与Monitor_临界区_03

可以看出,这种情况不加lock是非常危险的事情。

示例代码

​UseLockDemo​

Monitor的其他用法

private static void WriteToResource(Random rnd, int timeout)
{
try
{
//尝试获得锁
if(Monitor.TryEnter(locker, timeout))
{
resource = rnd.Next(500);
Display("writes resource value " + resource);
writes++;
}
else
{
//获取失败
Display("can not writes resource value.");
}
}
catch
{

}
finally
{
//如果获得了锁,需要释放
if(Monitor.IsEntered(locker))
Monitor.Exit(locker);
}
}

示例代码

​示例代码MonitorTestDemo​

参考资料

​lock 语句(C# 参考)​

​Monitor 类​



学习技术最好的文档就是​​【​官方文档​】​​,没有之一。

还有学习资料​​【​Microsoft Learn​】​​、【​CSharp Learn】、​​【​My Note​】​​。

如果,你希望更容易地发现我的新博客,不妨【​关注​】。