lock与Monitor

应用场景

lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
lock语句根本使用的就是Monitor.EnterMonitor.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}");
}

结果如下:
1
2
3
可以看出,这种情况不加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 类

posted @ 2022-03-01 18:33  Lulus  阅读(138)  评论(0编辑  收藏  举报