VB.net学习笔记(三十)认识线程池

水是生命之源,计算机资源也一样。

每一线程尤如一滴水,你花一滴,我花一滴,你还一滴,我还一滴,就象游兵散将一样,线程越多,越复杂混乱。而每一个线程创建需要开销,活动的线程也需要开销。过多的线程导致系统内存占用过度或系统资源不足。为了解决线程生命周期开销问题和资源不足问题,创建线程池,让每滴水(线程)纳入统一管理。特别是那些生存期比较短暂的线程。使用线程池执行任务比每次完成一个任务时都创建一个全新的线程,随后又删除掉的做法更有效率。

一、线程池管理

线程池管埋是指在多线程应用程序的初始化过程屮创建线程的一个集合,当需要线程时,为新任务重用这些线程,而不是创建新的线程的过程。这个过程中创建的线程数量通常部是固定的。然而,增加可用的线程数量也是可以的。池中的每个线程都被分派一个任务,当任务完成时,线程就返回线程池中等待下一次分派。

1.什么时候需要用线程池

线程池在多线程应用程序中是必需的,原因如下:

(1)线程池有效改善了应用程序的响应时间,因为线程池中的线程是现成的,就处于等待任务分派的状态中,系统无需再从头创建线程。

(2)在线程池方式下,CLR节省了为每个生存期短暂的任务创建一个全新线程,并在其结束时回收其资源的开销。

(3)线程池根据系统当前正在运行的进程情况优化线程时间片。

(4)线程池允许我们在不逐一设置线程的属性的情况下,启动多个线程。

(5)线程池允许我们将状态信息作为一个对象传递给当前正在执行任务的过程参数。

(6)线程池可以将处理客户请求的线程数量固定为某一个最大值。

2.线程池的概念

影响多线程应用程序的响应性的一个主要原因就是为每个任务创建线程的时间开销。

例如,Web服务器是响应10个客户的访问,以前的做法是:若服务器采用为每个客户创建一个线程的策略,则需创建10个新线程。服务器将承担线程的创建、在整个生存期内管理这些线程的开销。在某个时刻,线程可能会耗尽整个系统的资源。替代的方法:若服务器使用线程池来响应客户请求,每当客户发出请求时,系统就无需再为创建线程耗费时间。这就是线程池管理方式的重要概念。

Windows操作系统为响应客户请求维护一个线程池。当我们的应用程序请求一个新的线程时,Windows就试图从池中取出一个,如果线程池是空的,那就创建一个新的供我们使用。Windows将动态处理线程池的大小以加快应用程序的响应时间。

影响多线程应用程序线程设计的因素有:应用程序的响应性、线程管理资源的分配、资源共享、线程同步。

二、CLR与线程池

CLR是专门用于创建托管代码环境,为在.NET平台上运行的应用程序提供各种服务的,例如编译、垃圾收集、内存管理,还有线程池。

确实,在定义宿主应用程序使用的线程的进程方面,Win32和.NET Framework有着显著的差别。

在传统的多线程Win32应用程序中,每个进程都是由线程集合组成的。每个线程又由线程本地存储(Thread Local Storage,TLS)、调用堆栈组成,用于在单处理器系统中提供时间片。单处理器系统根据线程的优先级为每个线程分配时间片。当某个特定线程的时间片消耗完时,它就会处于挂起状态,其他线程就将开始执行其任务。

在.NET Framework中,每个进程都可分成多个应用程序域,它是用于宿主线程以及TLS和调用堆栈的。值得关注的是,进程间的通信是通过.NETFramework中的一个称为远程处理的技术来进行处理的。

1.CLR管理线程池

CLR构成了.NET Framework的灵魂和核心,为托管应用程序提供多个服务(线程池管理就是其中之一)。对于线程池中排在队列中的每个任务(任务项),CLR从线程池中指派一个线程(工作者线程),然后在任务结束时将线程释放回池中。

线程池总是通过CLR使用多线程单元模式,借助抢先式多任务管理使用高性能的队列和调度程序来实现的。它是CPU时间被分成多个时间片的一个过程。在每个时间片中,都有个特定的线程在执行,而其他线程则处于等待状态。一旦这个时间片用完之后,系统就根据剩余线程的最高优先级决定由哪个线程使用CPU。客户请求排在任务队列中,队列中的毎个任务都将被分配给线程池中第一个可用的线程。

一旦线程完成了分配给它的任务,它就返回到线程池中等待CLR的下一次分配。

线程池的大小可以是固定不变的,也可以是动态变化的。在前面的示例中,线程的数量在线程池的生存期间不发生变化。通常情况下,这种类型的线程池用于我们确切知道应用程序可用资源的数量的情况,这样固定数目的线程就可以在线程池初始化过程中创建完成。

面这种情况正好适用于这种类型:我们为企业内部网开发解决方案或者在可以严格定义目标平台的系统需求的应用程序中,大小动态可变的线程池适用于不知道可用资源数量的情况,因为在Web服务器的情况下,我们不知道将要同时处理多少客户请求。

 2. 避免使用线程池的情况

尽管线程池在我们构建多线程应用程序时给我们带來了大量的好处,但是,下面情况应避免使用线程池:

(1)CLR将线程池中的线程分配给任务,并在任务完成时将线程释放间池中。如果任务已经被添加到队列中,此时就没有直接的方法可以终止任务。

(2)线程池管理对于任务生存期较短的情况非常有效,例如Web服务器响应客户对某个特定文件的请求。线程池不适用又大又长的任务。

(3)线程池管理是一种以成本效率方式使用线程的技术,此处成本效率根据数量和启动开销来确定,决定使用池中线程的时候要十分小心。线程池的大小应该固定不变。

(4)线程池中的所有线程都是处于多线程单元之中。如果我们想把线程放置到单线程单元中,那么线程池就没有用了。

(5)如果我们想标识线程并执行各种操作,例如启动线程、挂起和中止等,那么线程池不能完成这样的工作。

(6)同样,我们不可能对使用线程池的任务设置优先级。

(7)对任意给定的进程,只能有一个线程池与其相关联。

(8)如果分配给线程池中的一个线程的任务被锁定,那么这个线程将不会再释放回池中。这种情况可以通过使用有效的编程技巧加以避免。

 3.线程池的大小

线程池中可以排队等待的任务数量取决于机器的内存数量。同样,在进程中可以激活的线程数量取决于机器中的CPU个数。

正如我们己经知道的,这是因为每个处理器在同一时间只能执行一个线程,默认情况下,处在多线程单元中的线程池的每个线程都将使用默认的任务,运行库将采用默认的优先级。此处使用的单词“默认”显得似乎有些不太明确,但这不会产生任何问题。每个系统都有的默认优先级设置。

在任意时刻,如果某个线程处于空闲状态,那么线程池就会引导工作者线程使所有的处理器保持繁忙。如果线程池中的所有线程都处于繁忙状态,并且队列中有未处理的任务,那么线程池将产生一个新的线程来完成待处理的工作。但是,产生的线程数量不能超过指定的最大值。

默认情况下,每个进程可以产生25个线程池线程。然而,这个数量可以通过编辑mscoree.h文件中定义的CorSetMaxThreads成员加以改变,万一要是有额外的线程请求的话,那么这个请求将加入到队列中,直到某些线程完成了分配给它的任务返回到线程池中为止。

.NET Framework对异步调用、建立套接字连接和注册过的等待操作等使用线程池功能。

三、ThreadPool 类

为了在应用程序中使用线程池,.NET Framework在System.Threading命名空间中提供了 ThreadPool类。ThreadPool类提供的线程池可以用来解决以下问题: 处理任务项、处埋异步I/O调用、处理计时器、代表其他线程等待。

BindHandle(SafeHandle)

将操作系统句柄绑定到 ThreadPool。返回值True绑定成功,False绑定失败。

osHandle           保存操作系统句柄的 SafeHandle。在非托管端必须为重叠 I/O 打开该句柄。

GetAvailableThreads(workerThreads, completionPortThreads) 

检索由 GetMaxThreads 方法返回的最大线程池线程数和当前活动线程数之间的差值。

WorkerThreads          线程池中工作者线程的最大数目。

completionPortThreads  线程池中异步 I/O 线程的最大数目。

GetMaxThreads(workerThreads, completionPortThreads)

检索可以同时处于活动状态的线程池请求的数目。所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。

workerThreads           线程池中工作者线程的最大数目。

completionPortThreads   线程池中异步 I/O 线程的最大数目。

QueueUserWorkItem(WaitCallback)

ThreadPool.QueueUserWorkItem(WaitCallback, state)

将一个任务项排列到线程池中。返回值True执行成功,False执行失败。

callBack                一个 WaitCallback(委托),表示要执行的方法。

State                   传递给委托的对象。

RegisterWaitForSingleObject(WaitHandle, WaitOrTimerCallback,state, Int32, Boolean)

注册一个等待 WaitHandle 的委托,并指定超时值(毫秒)。

waitObject                    要注册的WaitHandle。使用WaitHandle而非Mutex。

callBack                   向waitObject参数发出信号时调用的WaitOrTimerCallback委托。

State                       传递给委托的对象。

millisecondsTimeOutInterval 以毫秒为单位的超时。为0立即返回。为-1永远不过期。

executeOnlyOnce            为true表示委托后线程将不再在waitObject参数上等待;为false表示每次完成等待操作后都重置计时器,直到注销等待。

 UnsafeRegisterWaitForSingleObject(WaitHandle,WaitOrTimerCallback,Object,Int32,Boolean)

注册一个等待 WaitHandle 的委托,并使用超时时间(毫秒)。此方法不将调用堆栈传播到辅助线程。

waitObject                     要注册的 WaitHandle。使用 WaitHandle 而非 Mutex。

callBack                       向 waitObject 参数发出信号时调用的委托。

State                          传递给委托的对象。

millisecondsTimeOutInterval    以毫秒为单位的超时。为0立即返回。为-1永远不过期。

executeOnlyOnce             为true表示委托后线程将不再在waitObject参数上等待;为false表示每次完成等待操作后都重置计时器,直到注销等待。

四、VB.NET中线程池的编程

ThreadPool类的3个规则:

(1)每个ThreadPool对象只能有一个工作者线程;

(2)每个进程只能有一个ThreadPool对象;

(3)第一次创建ThreadPool对象是当我们调用ThreadPool.QueueUserWorkItem( )方法,或者是通过计时器或已注册等待的操作调用回调方法时发生的。

ThreadPool类的一个普通用法就是不用设置每个线程的属性而启动多个独立的任务。

例:微软例子

Imports System.Threading
Public Class Example
    <MTAThread> Public Shared Sub Main()
        ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf ThreadProc)) '委托方法入队
        'ThreadPool.QueueUserWorkItem(AddressOf ThreadProc)              ‘等同上面语句

        Console.WriteLine("Main thread does some wor, then sleeps.")
        Thread.Sleep(1000)

        Console.WriteLine("Main thread exits.")
        Console.ReadLine()
    End Sub
    Shared Sub ThreadProc(stateInfo As Object) '无状态传来,为空
        Console.WriteLine("Hello from the thread pool.")
    End Sub
End Class

说明:创建线程池后,两线程入队,Main线程活动时,ThreadProc只能等待;Main线程Sleep时,ThreadProc激活,其执行完后,Main线程又激活。

  例:理解线程池中各线程执行次序

Imports System.Threading
Friend Class ObjState
    Friend inarg1 As String
    Friend inarg2 As String
    Friend outval As String
End Class
Module ThreadAppModule
    Sub Taskl(ByVal StateObj As Object)     '任务1
        Dim StObj As ObjState = CType(StateObj, ObjState) '传来的状态转类型
        Console.WriteLine("Input Argument 1 in task 1:" & StObj.inarg1)
        Console.WriteLine("Input Argument 2 in task 1: " & StObj.inarg2)
        StObj.outval = "From Task1 " & StObj.inarg1 & " " & StObj.inarg2 '此对象还可作返回值
    End Sub
    Sub Task2(ByVal StateObj As Object)     '任务2
        Dim StObj As ObjState = CType(StateObj, ObjState)
        Console.WriteLine("Input Argument 1 in task 2:" & StObj.inarg1)
        Console.WriteLine("Input Argument 2 in task 2:" & StObj.inarg2)
        StObj.outval = "From Task2 " & StObj.inarg1 & " " & StObj.inarg2
    End Sub
    Sub Main()
        Dim StObj1 As New ObjState()
        Dim StObj2 As New ObjState()
        StObj1.inarg1 = "String Param1 of task 1" '分别设置两对象值
        StObj1.inarg2 = "String Param2 of task 1"
        StObj2.inarg1 = "String Param1 of task 2"
        StObj2.inarg2 = "String Param2 of task 2"
        '进程中只有一个Threadpool,且成员多为共享,故不必单独为其实例化对象,直接用类。
        ThreadPool.QueueUserWorkItem(New Threading.WaitCallback(AddressOf Taskl), StObj1)
        ThreadPool.QueueUserWorkItem(New Threading.WaitCallback(AddressOf Task2), StObj2)
        Console.Read()
    End Sub
End Module

说明:任务1与任务2通过线程池逐个激活(反正不让任务线程空闲),传递给委托方法的对象都被转了定义的ObjState类型。

例:理解定时触发器与开关门的关系

Imports System.Threading
Public Class vbThreadPool
    Private Shared i As Integer = 0
    Public Shared Sub main()
        Dim arev As New AutoResetEvent(False)      '1、自动同步
        'Dim arev As New ManualResetEvent(False)   ‘2、手动同步
        '注册(添加)一个定时器(定时触发委托项),第一参数为开关量以确定开或关,此处False为关,受阻。
        ThreadPool.RegisterWaitForSingleObject(arev, AddressOf workitem, Nothing, 1000, False) '3、定时触发器
        arev.Set()   '4、开关量打开(True),定时器启动(如Timer一样,每1秒调用委托方法)
        Console.Read()
    End Sub
    Public Shared Sub workitem(ByVal obj As Object, ByVal signaled As Boolean)
        i += 1
        Console.WriteLine("Thread Pool Work Item Ivoked:" & i.ToString)
    End Sub
End Class

说明:本例代码简单,理解较难。

1、理解线程池的RegisterWaitForSingleObject。

它是一个定时触发器,类似Timer控件一样,即每隔一段时间触发委托方法。

第一参数WaitHandle是一个关开量(开True,关False),它可以是手动同步ManualResetEvent或自动同步AutoResetEvent,它类似一个大门;第二参数委托方法,类似大门内关着的一匹马;第三参数state状态对象,类似给马的货物或装饰(当然也可以没有Nothing);第四参数间隔时间,类似开大门时需要间隔多久(每开一次门,就跑出一匹马,方法就执行起来了),第五参数是否只开一次门(方法也就只能执行一次,如右图)。为真,总共只执行一次;为假,不止执行一次。

因此,上面3处代码可以解释为:每隔1秒触发打开大门,马儿就跑出来,马儿无货物托运,并且总共大门不止开一次。即一直间隔1秒地去开门。

2、理解Set

Set的目的,在AutoResentEvent自动同步中,直接打开门(True),于是下面左图或右图第一语句都是没有等1秒,就执行了。因为Set是另一个人来打开门(不是定时触发器来打开门),也即打开门有两种方式。既然门开了,马儿就直接跑出来了,还管什么定时触发器的1秒间隔?所以第一句都没有等待1秒,就直接显示出来了。

但后面语句(红箭头)都是等待了1秒才显示。这里又涉及到手动同步(ManualResetEvent)或自动同步(AutoResetEvent)的区别:

若是用的1处的自动同步(AutoResetEvent),它就类似自动门一样,打开(Set为真)后,跑出一匹马,然后大门会自动关上(为假),所以这里定时触发器的间隔时间就体现出来了:每隔一秒去开大门,跑出马后会自动关上大门,所以后面每隔一秒会显示信息。

若是用的2处的手动同步(ManualResetEvent),情况就不一样的,大门不是自动大门,打开(Set其为True)后不会自动关门(False)。如果用了4处的开大门,大门不会再关闭,大大地敞开,于是马儿一匹接一匹的向外跑,于是信息就无间隔的连续一直显示下去。定时触发器中的时间间隔就不会起作用。

本来定时触发器也有自动关门的作用,因为没有开也就没有与之对应的关(因为开是别人做的),所以它也只有干瞪眼看着一匹接一匹的马儿跑。如果我们注释掉1处使用2处语句,同时注释掉4处,就会发现:第一句也会间隔1秒(因为没人去开门,所以定时触发器来开门),然后,后面也是每隔一秒显示(因为定时触发器自已开门,也会自动去关门)。

五、在.NET中的可伸缩性

Windows 操作系统管理着如何将线程分配给处理器,同时,触发任何进程会自动启动一个线程。.NET Framework对处理器分配不提供细粒度的控制,它宁愿让操作系统控制调度,因为与CLR相比,处理器提供更多的载入信息。然而,它还是对整个进程运行于哪个处理器提供了一些控制措施。但是这适用于进程中的所有线程,选择哪些处理器来处理超出了我们的话题。

如果只有一个线程(即主线程),那么线程中的每个任务都将运打在相同的处理器上。然而,如果创建一个新的线程,那么操作系统将安排线程将在哪个处理器上执行。系统在作出这个判断时会消耗一些处理器资源,所以对于那些小的任务而言,这种消耗通常不值得,因为执行这些任务的时间几乎和判断由哪个处理器来执行线程的时间一样。

然而,随着Windows版本的延续,这种分配占用的时间越来越少,同时对于除最细微的任务之外的事情而言,当使用线程时,您将发现通过创建新线程来执行任务将提高系统性能。这也只有在对称多处现器(symmetric multi-processor, SMP)系统中您才能看到线程的真正优点,因为所有的处理器都会被充分用于分配承担应用程序的负荷。

通用线程池管理器类的设计

以前创建线程的方法过于松散自由,现在线程池又过于僵硬呆板。下面创建一个线程池管理类,它维护指定数量线程的线程池,这些线程用于请求的应用程序。这样既可以在代码中更加容易控制这些线程,同时在实例化线程对象时更快地执行线程。简单地说,就是综合两者的优点。

代码较长,逻辑图有点类似下面(引用别人的图),只是少了一部分超时的判断。

提示:右击关键词,可以选择“转到定义”(迅速到定义处)或“转到实现”(迅速到实现处),工具栏左端面有一个“后退”,可迅速回到前一个代码定位处。

首先添加一个新的类(管理器类):

Imports System.Threading
Imports System.Text
Namespace GenThreadPool '通用线程池管理
    Public Interface IThreadPool       'ThreadPool接口,用于GenThreadPoolImpl类
        Sub AddJob(jobToRun As Thread) '添加作业
        Function GetStats() As Stats   '获取状态
    End Interface

    Public Class GenThreadPoolImpl
        Implements IThreadPool
        Private m_maxThreads As Integer         '最多线程
        Private m_minThreads As Integer         '最少线程
        Private m_maxIdleTime As Integer        '最长空闲时间(超时删除对应线程)
        Private Shared m_debug As Boolean       '是否调试
        Private m_pendingJobs As ArrayList      '等待的作业量(数组形式,以便列队进入线程池)
        Private m_availableThreads As ArrayList '可用线程数

        Public Property PendingJobs() As ArrayList
            Get
                Return m_pendingJobs
            End Get
            Set
                m_pendingJobs = Value
            End Set
        End Property
        Public Property AvailableThreads() As ArrayList
            Get
                Return m_availableThreads
            End Get
            Set
                m_availableThreads = Value
            End Set
        End Property
        Public Property Debug() As Boolean
            Get
                Return m_debug
            End Get
            Set
                m_debug = Value
            End Set
        End Property
        Public Property MaxIdleTime() As Integer
            Get
                Return m_maxIdleTime
            End Get
            Set
                m_maxIdleTime = Value
            End Set
        End Property
        Public Property MaxThreads() As Integer
            Get
                Return m_maxThreads
            End Get
            Set
                m_maxThreads = Value
            End Set
        End Property
        Public Property MinThreads() As Integer
            Get
                Return m_minThreads
            End Get
            Set
                m_minThreads = Value
            End Set
        End Property

        Public Sub New()       '默认构造。只允许1个线程在池中,0.3秒后销毁
            m_maxThreads = 1
            m_minThreads = 0
            m_maxIdleTime = 300
            m_pendingJobs = ArrayList.Synchronized(New ArrayList)      '对数组同步包装(仍为数组),因为这样上
            m_availableThreads = ArrayList.Synchronized(New ArrayList) '锁才线程安全,以防其它线程同时进行修改。
            m_debug = False
        End Sub
        Public Sub New(ByVal maxThreads As Integer, ByVal minThreads As Integer, ByVal maxIdleTime As Integer)
            '构造函数,用3个参数实例化
            m_maxThreads = maxThreads
            m_minThreads = minThreads
            m_maxIdleTime = maxIdleTime
            m_pendingJobs = ArrayList.Synchronized(New ArrayList)
            m_availableThreads = ArrayList.Synchronized(New ArrayList)
            m_debug = False
            InitAvailableThreads()
        End Sub
        Private Sub InitAvailableThreads() '初始化线程池,分别进入池中
            If m_maxThreads > 0 Then
                For i As Integer = 1 To m_maxThreads
                    Dim t As New Thread(AddressOf (New GenPool(Me, Me)).Run)
                    Dim e As New ThreadElement(t)
                    e.Idle = True
                    m_availableThreads.Add(e)
                Next
            End If
        End Sub
        Public Sub New(ByVal maxThreads As Integer, ByVal minThreads As Integer, ByVal maxIdleTime As Integer, ByVal debug As Boolean)
            '构造函数,用4个参数实例化,增加调试参数
            m_maxThreads = maxThreads
            m_minThreads = minThreads
            m_maxIdleTime = maxIdleTime
            m_pendingJobs = ArrayList.Synchronized(New ArrayList)
            m_availableThreads = ArrayList.Synchronized(New ArrayList)
            m_debug = debug
            InitAvailableThreads()
        End Sub
        Public Sub AddJob(ByVal job As Thread) Implements IThreadPool.AddJob '向池中添加作业
            If job Is Nothing Then
                Return  '作业不存在,退出
            End If

            SyncLock Me                   '锁定GenThreadPoolImpl,防止其它线程来添加或删除作业
                m_pendingJobs.Add(job)    '将作业添加到 ArrayList 的结尾处。
                Dim index As Integer = FindFirstIdleThread()     '空闲可用线程的索引
                If m_debug Then
                    Console.WriteLine("First Idle Thread Is " & index.ToString)
                End If

                If index = -1 Then        '-1无空闲线程,故需创建新的线程
                    If m_maxThreads = -1 Or m_availableThreads.Count < m_maxThreads Then
                        '池中无线程,或在用(有效)线程还未达最大线程数限制--->创建线程
                        If m_debug Then
                            Console.WriteLine("Creating a New thread")
                        End If

                        Dim t As New Thread(AddressOf (New GenPool(Me, Me)).Run)
                        Dim e As New ThreadElement(t) '帮助类,提供线程额外属性
                        e.Idle = False
                        e.GetMyThread.Start()         '线程添加到ArrayList数组前先激发

                        Try                           '添加
                            m_availableThreads.Add(e)
                        Catch ex As OutOfMemoryException
                            Console.WriteLine("Out Of memory: " & ex.ToString)
                            Thread.Sleep(3000)
                            m_availableThreads.Add(e)
                            Console.WriteLine("Added Job again")
                        End Try
                        Return
                    End If

                    If m_debug Then
                        Console.WriteLine("No Threads Available...” & GetStats.ToString)
                    End If
                Else  '池中找到有空的线程
                    Try
                        If m_debug Then
                            Console.WriteLine("Using an existing thread...")
                        End If

                        CType(m_availableThreads(index), ThreadElement).Idle = False   '标注忙碌
                        SyncLock CType(m_availableThreads(index), ThreadElement).GetMyThread()
                            Monitor.Pulse(CType(m_availableThreads(index), ThreadElement).GetMyThread())
                        End SyncLock
                    Catch ex As Exception
                        Console.WriteLine(("Error while reusing thread " & ex.Message))
                        If m_debug Then
                            Console.WriteLine("Value of index Is " & index.ToString)
                            Console.WriteLine("Size of available threads Is " & m_availableThreads.Count.ToString)
                            Console.WriteLine("Available Threads Is " & m_availableThreads.IsSynchronized.ToString)
                        End If
                    End Try
                End If
            End SyncLock
        End Sub
        Public Function GetStats() As Stats Implements IThreadPool.GetStats  '池中状态
            Dim statsInstance As New Stats()
            statsInstance.MaxThreads = m_maxThreads
            statsInstance.MinThreads = m_minThreads
            statsInstance.MaxIdleTime = m_maxIdleTime                     '最大空闲时间
            statsInstance.PendingJobs = m_pendingJobs.Count               '等待的作业量
            statsInstance.NumThreads = m_availableThreads.Count           '有效线程数
            statsInstance.JobsInProgress = m_availableThreads.Count - FindIdleThreadCount() '正在处理的作业量
            Return statsInstance
        End Function
        Public Function FindIdleThreadCount() As Integer  '遍历有效线程,返回空闲线程数
            Dim idleThreads As Integer = 0
            For i As Integer = 0 To m_availableThreads.Count - 1
                If CType(m_availableThreads(i), ThreadElement).Idle Then '根据帮助类中的空闲标志Idle来统计
                    idleThreads += 1
                End If
            Next
            Return idleThreads
        End Function
        Public Function FindFirstIdleThread() As Integer '遍历,找到第一个空闲线程,就立即返回其索引
            For i As Integer = 0 To m_availableThreads.Count - 1
                If CType(m_availableThreads(i), ThreadElement).Idle Then
                    Return i
                End If
            Next
            Return -1
        End Function
        Public Function FindThread() As Integer '查找当前线程的位置(索引号),失败为-1(说明池中无线程)
            For i As Integer = 0 To m_availableThreads.Count - 1
                If CType(m_availableThreads(i), ThreadElement).GetMyThread.Equals(Thread.CurrentThread) Then
                    Return i
                End If
            Next
            Return -1
        End Function
        Public Sub RemoveThread() '移除线程
            For i As Integer = 0 To m_availableThreads.Count - 1
                If CType(m_availableThreads(i), ThreadElement).GetMyThread.Equals(Thread.CurrentThread) Then
                    m_availableThreads.RemoveAt(i)
                    Exit Sub
                End If
            Next
        End Sub
    End Class
    Public Class GenPool '执行线程类(执行完毕后,过了指定超时时限,将自动从池中删除)
        Private m_lock As Object           '锁定对象
        Private m_gn As GenThreadPoolImpl
        Public Sub New(lock_ As Object, gn As GenThreadPoolImpl)
            m_lock = lock_
            m_gn = gn
        End Sub
        Public Sub Run() '循环运行并检测是否过期
            Dim job As Thread
            While True '无限循环,一直检查池中状态
                While True
                    SyncLock m_lock
                        If m_gn.PendingJobs.Count = 0 Then           '后续无作业进来
                            Dim index As Integer = m_gn.FindThread() '取当前线程索引号
                            If index = -1 Then               '无作业,池中也无线程,退出
                                Exit Sub
                            End If                                 '无作业,新的线程设为空闲
                            CType(m_gn.AvailableThreads(index), ThreadElement).Idle = True
                            Exit While
                        End If
                        job = CType(m_gn.PendingJobs(0), Thread) '有作业,取出(从原作业数组中删除)
                        m_gn.PendingJobs.RemoveAt(0)
                    End SyncLock
                    job.Start()             '作业执行启动
                End While
                Try   '无后续作业
                    SyncLock Me
                        If m_gn.MaxIdleTime = -1 Then '池中无空闲线程,阻塞等待
                            Monitor.Wait(Me)
                        Else
                            Monitor.Wait(Me, m_gn.MaxIdleTime)
                        End If
                    End SyncLock
                Catch
                End Try
                SyncLock m_lock
                    If m_gn.PendingJobs.Count = 0 Then '无等待的作业(没有新的作业进来)
                        If m_gn.MinThreads <> -1 And m_gn.AvailableThreads.Count > m_gn.MinThreads Then
                            m_gn.RemoveThread() '池中线程不空,且有效线程大于最小线程,删除线程
                            Return
                        End If
                    End If
                End SyncLock
            End While
        End Sub
    End Class

    Public Class ThreadElement '对应线程的线程帮助类(以例为之设计空闲标志,获取引用)
        Private m_idle As Boolean   '空闲线程标志
        Private m_thread As Thread
        Public Sub New(th As Thread)
            m_thread = th
            m_idle = True      '初始化即为空闲
        End Sub
        Public Property Idle() As Boolean '设置或获取线程空闲标志
            Get
                Return m_idle
            End Get
            Set
                m_idle = Value
            End Set
        End Property
        Public Function GetMyThread() As Thread '取得原线程
            Return m_thread
        End Function
    End Class

    Public Structure Stats  '状态统计
        Public MaxThreads As Integer
        Public MinThreads As Integer
        Public MaxIdleTime As Integer
        Public NumThreads As Integer
        Public PendingJobs As Integer     '列队等待的作业量
        Public JobsInProgress As Integer  '正在处理的作业量
        Public Overrides Function ToString() As String        '提取状态
            Dim sb As New StringBuilder("MaxThreads = ", 107) '容量大小107字符
            sb.Append(MaxThreads)
            sb.Append(ControlChars.Lf & "MinThreads=" & MinThreads)
            sb.Append(ControlChars.Lf & "MaxIdleTime=" & MaxIdleTime)
            sb.Append(ControlChars.Lf & "PendingJobs=" & PendingJobs)
            sb.Append(ControlChars.Lf & "JobsInProgress=" & JobsInProgress)
            Return sb.ToString
        End Function
    End Structure
End Namespace

然后,完成主程序代码,用来测试:

Imports System.Threading
Namespace TestGenThreadPool
    Public Class TestPerformance
        Public count As Integer
        Private m_lock As New Object()

        Public Sub New(pool As GenThreadPool.IThreadPool, times As Integer)
            Console.WriteLine("Performance using Pool [in ms]: ")
            count = 0
            Dim start As Long = Now.Millisecond
            Console.WriteLine("Start Time For Job Is " & Now)
            Dim i As Integer
            For i = 0 To times - 1
                Dim tl As New Thread(AddressOf (New Job(Me)).Run)
                pool.AddJob(tl)
            Next

            While True
                SyncLock m_lock
                    If count = times Then
                        Exit While
                    End If
                End SyncLock

                Try
                    Thread.Sleep(5000)
                Catch
                End Try
            End While

            Console.WriteLine(" " & (Now.Millisecond - start).ToString)
            Console.WriteLine("End Time for Job is " & Now.ToString)
            Console.WriteLine("Performance using no Pool [in ms]: ")
            count = 0
            start = Now.Millisecond
            Console.WriteLine("Start Time for JobThread is " & Now.ToString)
            For i = 0 To times - 1
                Dim jt As New Thread(AddressOf (New JobThread(Me)).Run)
                jt.Start()
            Next

            While True
                SyncLock m_lock
                    If count = times Then
                        Exit While
                    End If
                End SyncLock
                Try
                    Thread.Sleep(5000)
                Catch
                End Try
            End While
            Console.WriteLine(" " & (Now.Millisecond - start).ToString())
            Console.WriteLine("End Time for JobThread is ” & Now.ToString)
        End Sub
        NotInheritable Class JobThread
            Private m_lock As New Object()
            Private tpf As TestPerformance
            Public Sub New(tpf_ As TestPerformance)
                tpf = tpf_
            End Sub
            Public Sub Run()
                SyncLock m_lock
                    tpf.count += 1
                End SyncLock
            End Sub
        End Class
        NotInheritable Class Job
            Private m_lock As New Object()
            Private tpf As TestPerformance

            Public Sub New(tpf_ As TestPerformance)
                tpf = tpf_
            End Sub
            Public Sub Run()
                SyncLock m_lock
                    tpf.count += 1
                End SyncLock
            End Sub
        End Class
    End Class
    Class TestPool
        Private Shared i As Integer = 0
        Private j As Integer = 0
        Public Sub Run()
            i += 1
            j = i
            Console.WriteLine("Value of i in run is {0} ", j)
        End Sub
        Public Shared Sub Main(args() As String)
            Dim tp = New GenThreadPool.GenThreadPoolImpl(1000, 1000, 300, True)
            Dim i As Integer
            For i = 0 To 99 '添加作业到线程池管理器
                Dim td1 As New TestPool
                Dim t1 As New Thread(AddressOf td1.Run)
                Dim td2 As New TestPool
                Dim t2 As New Thread(AddressOf td2.Run)
                Dim td3 As New TestPool
                Dim t3 As New Thread(AddressOf td3.Run)
                Dim td4 As New TestPool
                Dim t4 As New Thread(AddressOf td4.Run)
                Dim td5 As New TestPool
                Dim t5 As New Thread(AddressOf td5.Run)
                Dim td6 As New TestPool
                Dim t6 As New Thread(AddressOf td6.Run)
                Dim td7 As New TestPool
                Dim t7 As New Thread(AddressOf td7.Run)
                Dim td8 As New TestPool
                Dim t8 As New Thread(AddressOf td8.Run)
                Dim td9 As New TestPool
                Dim t9 As New Thread(AddressOf td9.Run)
                Dim td10 As New TestPool
                Dim t10 As New Thread(AddressOf td10.Run)
                Dim td11 As New TestPool
                Dim t11 As New Thread(AddressOf td11.Run)
                tp.AddJob(t1)
                tp.AddJob(t2)
                tp.AddJob(t3)
                tp.AddJob(t4)
                tp.AddJob(t5)
                tp.AddJob(t6)
                tp.AddJob(t7)
                tp.AddJob(t8)
                tp.AddJob(t9)
                tp.AddJob(t10)
                tp.AddJob(t11)
            Next
            Dim td12 As New TestPool
            Dim t12 As New Thread(AddressOf td12.Run)
            tp.AddJob(t12)
            Dim p As New TestPerformance(tp, 1000)
        End Sub
    End Class
End Namespace

结果较长,同时播放视频时,就可明显感觉到视频有点卡顿,说明CPU有点忙不过来了:)

时间: 2024-11-05 18:53:22

VB.net学习笔记(三十)认识线程池的相关文章

Java多线程高并发学习笔记(三)——深入理解线程池

线程池最核心的一个类:ThreadPoolExecutor. 看一下该类的构造器: public ThreadPoolExecutor(int paramInt1, int paramInt2, long paramLong, TimeUnit paramTimeUnit, BlockingQueue<Runnable> paramBlockingQueue) { this(paramInt1, paramInt2, paramLong, paramTimeUnit, paramBlockin

JAVA学习笔记(三十九)- 线程优先级

线程优先级 /* * 线程优先级,范围[1,10] * 不同优先级的线程获取执行的机会不同,优先级越高,执行机会越大 * * 对比不同优先级的线程被执行的机率 */ public class Test05 { public static void main(String[] args) { System.out.println("最大优先级:" + Thread.MAX_PRIORITY); System.out.println("最小优先级:" + Thread.

马哥学习笔记三十二——计算机及操作系统原理

缓存方式: 直接映射 N路关联 缓存策略: write through:通写 write back:回写 进程类别: 交互式进程(IO密集型) 批处理进程(CPU密集型) 实时进程(Real-time) CPU: 时间片长,优先级低IO:时间片短,优先级高 Linux优先级:priority 实时优先级: 1-99,数字越小,优先级越低 静态优先级:100-139,数据越小,优先级越高 实时优先级比静态优先级高 nice值:调整静态优先级   -20,19:100,139   0:120 ps

马哥学习笔记三十——tomcat

Java体系结构包含四个独立却又彼此相关的技术: Java程序设计语言 Java API Java Class文件格式 JVM: Java Virtual Machine JVM的实现方式: 1.一次性解释器,解释字节码并执行: 2.即时编译器(just-in-time complier) 依赖于更多内存缓存解释后的结果 3.自适应编译器 缓存20%左右代码,提高80%左右的速度: 运行时数据区: 线程私有内存区: 程序计数器 java虚拟机栈 线程共享内存区: 方法区 堆:java自动内存回收

【Unity 3D】学习笔记三十四:游戏元素——常用编辑器组件

常用编辑器组件 unity的特色之一就是编辑器可视化,很多常用的功能都可以在编辑器中完成.常用的编辑器可分为两种:原有组件和拓展组件.原有组件是编辑器原生的一些功能,拓展组件是编辑器智商通过脚本拓展的新功能. 摄像机 摄像机是unity最为核心组件之一,游戏界面中显示的一切内容都得需要摄像机来照射才能显示.摄像机组件的参数如下: clear flags:背景显示内容,默认的是skybox.前提是必须在render settings 中设置天空盒子材质. background:背景显示颜色,如果没

【Unity 3D】学习笔记三十二:游戏元素——游戏光源

游戏光源 在3D游戏中,光源是一个非常具有特色的游戏组件.用来提升游戏画面质感的.如果没有加入光源,游戏场景可能就会显得很昏暗.在unity中提供了三种不同的光源类型:点光源,聚光灯,平行光. 点光源 顾名思义,点光源是从一个点向周围散发出光的光源,就像电灯一样.创建点光源在hierarchy视图中点击create--point light: 创建完以后,点击点光源对象,在右侧inspector视图中可以看到点光源的所有信息: type:光源的类型.有point(点光源),directional

angular学习笔记(三十)-指令(5)-link

这篇主要介绍angular指令中的link属性: link:function(scope,iEle,iAttrs,ctrl,linker){ .... } link属性值为一个函数,这个函数有五个参数:scope,iEle,iAttrs,ctrl,linker scope:指令所在的作用域,这个scope和指令定义的scope是一致的.至于指令的scope,会在讲解scope属性的时候详细解释 iEle:指令元素的jqLite封装.(也就是说iEle可以调用angular封装的简版jq的方法和属

angular学习笔记(三十)-指令(6)-transclude()方法(又称linker()方法)-模拟ng-repeat指令

在angular学习笔记(三十)-指令(4)-transclude文章的末尾提到了,如果在指令中需要反复使用被嵌套的那一坨,需要使用transclude()方法. 在angular学习笔记(三十)-指令(5)-link文章也提到了link函数的第五个参数linker. 这篇文章就来讲解一下transclude()方法(linker()方法),是怎么使用的,另外,它也是compile函数的第三个参数,用法一样. 下面就通过自己写一个简易的模拟ngRepeat的指令cbRepeat,来了解linke

angular学习笔记(三十)-指令(7)-compile和link(2)

继续上一篇:angular学习笔记(三十)-指令(7)-compile和link(1) 上一篇讲了compile函数的基本概念,接下来详细讲解compile和link的执行顺序. 看一段三个指令嵌套的代码: html: <body> <div ng-controller="compileCtrl"> <level-one> <level-two> <level-three> hello,{{name}} </level-

angular学习笔记(三十)-指令(2)

本篇主要讲解指令中的 restrict属性, replace属性, template属性 这三个属性 一. restrict: 字符串.定义指令在视图中的使用方式,一共有四种使用方式: 1. 元素: E 2. 属性: A 3. 样式类: C 4. 注释: M restrict的值可以是上面四个字母的任意一个或多个的组合. 不指定的话默认为A. 二. replace: 布尔值.是否将指令元素替换,可以有两个值: 1.true: 替换整个使用指令的元素 2.false: 不替换整个使用指令的元素,而