3、ReaderWriterLock 类
ReaderWriterLock定义了实现单写程序和多写程序语义的锁。ReaderWriterLock类中4个主要的方法
? AcquireReacJerLock():获得-个读程序锁,超时值使用一个整数或一个 TimeSpan。
? AcquireWiiterLock(): 获得一个写程序锁,超时值使用一个整数或一个 TimeSpan。
? ReleaseReaderLock():释放读程序锁。
? ReleaseWriterLock(): 释放写程序锁。
一个线程可以持有读线程锁或写线程锁,但是不能同时持有两者。
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
线程的读锁或写锁同一时间只能有一个进入,结果如下:
(三)手控同步
System.Threading命名空间的一些可以用做手控同步的类。它们赋予了程序员使用类似于WIN32线程API的低级线程API创建和管理多线程应用程序的能力。如:Auto ResetEvent类、ManualResetEvent类、Mutex类、Interlocked类。
1、ManualResetEvent类
通知一个或多个正在等待的线程已发生事件,根据这个信号正在等待的线程决定是否继续向下运行。ManualResetEvent对象只能拥有两种状态:有信号(True)或无信号(False)。
ManualResetEvent 就象灯塔的信号灯,作用是阻塞一个或多个线程,直到收到一个信号告诉ManualResetEvent不要再阻塞当前的线程。
如果有信号,船只一路畅通无阻,当前线程勇往直前运行,即使遇WaitOne也继续前行;
如果无信号,船只一路遇礁则止,当前线程遇WaitOne则挂起受阻。
Imports System.Threading Namespace NETThreadEvents Class AManualReset Shared LightSign As New ManualResetEvent(False) '1、无信号 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、恢复有信号,线程甩开阻碍继续畅通向前运行 Console.Read() End Sub Public Shared Sub RunOrWait() Console.WriteLine("ID " & Thread.CurrentThread.GetHashCode.ToString & " waiting... ") LightSign.WaitOne() '2、无信号时线程挂起受阻,有信号则畅通 Console.WriteLine("ID " & Thread.CurrentThread.GetHashCode.ToString & "Running... not blocked!") End Sub End Class End Namespace
说明:左图:1处设置为False,指示线程遇WaitOne受阻挂起,所以在RunOrWait()方法(2处)的WaitOne时就挂起了。中图:1处设置为True,线程畅通,2处WaiOne直接通过。右图:1处为false无信号,所以在2处受阻,但是由于3处加了一句Set,让信号亮起,前面挂起的恢复通过,没挂起当然也通过。图中可以看出10-12线程受阻,由于主线程Set恢复信号后,原挂起的11、12线程继续跑起,至于谁抢到CPU由OS决定。
注意:至于终结与未终结,MSDN:
原文:Reset Sets the state of the event to non-signaled, which causes threads to block. (Inherited from EventWaitHandle.)
原译文:Reset 将事件状态设置为非终止状态,从而导致线程受阻。 (从 EventWaitHandle 继承。)
因为non-signaled被翻译成了未终止状态。把受阻线程设置成无信号事件状态。
2、AutoResetEvent类
通知正在等待的线程已发生事件。处于等待状态的线程,直到通过调用Set()方法将它置于信号通知状态。
AutoResetEvent和ManualResetEvent是类似的。但有少些区别。
AutoResetEvent和ManualResetEvent的区别:
(1) AutoResetEvent只会给一个线程发送信号(非全部)。ManualResetEvent给全部线程发信号。
(2) AutoResetEvent在set()后,会将线程状态自动置为false。ManualResetEvent在Set()后,线程的状态就变为true,必须手动ReSet()之后,才会重新将线程置为false。
所以ManualResetEvent就象大门(Sign)一打开(True),所有马(Threads)都跑了,且门一直开启True。AutoResetEvent只随机开一匹的门(Set),放跑(True)后马上关闭(False)。
Imports System.Threading Namespace NETThreadEvents Class AManualReset Shared LightSign As New AutoResetEvent(False) '1、无信号 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,随机释放某一已经阻塞的线程(不是全部) 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
说明:Set后,仅将一个线程变成True,所以 ID9线程通过了,ID12仍然阻塞.
3、Mutex类(mutex互斥)
Mutex锁提供了交叉线程和交叉同步进程。如果没有线程拥有有信号状态,Mutex的状态就会被置为有信号状态。Mutex并不具有Monitor类的所有等待和脉冲功能,不过它确实提供了能够在进程之间使用的命名互斥体(使用重载 构造函数)的创建。使用Mutex类优于Monitor类的是:Mutex类可跨进程使用,而Monitor类则不行。
Mutex(boolean initiallyOwned, string name) 初始化 Mutex 类的新实例。
initiallyOwned: 若为 true,调用端拥有互斥体的初始所属权;否则为 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处得到锁,以便向下运行) 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处会无限等待锁(最后抛出异常) End Sub End Class End Namespace
说明: 主线程创建互斥锁后,拥有该锁,等待3秒后在1处释放锁,以便让线程t拥有锁后(3处)继续向下运行,直到释放锁(4处),释放后主线程马上得到锁并在阻塞的2处继续向下运行。4处必须释放原因:MSDN: 在桌面的 .NET 中,如果没有线程拥有互斥体,则互斥体的状态为终止并将在下一个获取互斥体的线程中引发 AbandonedMutexException。
4、Intelocked类
Interlocked类为原子操作,在多个线程之间共享的非阻塞整数更新提供了方法。如果变量位于共享的内存中的话,不同进程的线程以使用这种机制。
原子操作,即不可分割操作,一个线程操作时,不会切换到另一个操作。可简单理解为独占模式。
Interlocked可以为多个线程共享的变量提供原子操作:
Interlocked.Increment:以原子操作的形式递增指定变量的值并存储结果。
Interlocked.Decrement 以原子操作的形式递减指定变量的值并存储结果。
Interlocked.Add 以原子操作的形式,添加两个整数并用两者的和替换第一个整数
注意:Intelocked原子操作锁定的是某一个值,仅在类似上面三句中生效,越过此句原子操作失效。
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、线程池排队 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
说明:下面左右两图都是上面的结果,特别是左图,为啥会出现两个11,而少了10呢?首先确认10个线程进入后是进行了原子操作,每个线程增加1,因为10个线程的结果都是15(=5+10),因为原子操作仅在2处,过了此句后面的都不是原子操作。细节为:A线程原子锁定i(=9),然后增加1(i=10),后而退出原子操作马上要执行后一句提取i值(但还没显示),这里B线程切换进来,原子锁定i并对i增加1(i=10+1=11),然后原子操作退出并提取i值到B线程中。注意A、B线程提取的i值都是11,故显示的都是11,至于中间的12则是在输出过程中一样是要花费时间的,被另一线程C切入并提取i值12,并于B线程前输出。在3处增加一句显示i的结果,上面的效果将更加明显。
5、共享变量、方法和同步
共享(Shared)的变量和方法,既可以被类访问也可被该类的实例访问,所以同步锁定Shared的变量或方法就被应用到整个该类上。此时,其它对象不允许使用此类的Shared变量或方法。
ThreadStaticAttribute 类
带有ThreadStaticAttribute 的Shared变量,对每个访问变量的线程都会有一个同一变量的单独副本。意味着如果一个线程修改了变量,另一个访问变量的线程就不能看到这些变化(包括主线程)。这种行为是有背于Shared变量的默认行为的。用处比如web应用中,每个请求都是一个独立的线程,如果我们希望将一个值作为静态字段全局使用,同时又不想影响其他用户,这时候一般我们是使用Session的。
Imports System.Threading Namespace TestShared Class AThreadStatic <ThreadStatic> Public Shared x As Integer '1、共享变量,各线程分别有副本x Public Shared y As Integer = 1 '2、共享变量,各线程无副本 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次循环,花时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
说明:x被标注后,在两个线程中将分别生成各自的x的副本(并不影响原类的值),所以在4处可以看到原类中x仍为0,但y没被标注,所以值是变化的。最后的结果(5处),也可以看出始终x没被修改为0(仅在各自的线程中修改其副本),y是修改的为11。
(四)防止死锁
线程越多,上锁越复杂,就越容易死锁。通常的防止的原则就是:一个线程最多只能有一个锁。
如果想更多的锁,就越容易死锁。例如:A线程中拥有锁L1期待锁L2;而B线程中拥有锁L2期待锁L1;如果两个同时发生或重叠,就会发生死锁。当然如果时间错过也就无所谓。
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
说明:两个线程都分别拥有锁,还期待对方向的锁,都处于等待对方的锁,于是就死锁了。