why?
當我們使用線程的時候,效率最高的方式當然是異步,即各個線程同時運行,其間不相互依賴和等待。
但當不同的線程都需要訪問某個資源的時候,就需要同步機制了,也就是說當對同一個資源進行讀寫的時候,
我們要使該資源在同一時刻只能被一個線程操作,以確保每個操作都是有效即時的,也即保證其操作的原子性。
lock是C#中最常用的同步方式,格式爲lock(objectA){codeB} 。
lock (objectA) { codeB}
看似簡單,實際上有三個意思,這對於適當地使用它至關重要:
1.objectA被lock了嗎?沒有則由我來lock,否則一直等待,直至objectA被釋放。
2.lock以後在執行codeB的期間其他線程不能調用codeB,也不能使用objectA。
3.執行完codeB之後釋放objectA,並且codeB可以被其他線程訪問。
https://www.cnblogs.com/apsnet/archive/2012/07/08/2581475.html
即爲了保證資源的一致性
when?
需要該資源在同一時刻只能被一個線程操作
how?
通過加鎖來確保在操作完成之前不會被第二個資源進行訪問
場景一:
存在生產與售出方法
兩個方法同時操作庫存
code:
// 生產數量
public int MakeCount { get; set; }
// 售出數量
public int SellCount { get; set; }
//庫存
private int Products { get; set; }
//產品製造
public void Make(int num)
{
Products += num;
MakeCount += num;
Console.WriteLine($"生產了{num}個{ProductName},當前產品總數爲:{Products}");
}
//產品售出
public void Sell(int num)
{
if (Products < num)
{
Console.WriteLine($"庫存不足,當前庫存爲:{Products}");
return;
}
Products -= num;
SellCount += num;
Console.WriteLine($"售出了{num}個{ProductName},當前產品總數爲:{Products}");
}
result:
總生產數量: 11,總售出數量: 8,當前庫存: 3,實際庫存: 3
總生產數量: 31,總售出數量: 29,當前庫存: 1,實際庫存: 2
總生產數量: 51,總售出數量: 47,當前庫存: 1,實際庫存: 4
總生產數量: 73,總售出數量: 67,當前庫存: 3,實際庫存: 6
總生產數量: 93,總售出數量: 71,當前庫存: 19,實際庫存: 22
總生產數量: 97,總售出數量: 71,當前庫存: 23,實際庫存: 26
解決方法: 在操作數據時加鎖,保證在同一時間僅有一個線程操作此數據
private int _products;
public int Products
{
get
{
return this._products;
}
set
{
lock (this)
this._products = value;
}
}
note1: 單方法操作,不會導致數據異常
note2: 當執行時間越短,觸發數據異常機遇越小
lock是如何執行的?
lock <==> Monitor
即
lock (this)
{
_products = value;
}
//等價於:
bool lockTaken = false;
try
{
Monitor.Enter(this, ref lockTaken);
_products = value;
}
finally
{
if (lockTaken) Monitor.Exit(this);
}
https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.monitor?view=netframework-4.8
什麼是Monitor?/Monitord有什麼作用?
提供同步訪問對象的機制。
Monitor的使用
code:
//庫存
private int goods { get; set; }
//生產量
private int make { get; set; }
//銷售量
private int sell { get; set; }
public void Make()
{
if (Monitor.TryEnter(_lock, TimeSpan.FromMilliseconds(1000)))
{
try
{
goods++;
make++;
Console.WriteLine($"製造了一個產品,當前產品數量:{goods}");
////System.Threading.SynchronizationLockException:“Object synchronization method was called from an unsynchronized block of code.”
//爲避免生產過量,生產完後會提醒銷售售出
Monitor.Pulse(_lock);
}
finally
{
Monitor.Exit(_lock);
}
}
else
{
Console.WriteLine("生成失敗");
}
}
public void Sell()
{
if (Monitor.TryEnter(_lock, TimeSpan.FromMilliseconds(1000)))
{
while (goods <= 0) //庫存不足
{
//System.Threading.SynchronizationLockException:“Object synchronization method was called from an unsynchronized block of code.”
//同步方法不能在非同方方法中調用
if (!Monitor.Wait(_lock, TimeSpan.FromMilliseconds(1000))) //等待生成
{
Console.WriteLine("庫存不足!");
return;
}
}
sell++;
goods--;
Console.WriteLine($"售出了一個產品,當前庫存:{goods}");
}
else
{
Console.WriteLine($"購買失敗");
}
}
public void Show()
{
Console.WriteLine($"當前庫存:{goods},生產量:{make},銷售量:{sell},實際庫存:{sell - make}");
if (make - sell != goods) throw new Exception("銷售異常!");
}
note
- 同步索引塊是.NET中解決對象同步問題的基本機制
- 這個對象肯定要是引用類型,值類型可不可呢?值類型可以裝箱啊!你覺得可不可以?但也不要用值類型,因爲值類型多次裝箱後的對象是不同的,會導致無法鎖定;
- 不要鎖定this,儘量使用一個沒有意義的Object對象來鎖;
- 不要鎖定一個類型對象,因類型對象是全局的;
- 不要鎖定一個字符串,因爲字符串可能被駐留,不同字符對象可能指向同一個字符串;
- 不要使用[System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.Synchronized)],這個可以使用在方法上面,保證方法同一時刻只能被一個線程調用。她實質上是使用lock的,如果是實例方法,會鎖定this,如果是靜態方法,則會鎖定類型對象;
confirm
通常,應避免鎖定 public 類型,否則實例將超出代碼的控制範圍。
常見的結構 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違反此準則:
如果實例可以被公共訪問,將出現 lock (this) 問題。
如果 MyType 可以被公共訪問,將出現 lock (typeof (MyType)) 問題。
由於進程中使用同一字符串的任何其他代碼將共享同一個鎖,所以出現 lock(“myLock”) 問題。
ext
https://www.cnblogs.com/anding/p/5301754.html
https://www.cnblogs.com/carsonzhu/p/7446953.html
author:monster
since:5/7/2019 2:02:24 PM
direction:lock