應用場景
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是非常危險的事情。