[C#参考]锁定lock
Lock關鍵字解釋:
lock 關鍵字將語句塊標記為臨界區,方法是獲取給定對象的互斥鎖,執行語句,然后釋放該鎖。 下面的示例包含一個 lock 語句。
lock 關鍵字可確保當一個線程位于代碼的臨界區時,另一個線程不會進入該臨界區。 如果其他線程嘗試進入鎖定的代碼,則它將一直等待(即被阻塞),直到該對象被釋放。lock 關鍵字在塊的開始處調用 Enter,而在塊的結尾處調用 Exit。
在多線程中,每個線程都有自己的資源,但是代碼區是共享的,即每個線程都可以執行相同的函數。這可能帶來的問題就是幾個線程同時執行一個函數,導致數據的混亂,產生不可預料的結果,因此我們必須避免這種情況的發生。
而在.NET中最好了解一下進程、應用域和線程的概念,因為Lock是針對線程一級的,而在.NET中應用域是否會對Lock起隔離作用,我的猜想是,即不在同一應用域中的線程無法通過Lock來中斷;另外也最好能了解一下數據段、代碼段、堆、棧等概念。
在C# lock關鍵字定義如下:
lock(expression) statement_block,其中expression代表你希望跟蹤的對象,通常是對象引用。
如果你想保護一個類的實例,一般地,你可以使用this;如果你想保護一個靜態變量(如互斥代碼段在一個靜態方法內部),一般使用類名就可以了。
而statement_block就是互斥段的代碼,這段代碼在一個時刻內只可能被一個線程執行。
二、簡單解釋一下執行過程
先來看看執行過程,代碼示例如下:
private?static?object? ojb = new?object(); lock(obj) { ???????? //鎖定運行的代碼段 }?假設線程A先執行,線程B稍微慢一點。線程A執行到lock語句,判斷obj是否已申請了互斥鎖,判斷依據是逐個與已存在的鎖進行object.ReferenceEquals比較(此處未加證實),如果不存在,則申請一個新的互斥鎖,這時線程A進入lock里面了。
這時假設線程B啟動了,而線程A還未執行完lock里面的代碼。線程B執行到lock語句,檢查到obj已經申請了互斥鎖,于是等待;直到線程A執行完畢,釋放互斥鎖,線程B才能申請新的互斥鎖并執行lock里面的代碼。
三、Lock的對象選擇問題
??? 接下來說一些lock應該鎖定什么對象。
??? 1、為什么不能lock值類型
??? 比如lock(1)呢?lock本質上Monitor.Enter,Monitor.Enter會使值類型裝箱,每次lock的是裝箱后的對象。lock其實是類似編譯器的語法糖,因此編譯器直接限制住不能lock值類型。退一萬步說,就算能編譯器允許你lock(1),但是object.ReferenceEquals(1,1)始終返回false(因為每次裝箱后都是不同對象),也就是說每次都會判斷成未申請互斥鎖,這樣在同一時間,別的線程照樣能夠訪問里面的代碼,達不到同步的效果。同理lock((object)1)也不行。
??? 2、Lock字符串
??? 那么lock("xxx")字符串呢?MSDN上的原話是:
鎖定字符串尤其危險,因為字符串被公共語言運行庫 (CLR)“暫留”。 這意味著整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了所有運行的應用程序域的所有線程中的該文本。因此,只要在應用程序進程中的任何位置處具有相同內容的字符串上放置了鎖,就將鎖定應用程序中該字符串的所有實例。
??? 3、MSDN推薦的Lock對象
??? 通常,最好避免鎖定 public 類型或鎖定不受應用程序控制的對象實例。例如,如果該實例可以被公開訪問,則 lock(this) 可能會有問題,因為不受控制的代碼也可能會鎖定該對象。這可能導致死鎖,即兩個或更多個線程等待釋放同一對象。出于同樣的原因,鎖定公共數據類型(相比于對象)也可能導致問題。
??? 而且lock(this)只對當前對象有效,如果多個對象之間就達不到同步的效果。
??? 而自定義類推薦用私有的只讀靜態對象,比如:
private static readonly object obj = new object();
為什么要設置成只讀的呢?這時因為如果在lock代碼段中改變obj的值,其它線程就暢通無阻了,因為互斥鎖的對象變了,object.ReferenceEquals必然返回false。
4、lock(typeof(Class))
??? 與鎖定字符串一樣,范圍太廣了。
四、特殊問題:Lock(this)等的詳細解釋
??? 在以前編程中遇到lock問題總是使用lock(this)一鎖了之,出問題后翻看MSDN突然發現下面幾行字:通常,應避免鎖定 public 類型,否則實例將超出代碼的控制范圍。常見的結構 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違反此準則:如果實例可以被公共訪問,將出現C# lock this問題。如果 MyType 可以被公共訪問,將出現 lock (typeof (MyType)) 問題。由于進程中使用同一字符串的任何其他代碼將共享同一個鎖,所以出現 lock(“myLock”) 問題。
??? 來看看C# lock this問題:如果有一個類Class1,該類有一個方法用lock(this)來實現互斥:
publicvoidMethod2() { ??? lock(this) ??? { ??????? System.Windows.Forms.MessageBox.Show("Method2End"); ??? } }如果在同一個Class1的實例中,該Method2能夠互斥的執行。但是如果是2個Class1的實例分別來執行Method2,是沒有互斥效果的。因為這里的lock,只是對當前的實例對象進行了加鎖。
Lock(typeof(MyType))鎖定住的對象范圍更為廣泛,由于一個類的所有實例都只有一個類型對象(該對象是typeof的返回結果),鎖定它,就鎖定了該對象的所有實例,微軟現在建議,不要使用lock(typeof(MyType)),因為鎖定類型對象是個很緩慢的過程,并且類中的其他線程、甚至在同一個應用程序域中運行的其他程序都可以訪問該類型對象,因此,它們就有可能代替您鎖定類型對象,完全阻止您的執行,從而導致你自己的代碼的掛起。
鎖住一個字符串更為神奇,只要字符串內容相同,就能引起程序掛起。原因是在.NET中,字符串會被暫時存放,如果兩個變量的字符串內容相同的話,.NET會把暫存的字符串對象分配給該變量。所以如果有兩個地方都在使用lock(“my lock”)的話,它們實際鎖住的是同一個對象。到此,微軟給出了個lock的建議用法:鎖定一個私有的static 成員變量。
總結:
應避免鎖定 public 類型,否則實例將超出代碼的控制范圍。常見的結構 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違反此準則:????
1)如果實例可以被公共訪問,將出現 lock (this) 問題;????
2)如果 MyType 可以被公共訪問,將出現 lock (typeof (MyType)) 問題;????
3)由于進程中使用同一字符串的任何其他代碼將共享同一個鎖,所以出現 lock("myLock") 問題;????
最佳做法是定義 private 對象來鎖定, 或 private static 對象變量來保護所有實例所共有的數據。
下面引入lock關鍵字的理論:
在應用程序中使用多個線程的一個好處是每個線程都可以異步執行。對于 Windows 應用程序,耗時的任務可以在后臺執行,而使應用程序窗口和控件保持響應。對于服務器應用程序,多線程處理提供了用不同線程處理每個傳入請求的能力。否則,在完全滿足前一個請求之前,將無法處理每個新請求。 然而,線程的異步特性意味著必須協調對資源(如文件句柄、網絡連接和內存)的訪問。否則,兩個或更多的線程可能在同一時間訪問相同的資源,而每個線程都不知道其他線程的操作。結果將產生不可預知的數據損壞。 對于整數數據類型的簡單操作,可以用 Interlocked 類的成員來實現線程同步。對于其他所有數據類型和非線程安全的資源,只有使用本主題中的結構才能安全地執行多線程處理。
?? lock 關鍵字可以用來確保代碼塊完成運行,而不會被其他線程中斷。這是通過在代碼塊運行期間為給定對象獲取互斥鎖來實現的。
?? 提供給 lock 關鍵字的參數必須為基于引用類型的對象,該對象用來定義鎖的范圍。在上例中,鎖的范圍限定為此函數,因為函數外不存在任何對該對象的引用。如果確實存在此類引用,鎖的范圍將擴展到該對象。嚴格地說,提供給 lock 的對象只是用來唯一地標識由多個線程共享的資源,所以它可以是任意類實例。然而,實際上,此對象通常表示需要進行線程同步的資源。例如,如果一個容器對象將被多個線程使用,則可以將該容器傳遞給 lock,而 lock 后面的同步代碼塊將訪問該容器。只要其他線程在訪問該容器前先鎖定該容器,則對該對象的訪問將是安全同步的。
通常,最好避免鎖定 public 類型或鎖定不受應用程序控制的對象實例。例如,如果該實例可以被公開訪問,則 lock(this) 可能會有問題,因為不受控制的代碼也可能會鎖定該對象。這可能導致死鎖,即兩個或更多個線程等待釋放同一對象。出于同樣的原因,鎖定公共數據類型(相比于對象)也可能導致問題。鎖定字符串尤其危險,因為字符串被公共語言運行庫 (CLR)“暫留”。 這意味著整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了所有運行的應用程序域的所有線程中的該文本。因此,只要在應用程序進程中的任何位置處具有相同內容的字符串上放置了鎖,就將鎖定應用程序中該字符串的所有實例。因此,最好鎖定不會被暫留的私有或受保護成員。某些類提供專門用于鎖定的成員。例如,Array 類型提供 SyncRoot。許多集合類型也提供 SyncRoot。
實例代碼:
/* 該實例是一個線程中lock用法的經典實例,使得到的balance不會為負數 同時初始化十個線程,啟動十個,但由于加鎖,能夠啟動調用WithDraw方法的可能只能是其中幾個 作者:http://hi.baidu.com/jiang_yy_jiang */ using System; ? namespace ThreadTest29 { ??? class Account ??? { ??????? private Object thisLock = new?object(); ??????? int balance; ??????? Random r = new Random(); ? ??????? public Account(int initial) ??????? { ??????????? balance = initial; ??????? } ? ??????? int WithDraw(int amount) ??????? { ??????????? if (balance < 0) ??????????? { ??????????????? throw?new Exception("負的Balance."); ??????????? } ??????????? //確保只有一個線程使用資源,一個進入臨界狀態,使用對象互斥鎖,10個啟動了的線程不能全部執行該方法 ??????????? lock (thisLock) ??????????? { ??????????????? if (balance >= amount) ??????????????? { ??????????????????? Console.WriteLine("----------------------------:" + System.Threading.Thread.CurrentThread.Name + "---------------"); ??????????????????? ??????????????????? Console.WriteLine("調用Withdrawal之前的Balance:" + balance); ??????????????????? Console.WriteLine("把Amount輸入Withdrawal???? :-" + amount); ??????????????????? //如果沒有加對象互斥鎖,則可能10個線程都執行下面的減法,加減法所耗時間片段非常小,可能多個線程同時執行,出現負數。 ??????????????????? balance = balance - amount; ??????????????????? Console.WriteLine("調用Withdrawal之后的Balance :" + balance); ??????????????????? return amount; ??????????????? } ??????????????? else ??????????????? { ??????????????????? //最終結果 ??????????????????? return 0; ??????????????? } ??????????? } ??????? } ??????? public?void DoTransactions() ??????? { ??????????? for (int i = 0; i < 100; i++) ??????????? { ??????????????? //生成balance的被減數amount的隨機數 ??????????????? WithDraw(r.Next(1, 100)); ??????????? } ??????? } ??? } ? ??? class Test ??? { ??????? static?void Main(string[] args) ??????? { ??????????? //初始化10個線程 ??????????? System.Threading.Thread[] threads = new System.Threading.Thread[10]; ??????????? //把balance初始化設定為1000 ??????????? Account acc = new Account(1000); ??????????? for (int i = 0; i < 10; i++) ??????????? { ??????????????? System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(acc.DoTransactions)); ??????????????? threads[i] = t; ??????????????? threads[i].Name = "Thread" + i.ToString(); ??????????? } ??????????? for (int i = 0; i < 10; i++) ??????????? { ??????????????? threads[i].Start(); ??????????? } ??????????? Console.ReadKey(); ??????? } ??? } }原文轉載地址:http://www.cnblogs.com/promise-7/articles/2354077.html
還有一些知識,感覺現在沒有用,就沒有轉載。
轉載于:https://www.cnblogs.com/stemon/p/4201362.html
總結
以上是生活随笔為你收集整理的[C#参考]锁定lock的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tomcat设置https访问
- 下一篇: duilib 修复Text控件无法设置宽