C#多線程系列之多線程鎖lock和Monitor
1,Lock
lock 用于讀一個引用類型進行加鎖,同一時刻內(nèi)只有一個線程能夠訪問此對象。lock 是語法糖,是通過 Monitor 來實現(xiàn)的。
Lock 鎖定的對象,應(yīng)該是靜態(tài)的引用類型(字符串除外)。
實際上字符串也可以作為鎖的對象使用,只是由于字符串對象的特殊性,可能會造成不同位置的不同線程沖突。
如果你能保證字符串的唯一性,例如 Guid 生成的字符串,也是可以作為鎖的對象使用的(但不建議)。
鎖的對象也不一定要靜態(tài)才行,也可以通過類實例的成員變量,作為鎖對象。
lock 原型
lock 是 Monitor 的語法糖,生成的代碼對比:
lock (x)
{
// Your code...
}object __lockObj = x;
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}這里先不理會 Monitor,后面再說。
lock 編寫實例
首先,如果像下面這樣寫的話,拉出去打 si 吧。
public void MyLock()
{
object o = new object();
lock (o)
{
//
}
}下面編寫一個簡單的鎖,示例如下:
class Program
{
private static object obj = new object();
private static int sum = 0;
static void Main(string[] args)
{
Thread thread1 = new Thread(Sum1);
thread1.Start();
Thread thread2 = new Thread(Sum2);
thread2.Start();
while (true)
{
Console.WriteLine($"{DateTime.Now.ToString()}:" + sum);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
public static void Sum1()
{
sum = 0;
lock (obj)
{
for (int i = 0; i < 10; i++)
{
sum += i;
Console.WriteLine("Sum1");
Thread.Sleep(TimeSpan.FromSeconds(2));
}
}
}
public static void Sum2()
{
sum = 0;
lock (obj)
{
for (int i = 0; i < 10; i++)
{
sum += 1;
Console.WriteLine("Sum2");
Thread.Sleep(TimeSpan.FromSeconds(2));
}
}
}
}類將自己設(shè)置為鎖, 這可以防止惡意代碼對公共對象采用做鎖。
例如:
public void Access()
{
lock(this) {}
}鎖可以阻止其它線程執(zhí)行鎖塊(lock(o){})中的代碼,當鎖定時,其它線程必須等待鎖中的線程執(zhí)行完成并釋放鎖。但是這可能會給程序帶來性能影響。
鎖不太適合I/O場景,例如文件I/O,繁雜的計算或者操作比較持久的過程,會給程序帶來很大的性能損失。
10 種優(yōu)化鎖的性能方法: http://www.thinkingparallel.com/2007/07/31/10-ways-to-reduce-lock-contention-in-threaded-programs/
2,Monitor
此對象提供同步訪問對象的機制;Monotor 是一個靜態(tài)類型,其方法比較少,常用方法如下:
| 操作 | 說明 |
|---|---|
| Enter, TryEnter | 獲取對象的鎖。 此操作還標記關(guān)鍵節(jié)的開頭。 其他任何線程都不能輸入臨界區(qū),除非它使用不同的鎖定對象執(zhí)行臨界區(qū)中的說明。 |
| Wait | 釋放對象的鎖,以允許其他線程鎖定并訪問對象。 調(diào)用線程會等待另一個線程訪問對象。 使用脈沖信號通知等待線程關(guān)于對象狀態(tài)的更改。 |
| Pulse 、PulseAll | 將信號發(fā)送到一個或多個等待線程。 信號通知等待線程:鎖定對象的狀態(tài)已更改,鎖的所有者已準備好釋放該鎖。 正在等待的線程置于對象的就緒隊列中,因此它可能最終接收對象的鎖。 線程鎖定后,它可以檢查對象的新狀態(tài),以查看是否已達到所需的狀態(tài)。 |
| Exit | 釋放對象的鎖。 此操作還標記受鎖定對象保護的臨界區(qū)的結(jié)尾。 |
怎么用呢
下面是一個很簡單的示例:
private static object obj = new object();
private static bool acquiredLock = false;
public static void Test()
{
try
{
Monitor.Enter(obj, ref acquiredLock);
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
}
}Monitor.Enter 鎖定 obj 這個對象,并且設(shè)置 acquiredLock 為 true,告訴別人 obj 已經(jīng)被鎖定。
最后結(jié)束時,判斷 acquiredLock ,釋放鎖,并設(shè)置 acquiredLock 為 false。
解釋一下
臨界區(qū):指被某些符號包圍的范圍。例如 {} 內(nèi)。
Monitor 對象的 Enter 和 Exit 方法來標記臨界區(qū)的開頭和結(jié)尾。
Enter() 方法獲取鎖后,能夠保證只有單個線程能夠使用臨界區(qū)中的代碼。使用 Monitor 類,最好搭配 try{...}catch{...}finally{...} 來使用,因為如果獲取到鎖但是沒有釋放鎖的話,會導(dǎo)致其它線程無限阻塞,即發(fā)生死鎖。
一般來說,lock 關(guān)鍵字夠用了。
示例
下面示范了多個線程如何使用 Monitor 來實現(xiàn)鎖:
private static object obj = new object();
private static bool acquiredLock = false;
static void Main(string[] args)
{
new Thread(Test1).Start();
Thread.Sleep(1000);
new Thread(Test2).Start();
}
public static void Test1()
{
try
{
Monitor.Enter(obj, ref acquiredLock);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Test1正在鎖定資源");
Thread.Sleep(1000);
}
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
Console.WriteLine("Test1已經(jīng)釋放資源");
}
}
public static void Test2()
{
bool isGetLock = false;
Monitor.Enter(obj);
try
{
Monitor.Enter(obj, ref acquiredLock);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Test2正在鎖定資源");
Thread.Sleep(1000);
}
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
Console.WriteLine("Test2已經(jīng)釋放資源");
}
}設(shè)置獲取鎖的時效
如果對象已經(jīng)被鎖定,另一個線程使用 Monitor.Enter 對象,就會一直等待另一個線程解除鎖定。
但是,如果一個線程發(fā)生問題或者出現(xiàn)死鎖的情況,鎖一直被鎖定呢?或者線程具有時效性,超過一段時間不執(zhí)行,已經(jīng)沒有了意義呢?
我們可以通過 Monitor.TryEnter() 來設(shè)置等待時間,超過一段時間后,如果鎖還沒有釋放,就會返回 false。
改造上面的示例如下:
private static object obj = new object();
private static bool acquiredLock = false;
static void Main(string[] args)
{
new Thread(Test1).Start();
Thread.Sleep(1000);
new Thread(Test2).Start();
}
public static void Test1()
{
try
{
Monitor.Enter(obj, ref acquiredLock);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Test1正在鎖定資源");
Thread.Sleep(1000);
}
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
Console.WriteLine("Test1已經(jīng)釋放資源");
}
}
public static void Test2()
{
bool isGetLock = false;
isGetLock = Monitor.TryEnter(obj, 500);
if (isGetLock == false)
{
Console.WriteLine("鎖還沒有釋放,我不干活了");
return;
}
try
{
Monitor.Enter(obj, ref acquiredLock);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Test2正在鎖定資源");
Thread.Sleep(1000);
}
}
catch { }
finally
{
if (acquiredLock)
Monitor.Exit(obj);
Console.WriteLine("Test2已經(jīng)釋放資源");
}
}到此這篇關(guān)于C#多線程系列之多線程鎖lock和Monitor的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
DevExpress實現(xiàn)TreeList向上遞歸獲取符合條件的父節(jié)點
這篇文章主要介紹了DevExpress實現(xiàn)TreeList向上遞歸獲取符合條件的父節(jié)點,需要的朋友可以參考下2014-08-08
C#結(jié)合Minio實現(xiàn)文件上傳存儲與更新
MinIO是一個開源的對象存儲服務(wù)器,專門設(shè)計用于在大規(guī)模數(shù)據(jù)存儲環(huán)境中運行,這篇文章主要為大家介紹了C#如何結(jié)合Minio實現(xiàn)文件上傳存儲與更新,需要的可以參考下2024-03-03
Unity UGUI的ContentSizeFitter內(nèi)容尺寸適應(yīng)器組件使用示例
這篇文章主要為大家介紹了Unity UGUI的ContentSizeFitter內(nèi)容尺寸適應(yīng)器組件使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
Windows窗體的.Net框架繪圖技術(shù)實現(xiàn)方法
這篇文章主要介紹了Windows窗體的.Net框架繪圖技術(shù)實現(xiàn)方法,非常實用,需要的朋友可以參考下2014-08-08

