VB.net学习笔记(二十八)线程同步下

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

说明:两个线程都分别拥有锁,还期待对方向的锁,都处于等待对方的锁,于是就死锁了。

时间: 2024-10-03 07:51:19

VB.net学习笔记(二十八)线程同步下的相关文章

【Unity 3D】学习笔记二十八:unity工具类

unity为开发者提供了很多方便开发的工具,他们都是由系统封装的一些功能和方法.比如说:实现时间的time类,获取随机数的Random.Range( )方法等等. 时间类 time类,主要用来获取当前的系统时间. using UnityEngine; using System.Collections; public class Script_04_13 : MonoBehaviour { void OnGUI() { GUILayout.Label("当前游戏时间:" + Time.t

angular学习笔记(二十八)-$http(6)-使用ngResource模块构建RESTful架构

ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入angular-resource.min.js文件 2.在模块中依赖ngResourece,在服务中注入$resource var HttpREST = angular.module('HttpREST',['ngResource']); HttpREST.factory('cardResource

马哥学习笔记二十八——nginx反向代理,负载均衡,缓存,URL重写及读写分离

Nginx反向代理 Nginx通过proxy模块实现反向代理功能.在作为web反向代理服务器时,nginx负责接收客户请求,并能够根据URI.客户端参数或其它的处理逻辑将用户请求调度至上游服务器上(upstream server).nginx在实现反向代理功能时的最重要指令为proxy_pass,它能够将location定义的某URI代理至指定的上游服务器(组)上.如下面的示例中,location的/uri将被替换为上游服务器上的/newuri. location /uri { proxy_pa

angular学习笔记(二十八-附2)-$resource中的promise对象

下面这种promise的用法,我从第一篇$http笔记到$resource笔记中,一直都有用到: HttpREST.factory('cardResource',function($resource){ return $resource('/card/user/:userID/:id',{userID:123,id:'@id'},{charge:{method:'POST',params:{charge:true},isArray:false}}) }); HttpREST.factory('h

angular学习笔记(二十八-附1)-$resource中的资源的方法

通过$resource获取到的资源,或者是通过$resource实例化的资源,资源本身就拥有了一些方法,比如$save,可以直接调用来保存该资源: 比如有一个$resource创建的服务: var service = angular.module('myRecipe.service',['ngResource']); service.factory('Recipe',['$resource',function($resource){ return $resource('/recipe/:id',

Java基础学习笔记二十八 管家婆综合项目

本项目为JAVA基础综合项目,主要包括: 熟练View层.Service层.Dao层之间的方法相互调用操作.熟练dbutils操作数据库表完成增删改查. 项目功能分析 查询账务 多条件组合查询账务 添加账务 编辑账务 删除账务 项目环境搭建 技术选型和jar包介绍 每个项目都要使用一些已经成熟的技术,它们通常是由一些专业组织或团队所提供的开源免费技术.在今后的学习过程中,我们会逐渐对这些专业组织有所了解.本项目中使用的技术如下: apache的commons组件: commons-dbutils

[傅里叶变换及其应用学习笔记] 二十八. 高维移位定理

高维傅里叶变换的移位定理 在一维傅里叶变换的移位定理时,有 $f(t) \quad \leftrightarrow \quad F(s)$ $f(t-b) \quad \leftrightarrow \quad e^{-2\pi isb}F(s)$ 在二维傅里叶变换的移位定理时,有两个变量,可分别对它们进行移位, $f(x_1,x_2) \quad \leftrightarrow \quad F(\xi_1,\xi_2)$ $f(x_1-b_1,x_2-b_2) \quad \leftright

PHP学习笔记二十八【抽象类】

<?php //定义一个抽象类.主要用来被继承 //如果一个类继承了抽象类,则它必须实现该抽象类的所有抽象方法(除非它自己也是抽象类) // abstract class Animal{ public $name; protected $age; abstract function Cry();//定义抽象方法,不需要方法体,子类必须实现它 public function Run (){//抽象类可以定义非抽象方法 echo "动物在跑.......<br/>"; }

《Javascript权威指南》学习笔记之十八:BOM新成就(1)--客户端存储数据(Web SQL DataBase实现)

使用本地存储和会话存储可以实现简单的对象持久化,可以对简单的键值对或对象进行存储.但是,对于比较复杂的关系数据进行处理时,就要用Web SQL Database.浏览器对Web SQL Database的支持情况如图: 一.如何使用Web SQL Database <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-t

Android学习笔记二十之Toast吐司、Notification通知、PopupWindow弹出窗

Android学习笔记二十之Toast吐司.Notification通知.PopupWindow弹出窗 Toast吐司 Toast吐司是我们经常用到的一个控件,Toast是AndroidOS用来显示消息的一种机制,它与Dialog不同,Toast不会获取到焦点,通常显示一段时间之后就会自动消失,下面我们来介绍Toast的几种常用方式: 第一种,默认显示方式,也是最常用的方式: Toast.makeText(MainActivity.this, "这是默认的显示方式", Toast.LE