-
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
例:理解线程池中各线程执行次序。
-
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"
-
栏目列表最新更新
- python爬虫及其可视化
- 使用python爬取豆瓣电影短评评论内容
- nodejs爬虫
- Python正则表达式完全指南
- 爬取豆瓣Top250图书数据
- shp 地图文件批量添加字段
- 爬虫小试牛刀(爬取学校通知公告)
- 【python基础】函数-初识函数
- 【python基础】函数-返回值
- HTTP请求:requests模块基础使用必知必会
- SQL SERVER中递归
- 2个场景实例讲解GaussDB(DWS)基表统计信息估
- 常用的 SQL Server 关键字及其含义
- 动手分析SQL Server中的事务中使用的锁
- openGauss内核分析:SQL by pass & 经典执行
- 一招教你如何高效批量导入与更新数据
- 天天写SQL,这些神奇的特性你知道吗?
- openGauss内核分析:执行计划生成
- [IM002]Navicat ODBC驱动器管理器 未发现数据
- 初入Sql Server 之 存储过程的简单使用
- uniapp/H5 获取手机桌面壁纸 (静态壁纸)
- [前端] DNS解析与优化
- 为什么在js中需要添加addEventListener()?
- JS模块化系统
- js通过Object.defineProperty() 定义和控制对象
- 这是目前我见过最好的跨域解决方案!
- 减少回流与重绘
- 减少回流与重绘
- 如何使用KrpanoToolJS在浏览器切图
- performance.now() 与 Date.now() 对比