3、ReaderWriterLock 類
ReaderWriterLock定義了實(shí)現(xiàn)單寫程序和多寫程序語(yǔ)義的鎖,。ReaderWriterLock類中4個(gè)主要的方法
· AcquireReacJerLock():獲得-個(gè)讀程序鎖,,超時(shí)值使用一個(gè)整數(shù)或一個(gè) TimeSpan。
· AcquireWiiterLock(): 獲得一個(gè)寫程序鎖,,超時(shí)值使用一個(gè)整數(shù)或一個(gè) TimeSpan,。
· ReleaseReaderLock():釋放讀程序鎖。
· ReleaseWriterLock(): 釋放寫程序鎖,。
一個(gè)線程可以持有讀線程鎖或?qū)懢€程鎖,,但是不能同時(shí)持有兩者。
- Imports System.Threading
- Namespace AReadWriteLock
- Public Class ReadWrite
- Private rwl As ReaderWriterLock
- Private x As Integer
- Private y As Integer
- Public Sub New()
- rwl = New ReaderWriterLock()
- End Sub
- Public Sub ReadInts(ByRef a As Integer, ByRef b As Integer)
- rwl.AcquireReaderLock(Timeout.Infinite)
- Try
- a = x
- b = y
- Finally
- rwl.ReleaseReaderLock()
- End Try
- End Sub
- Public Sub WriteInts(ByVal a As Integer, ByVal b As Integer)
- rwl.AcquireWriterLock(Timeout.Infinite)
- Try
- x = a
- y = b
- Console.WriteLine(" x=" & x & " y=" & y & " ThreadID=" & Thread.CurrentThread.GetHashCode.ToString)
- Finally
- rwl.ReleaseWriterLock()
- End Try
- End Sub
- End Class
- Public Class RWApp
- Private rw As New ReadWrite
- Public Overloads Shared Sub Main(ByVal args() As String)
- Dim e As New RWApp()
- Dim wt1 As New Thread(New ThreadStart(AddressOf e.Write))
- wt1.Start()
- Dim wt2 As New Thread(New ThreadStart(AddressOf e.Write))
- wt2.Start()
- Dim rt1 As New Thread(New ThreadStart(AddressOf e.Read))
- rt1.Start()
- Dim rt2 As New Thread(New ThreadStart(AddressOf e.Read))
- rt2.Start()
- Console.ReadLine()
- End Sub
- Private Sub Write()
- Dim a As Integer = 10
- Dim b As Integer = 11
- Console.WriteLine("=== Write ID:" & Thread.CurrentThread.GetHashCode.ToString)
- For i As Integer = 0 To 2
- rw.WriteInts(a, b)
- a += 1
- b += 1
- Thread.Sleep(1000)
- Next i
- End Sub
- Private Sub Read()
- Dim a As Integer = 10
- Dim b As Integer = 11
- Console.WriteLine("=== Read ID:" & Thread.CurrentThread.GetHashCode.ToString)
- For i As Integer = 0 To 2
- rw.ReadInts(a, b)
- Console.WriteLine("For i=" & i & " a=" & a & ” b=” & b & " ThreadID=" & Thread.CurrentThread.GetHashCode.ToString)
- Thread.Sleep(1000)
- Next i
- End Sub
- End Class
- End Namespace
線程的讀鎖或?qū)戞i同一時(shí)間只能有一個(gè)進(jìn)入,,結(jié)果如下:
(三)手控同步
System.Threading命名空間的一些可以用做手控同步的類,。它們賦予了程序員使用類似于WIN32線程API的低級(jí)線程API創(chuàng)建和管理多線程應(yīng)用程序的能力。如:Auto ResetEvent類,、ManualResetEvent類,、Mutex類、Interlocked類,。
1,、ManualResetEvent類
通知一個(gè)或多個(gè)正在等待的線程已發(fā)生事件,根據(jù)這個(gè)信號(hào)正在等待的線程決定是否繼續(xù)向下運(yùn)行,。ManualResetEvent對(duì)象只能擁有兩種狀態(tài):有信號(hào)(True)或無(wú)信號(hào)(False),。
ManualResetEvent 就象燈塔的信號(hào)燈,作用是阻塞一個(gè)或多個(gè)線程,直到收到一個(gè)信號(hào)告訴ManualResetEvent不要再阻塞當(dāng)前的線程,。
如果有信號(hào),,船只一路暢通無(wú)阻,當(dāng)前線程勇往直前運(yùn)行,,即使遇WaitOne也繼續(xù)前行,;
如果無(wú)信號(hào),船只一路遇礁則止,,當(dāng)前線程遇WaitOne則掛起受阻,。
- Imports System.Threading
- Namespace NETThreadEvents
- Class AManualReset
- Shared LightSign As New ManualResetEvent(False) '1、無(wú)信號(hào)
- Shared Sub main()
- Dim t(4) As Thread
- For i As Integer = 0 To 3
- t(i) = New Thread(AddressOf RunOrWait)
- t(i).Start()
- Next
- 'LightSign.Set() '3,、恢復(fù)有信號(hào),線程甩開阻礙繼續(xù)暢通向前運(yùn)行
- Console.Read()
- End Sub
- Public Shared Sub RunOrWait()
- Console.WriteLine("ID " & Thread.CurrentThread.GetHashCode.ToString & " waiting... ")
- LightSign.WaitOne() '2,、無(wú)信號(hào)時(shí)線程掛起受阻,,有信號(hào)則暢通
- Console.WriteLine("ID " & Thread.CurrentThread.GetHashCode.ToString & "Running... not blocked!")
- End Sub
- End Class
- End Namespace
說(shuō)明:左圖:1處設(shè)置為False,指示線程遇WaitOne受阻掛起,,所以在RunOrWait()方法(2處)的WaitOne時(shí)就掛起了,。中圖:1處設(shè)置為True,,線程暢通,2處WaiOne直接通過(guò),。右圖:1處為false無(wú)信號(hào),,所以在2處受阻,但是由于3處加了一句Set,,讓信號(hào)亮起,,前面掛起的恢復(fù)通過(guò),沒掛起當(dāng)然也通過(guò),。圖中可以看出10-12線程受阻,,由于主線程Set恢復(fù)信號(hào)后,原掛起的11,、12線程繼續(xù)跑起,,至于誰(shuí)搶到CPU由OS決定。
注意:至于終結(jié)與未終結(jié),,MSDN:
原文:Reset Sets the state of the event to non-signaled, which causes threads to block. (Inherited from EventWaitHandle.)
原譯文:Reset 將事件狀態(tài)設(shè)置為非終止?fàn)顟B(tài),,從而導(dǎo)致線程受阻。 (從 EventWaitHandle 繼承,。)
因?yàn)閚on-signaled被翻譯成了未終止?fàn)顟B(tài),。把受阻線程設(shè)置成無(wú)信號(hào)事件狀態(tài)。
2,、AutoResetEvent類
通知正在等待的線程已發(fā)生事件,。處于等待狀態(tài)的線程,直到通過(guò)調(diào)用Set()方法將它置于信號(hào)通知狀態(tài),。
AutoResetEvent和ManualResetEvent是類似的,。但有少些區(qū)別。
AutoResetEvent和ManualResetEvent的區(qū)別:
(1) AutoResetEvent只會(huì)給一個(gè)線程發(fā)送信號(hào)(非全部),。ManualResetEvent給全部線程發(fā)信號(hào),。
(2) AutoResetEvent在set()后,會(huì)將線程狀態(tài)自動(dòng)置為false,。ManualResetEvent在Set()后,,線程的狀態(tài)就變?yōu)閠rue,必須手動(dòng)ReSet()之后,,才會(huì)重新將線程置為false,。
所以ManualResetEvent就象大門(Sign)一打開(True),所有馬(Threads)都跑了,,且門一直開啟True,。AutoResetEvent只隨機(jī)開一匹的門(Set),放跑(True)后馬上關(guān)閉(False),。
- Imports System.Threading
- Namespace NETThreadEvents
- Class AManualReset
- Shared LightSign As New AutoResetEvent(False) '1,、無(wú)信號(hào)
- Shared Sub main()
- Dim t(4) As Thread
- For i As Integer = 0 To 3
- t(i) = New Thread(AddressOf RunOrWait)
- t(i).Start()
- Next
- LightSign.Set() '2,隨機(jī)釋放某一已經(jīng)阻塞的線程(不是全部)
- Console.Read()
- End Sub
- Public Shared Sub RunOrWait()
- Console.WriteLine("ID " & Thread.CurrentThread.GetHashCode.ToString & " waiting... ")
- LightSign.WaitOne()
- Console.WriteLine("ID " & Thread.CurrentThread.GetHashCode.ToString & "Running... not blocked!")
- End Sub
- End Class
- End Namespace
說(shuō)明:Set后,,僅將一個(gè)線程變成True,所以 ID9線程通過(guò)了,,ID12仍然阻塞.
3,、Mutex類(mutex互斥)
Mutex鎖提供了交叉線程和交叉同步進(jìn)程。如果沒有線程擁有有信號(hào)狀態(tài),,Mutex的狀態(tài)就會(huì)被置為有信號(hào)狀態(tài),。Mutex并不具有Monitor類的所有等待和脈沖功能,不過(guò)它確實(shí)提供了能夠在進(jìn)程之間使用的命名互斥體(使用重載 構(gòu)造函數(shù))的創(chuàng)建,。使用Mutex類優(yōu)于Monitor類的是:Mutex類可跨進(jìn)程使用,,而Monitor類則不行。
Mutex(boolean initiallyOwned, string name) 初始化 Mutex 類的新實(shí)例,。
initiallyOwned: 若為 true,,調(diào)用端擁有互斥體的初始所屬權(quán);否則為 false,。
Name: Mutex 的名稱,。如果值為 null,則 Mutex 是未命名的,。
- Imports System.Threading
- Namespace AMutex
- Class NETMutex
- Private Shared myMutex As Mutex
- Public Shared Sub Main()
- myMutex = New Mutex(True, "Magic")
- Dim nm As New NETMutex
- Dim t As New Thread(New ThreadStart(AddressOf nm.Run))
- t.Start()
-
-
- Dim ID As String = Thread.CurrentThread.GetHashCode.ToString
- Console.WriteLine("ID:" & ID & " main thread will sleep for 3 seconds...")
- Thread.Sleep(3000)
- Console.WriteLine("ID:" & ID & ” main thread Woke Up")
-
- myMutex.ReleaseMutex() '1,、釋放互斥鎖(讓線程t中3處得到鎖,以便向下運(yùn)行)
- Console.WriteLine("ID:" & ID & " main Before WaitOne")
- myMutex.WaitOne() '2,、阻塞,,直到再次得到互斥鎖(線程t中4處釋放鎖)
- Console.WriteLine("ID:" & ID & " main Lock. owned by Main Thread")
- Console.ReadLine()
- End Sub
- Public Sub Run()
- Dim ID As String = Thread.CurrentThread.GetHashCode.ToString
- Console.WriteLine("ID:" & ID & " t_thread In Run method")
- myMutex.WaitOne() '3、阻塞線程t,直到得到互斥鎖),,1外釋放后,,這里通暢向下
- Console.WriteLine("ID:" & ID & " t_thread will sleep for 6 seconds")
- Thread.Sleep(6000)
-
- Console.WriteLine("ID:" & ID & " t_thread end of Run method")
- myMutex.ReleaseMutex() '4、此處必須釋放鎖,,否則2處會(huì)無(wú)限等待鎖(最后拋出異常)
- End Sub
- End Class
- End Namespace
說(shuō)明: 主線程創(chuàng)建互斥鎖后,,擁有該鎖,等待3秒后在1處釋放鎖,,以便讓線程t擁有鎖后(3處)繼續(xù)向下運(yùn)行,,直到釋放鎖(4處),釋放后主線程馬上得到鎖并在阻塞的2處繼續(xù)向下運(yùn)行,。4處必須釋放原因:MSDN: 在桌面的 .NET 中,,如果沒有線程擁有互斥體,則互斥體的狀態(tài)為終止并將在下一個(gè)獲取互斥體的線程中引發(fā) AbandonedMutexException,。
4,、Intelocked類
Interlocked類為原子操作,在多個(gè)線程之間共享的非阻塞整數(shù)更新提供了方法。如果變量位于共享的內(nèi)存中的話,,不同進(jìn)程的線程以使用這種機(jī)制。
原子操作,,即不可分割操作,,一個(gè)線程操作時(shí),不會(huì)切換到另一個(gè)操作,??珊?jiǎn)單理解為獨(dú)占模式。
Interlocked可以為多個(gè)線程共享的變量提供原子操作:
Interlocked.Increment:以原子操作的形式遞增指定變量的值并存儲(chǔ)結(jié)果,。
Interlocked.Decrement 以原子操作的形式遞減指定變量的值并存儲(chǔ)結(jié)果,。
Interlocked.Add 以原子操作的形式,添加兩個(gè)整數(shù)并用兩者的和替換第一個(gè)整數(shù)
注意:Intelocked原子操作鎖定的是某一個(gè)值,,僅在類似上面三句中生效,,越過(guò)此句原子操作失效。
- Imports System.Threading
- Namespace AInterLocker
- Class WinterLocked
- Public a As New ManualResetEvent(False)
- Private i As Integer = 5
- Public Sub Run(ByVal s As Object)
- Interlocked.Increment(i) '2,、原子操作遞增
- Console.WriteLine(Thread.CurrentThread.GetHashCode.ToString & " " & i)
- Thread.Sleep(500)
- 'Console.WriteLine(Thread.CurrentThread.GetHashCode.ToString & " completed") ‘3,、非原子操作
- End Sub
- Public Function GetValue() As Integer
- Return i
- End Function
- End Class
- Public Class MainApp
- Public Shared Sub Main()
- Dim mR As New ManualResetEvent(False)
- Dim wL As New WinterLocked
-
- For i As Integer = 1 To 10 '1、線程池排隊(duì)
- ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf wL.Run), 2)
- Next i
- mR.WaitOne(3000, True) '在3秒后退出同步域
- Console.WriteLine("Result of 10 times is " & wL.GetValue)
- Console.ReadLine()
- End Sub
- End Class
- End Namespace
說(shuō)明:下面左右兩圖都是上面的結(jié)果,,特別是左圖,,為啥會(huì)出現(xiàn)兩個(gè)11,而少了10呢,?首先確認(rèn)10個(gè)線程進(jìn)入后是進(jìn)行了原子操作,,每個(gè)線程增加1,因?yàn)?0個(gè)線程的結(jié)果都是15(=5+10),,因?yàn)樵硬僮鲀H在2處,,過(guò)了此句后面的都不是原子操作。細(xì)節(jié)為:A線程原子鎖定i(=9),,然后增加1(i=10),,后而退出原子操作馬上要執(zhí)行后一句提取i值(但還沒顯示),這里B線程切換進(jìn)來(lái),原子鎖定i并對(duì)i增加1(i=10+1=11),,然后原子操作退出并提取i值到B線程中,。注意A、B線程提取的i值都是11,,故顯示的都是11,,至于中間的12則是在輸出過(guò)程中一樣是要花費(fèi)時(shí)間的,被另一線程C切入并提取i值12,,并于B線程前輸出,。在3處增加一句顯示i的結(jié)果,上面的效果將更加明顯,。
5,、共享變量、方法和同步
共享(Shared)的變量和方法,,既可以被類訪問也可被該類的實(shí)例訪問,,所以同步鎖定Shared的變量或方法就被應(yīng)用到整個(gè)該類上。此時(shí),,其它對(duì)象不允許使用此類的Shared變量或方法,。
ThreadStaticAttribute 類
帶有ThreadStaticAttribute 的Shared變量,對(duì)每個(gè)訪問變量的線程都會(huì)有一個(gè)同一變量的單獨(dú)副本,。意味著如果一個(gè)線程修改了變量,,另一個(gè)訪問變量的線程就不能看到這些變化(包括主線程)。這種行為是有背于Shared變量的默認(rèn)行為的,。用處比如web應(yīng)用中,每個(gè)請(qǐng)求都是一個(gè)獨(dú)立的線程,如果我們希望將一個(gè)值作為靜態(tài)字段全局使用,同時(shí)又不想影響其他用戶,這時(shí)候一般我們是使用Session的,。
- Imports System.Threading
- Namespace TestShared
- Class AThreadStatic
- <ThreadStatic> Public Shared x As Integer '1、共享變量,,各線程分別有副本x
- Public Shared y As Integer = 1 '2、共享變量,,各線程無(wú)副本
- Public Sub Run()
- For i As Integer = 1 To 5
- Dim id As String = Thread.CurrentThread.GetHashCode.ToString
- x += 1
- y += 1
- Console.WriteLine("i=" & i & “ ThreadID=" & id & " x=" & x & " y=" & y)
- Thread.Sleep(1000) '3.共5次循環(huán),,花時(shí)5秒
- Next i
- End Sub
- End Class
- Public Class MainApp
- Public Shared Sub Main()
- Dim tS As New AThreadStatic
- Dim tl As New Thread(New ThreadStart(AddressOf tS.Run))
- Dim t2 As New Thread(New ThreadStart(AddressOf tS.Run))
- tl.Start()
- t2.Start()
- Thread.Sleep(3500) '4、在3.5秒后,,看一下類中共享變量值
- Console.WriteLine("Main thread get value1:" & AThreadStatic.x & " " & AThreadStatic.y)
- Thread.Sleep(4000) '5、又4秒后,,看一下類中共享變量值
- Console.WriteLine("Main thread get value2:" & AThreadStatic.x & " " & AThreadStatic.y)
- Console.ReadLine()
- End Sub
- End Class
- End Namespace
說(shuō)明:x被標(biāo)注后,,在兩個(gè)線程中將分別生成各自的x的副本(并不影響原類的值),所以在4處可以看到原類中x仍為0,,但y沒被標(biāo)注,,所以值是變化的。最后的結(jié)果(5處),,也可以看出始終x沒被修改為0(僅在各自的線程中修改其副本),y是修改的為11,。
(四)防止死鎖
線程越多,,上鎖越復(fù)雜,,就越容易死鎖,。通常的防止的原則就是:一個(gè)線程最多只能有一個(gè)鎖,。
如果想更多的鎖,,就越容易死鎖,。例如:A線程中擁有鎖L1期待鎖L2,;而B線程中擁有鎖L2期待鎖L1,;如果兩個(gè)同時(shí)發(fā)生或重疊,就會(huì)發(fā)生死鎖,。當(dāng)然如果時(shí)間錯(cuò)過(guò)也就無(wú)所謂。
- Imports System.Threading
- Namespace DeadLock
- Class DL
- Private field_1 As Integer = 0
- Private field_2 As Integer = 0
- Private lock_1 As Object = New Integer(1) {}
- Private lock_2 As Object = New Integer(1) {}
- Public Sub first(ByVal val As Integer)
- SyncLock lock_1
- Console.WriteLine("First:Acquired lock_1:" & Thread.CurrentThread.GetHashCode.ToString + " Now Sleeping")
- Thread.Sleep(1000)
- SyncLock lock_2
- Console.WriteLine("First:Acquired lock_2:" & Thread.CurrentThread.GetHashCode.ToString)
- field_1 = val
- field_2 = val
- End SyncLock
- End SyncLock
- End Sub
- Public Sub second(ByVal val As Integer)
- SyncLock lock_2
- Console.WriteLine("Second:Acquired lock_2:" & Thread.CurrentThread.GetHashCode.ToString)
- SyncLock lock_1
- Console.WriteLine("Second:Acquired lock I:" & Thread.CurrentThread.GetHashCode().ToString)
- field_1 = val
- field_2 = val
- End SyncLock
- End SyncLock
- End Sub
- End Class
- Public Class MainApp
- Private d As New DL()
- Public Shared Sub Main()
- Dim m As New MainApp
- Dim tl As New Thread(New ThreadStart(AddressOf m.Run1))
- tl.Start()
- Dim t2 As New Thread(New ThreadStart(AddressOf m.Run2))
- t2.Start()
- Console.ReadLine()
- End Sub
- Public Sub Run1()
- d.first(10)
- End Sub
- Public Sub Run2()
- d.second(10)
- End Sub
- End Class
- End Namespace
說(shuō)明:兩個(gè)線程都分別擁有鎖,,還期待對(duì)方向的鎖,,都處于等待對(duì)方的鎖,,于是就死鎖了,。
|