lock与Monitor
应用场景
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的其他用法
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);
}
}
示例代码
参考资料
学习技术最好的文档就是【官方文档】,没有之一。
还有学习资料【Microsoft Learn】、【CSharp Learn】、【My Note】。
如果,你认为阅读这篇博客让你有些收获,不妨点击一下右下角的【推荐】按钮。
如果,你希望更容易地发现我的新博客,不妨点击一下【关注】。