作為C#的程序員來說,在遇到線程同步的需求時最常用的就是lock關鍵字。但如何正確並有效地使用lock,卻是能否高效地達到同步要求的關鍵。正因為如此,程序員需要完全理解lock究竟為程序做了什么。
所涉及的知識點
• lock的等效代碼
• System.Threading.Monitor類型的作用和使用方法
分析問題
1.lock的等效代碼
在.NET的多線程程序中,經常會遇到lock關鍵字來控制同步,比如下列代碼:
private object o = new object();
public void Work()
{
lock(o)
{
//做一些需要線程同步的工作
}
}
事實上,lock這個關鍵字是C#為方便程序員而定義的語法,它等效於安全地使用System.Threading.Monitor類型。上面的代碼就直接等效於下面的代碼:
private object o = new object();
public void Work()
{
//這里很重要,是為了避免直接使用私有成員o,而導致線程不安全
object temp = o;
System.Threading.Monitor.Enter(temp);
try
{
//做一些需要線程同步的工作
}
finally
{
System.Threading.Monitor.Exit(temp);
}
}
正如你看到的,真正實現了線程同步功能的,就是System.Threading.Monitor類型,lock關鍵字只是用來代替調用Enter、Exit方法,並且將所有的工作包含在try塊內,以保證其最終退出同步。
2.System.Threading.Monitor類型的作用和使用
在前文中已經提到了,Monitor類型的Enter和Exit方法用來實現進入和退出對象的同步。具體來說,當Enter方法被調用時,對象的同步索引將被檢查,並且.NET將負責一系列的后續工作,來保證對象訪問時線程的同步,而Exit方法的調用,則保證了當前線程釋放該對象的同步塊。
示例
下列演示了自定義的類如何利用lock關鍵字(也就是Monitor類型)來實現線程同步,具體定義了一個包含需要同步執行方法的類型。
/// <summary>
/// 演示同步鎖
/// </summary>
public class MyLock
{
//用來在靜態方法中同步
private static object o1 = new object();
//用來在成員方法中不同
private object o2 = new object();
//成員變量
private static int i1 = 0;
private int i2 = 0;
/// <summary>
/// 測試靜態方法的同步
/// </summary>
/// <param name=" handleObject ">同步時操作的對象</param>
public static void Increment1(object handleObject)
{
lock (o1)
{
Console.WriteLine("i1的值為:{0}", i1);
//這里刻意制造線程並行機會,來檢查同步的功能
Thread.Sleep(200);
i1++;
Console.WriteLine("i1自增后為:{0}", i1);
}
}
/// <summary>
/// 測試成員方法的同步
/// </summary>
/// <param name=" handleObject ">同步時操作的對象</param>
public void Increment2(object handleObject)
{
lock (o2)
{
Console.WriteLine("i2的值為:{0}", i2);
//這里刻意制造線程並行機會,來檢查同步的功能
Thread.Sleep(200);
i2++;
Console.WriteLine("i2自增后為:{0}", i2);
}
}
}
這樣在main方法中,調用該類型對象的方法和其靜態方法,測試其同步的效果。代碼如下
/// <summary>
/// 程序入口
/// </summary>
class Program
{
/// <summary>
/// 測試同步效果
/// </summary>
static void Main(string[] args)
{
//開始多線程
Console.WriteLine("開始測試靜態方法的同步");
for (int i = 0; i < 5; i++)
{
Task t = new Task(MyLock.Increment1, i);
t.Start();
}
//這里等待線程執行結束
Thread.Sleep(3 * 1000);
Console.WriteLine("開始測試成員方法的同步");
MyLock myLock = new MyLock();
//開始多線程
for (int i = 0; i < 5; i++)
{
Thread t = new Thread(myLock.Increment2);
t.Start();
}
Console.Read();
}
}
下面是程序的執行結果:
開始測試靜態方法的同步
i1的值為:0
i1自增后為:1
i1的值為:1
i1自增后為:2
i1的值為:2
i1自增后為:3
i1的值為:3
i1自增后為:4
i1的值為:4
i1自增后為:5
開始測試成員方法的同步
i2的值為:0
i2自增后為:1
i2的值為:1
i2自增后為:2
i2的值為:2
i2自增后為:3
i2的值為:3
i2自增后為:4
i2的值為:4
i2自增后為:5
總結
可以看到,線程同步被很好地保證了。這里需要強調的是,線程同步本身違反了多線程並行運行的原則,所以讀者在使用線程同步時應該盡量做到把lock加在最小的程序塊上。如果一個方法有大量的代碼需要線程同步,那就需要重新考慮程序的設計了,是否真的有必要進行多線程處理,畢竟線程本身的開銷也是相當大的。
對靜態方法的同步,一般采用靜態私有的引用成員,而對成員方法的同步,一般采用私有的引用成員。讀者需要注意靜態和非靜態成員的使用,都把同步對象申明為私有,這是保證線程同步高效並且正確的關鍵點。
說明:以上內容是根據網上內容進行整理。