C#.Net使用线程池(ThreadPool)与专用线程(Thread)

 


线程池(ThreadPool)使用起来很简单,但它有一些限制: 

1. 线程池中所有线程都是后台线程,如果进程的所有前台线程都结束了,所有的后台线程就会停止。不能把入池的线程改为前台线 程。 

2. 不能给入池的线程设置优先级或名称。 

3. 对于COM对象,入池的所有线程都是多线程单元(Multi-threaded apartment,MTA)线程。许多COM对象都需要单线程单元(Single -threaded apartment,STA)线程。 

4.入池的线程只能用于时间较短的任务。如果线程要一直运行(如Word的拼写检查器线程),就应使用Thread类创建一个线程。

 




高效线程使用圣典

 

 


  严格来讲,线程的系统开销很大。系统必须为线程分配并初始化一个线程内核对象,还必须为每个线程保留1mb的地址空间 (按需提交)用于线程的用户模式堆栈,分配12kb左右的地址空间用于线程的内核模式堆栈。然后,紧接着线程创建后,windows调 用进程中每个dll都有的一个函数来通知进程中所有的dll操作系统创建了一个新的线程。同样,销毁一个线程的开销也不小:进程 中的每个dll都要接收一个关于线程即将“死亡”的通知,而且内核对象及堆栈还需释放。


  如果一台计算机中只有一个cpu,那么在某一时刻只有一个线程可以运行。windows必须跟踪记录线程对象,而且是不停地跟 踪记录每个线程对象。windows不得不决定cpu下次调度哪个线程来执行。这个额外的代码不得不每隔20ms左右执行一次。windows使 cpu停止执行一个线程的代码,而开始执行另一个线程的代码的现象,我们称之为上下文切换(context switch)。上下文切换的开 销相当大,因为操作系统必须执行以下步骤:


  1. 进入内核模式。


  2. 将cpu的寄存器保存到当前正在执行的线程的内核对象中。x86架构的机器上cpu寄存器占了大约700字节的空间;x64架构 的机器上cpu寄存器占了大约1240字节的空间;而在ia64架构的机器上cpu寄存器占了大约2500字节的空间。


  3. 需要一个自旋锁(spin lock),确定下一次调度哪个线程,然后再释放该自旋锁。如果下一次调度的线程属于另一个进 程,那么此处的开销会更大,因为操作系统必切换到虚拟地址空间。


  4. 将即将运行的线程的内核对象的值加载到cpu寄存器中。


  5. 退出内核模式。


  所有上述内容都是纯粹的开销,导致windows操作系统和应用程序的执行速度比在单线程系统上的执行速度慢。


  综合上述所有结果可得出以下结论:应尽可能地限制线程的使用。如果创建的线程越多,给操作系统带来的开销就越大,所 有的东西也就运行得越慢。另外,每个线程都需要资源(内核对象占用的内存及两个堆栈),所以每个线程都会消耗内存。


  线程还有另一个用途:可扩展性。当计算机有多个cpu时,windows能同时调度多个线程:每个cpu运行一个线程。


CLR线程池简介


  如前所述,创建并销毁一个线程在时间上的开销相当大。另外,线程多还会浪费内存资源,而且由于操作系统不得不在可运 行线程间进行调度和上下文切换,从而影响操作系统和应用程序的性能。为改进这种现象,clr中包含管理clr线程池的代码。我们 可以将线程池看作应用程序自己使用的线程的集合。每个进程都有一个线程池,这个线程池被该进程中的所有应用程序域共享。


  当clr初始化时,线程池中还没有任何线程。从内部实现上讲,线程池维护了一系列操作请求。应用程序希望执行一个异步 操作时,可以调用一些方法在线程池的队列中加入一个条目。线程池中的代码将从这个队列中提取出条目,并将该条目分派到线程 池中的线程。如果线程池中没有任何线程,就创建一个新的线程。创建一个线程会有相关的性能损失。但是,当线程池中的线程完 成任务时,并不会被销毁,而是返回到线程池中,在线程池中空闲,等待响应另外的请求。因为线程不对它自身进行销毁,所以此 处不会带来性能损失。


  如果应用程序对线程池进行了很多的请求,那么线程池将试图只用一个线程来响应所有的请求。但是,如果应用程序排队的 请求超出了线程池的处理能力,线程池中将创建另外的线程。最终,应用程序排队的请求与线程池中线程的处理能力达到一个平衡 点,我们可以采用较小数量的线程来处理所有的请求,因此线程池中也就不再需要创建更多的线程。


  如果应用程序停止请求线程池,线程池中可能会有许多不做事情的线程。这种情况会浪费内存资源。因此,当线程池中的线 程空闲超过大约2分钟后,线程将唤醒自己,并终止自己,以释放内存资源。当线程终止自己时,也会存在一个性能损失。但是,该 性能损失不是很严重,因为线程在终止自己时,线程已处于空闲状态,这意味着我们的应用程序当前没有执行太多的工作。


  从内部实现上讲,线程池将线程池中的线程进行分类,划分为工作线程(worker thread)和i/o线程(i/o thread)。当应 用程序请求线程池执行一个受计算限制的异步操作(包括初始化受i/o限制的异步操作)时使用工作线程,而i/o线程用于在受i/o限 制的异步操作完成时通知代码。具体而言,这意味着我们需要使用异步编程模型来进行i/o请求。


限制线程池中的线程数量


  clr的线程池允许开发人员设置工作线程和i/o线程的最大数量。clr保证创建的线程数量不会超过这个设置值。但永远不要 对线程池中线程的数量设置一个上限,因为饥饿和死锁现象可能会发生。在clr的2.0版默认中,工作线程的默认最大数量为机器中 每个cpu25个,i/o线程最大数量设为1000个。


  system.threading.threadpool类提供了几个操作线程池中线程数量的静态方法:getmaxthreads(查询线程池对线程数量的 最大限制)、setmax-threads(设置线程数量最大限制)、getminthreads(查询线程池对线程数量的最小限制)、setminthreads (设置线程数量最小限制)、getavailable-threads。


  强烈建议不要调用setmaxthreads方法修改线程池中线程数量的限制,因为这会导致损害应用程序的执行性能。


  clr的线程池试图避免过快地创建额外的线程。具体而言,线程池试图避免每隔500ms就创建一个新的线程。这对某些开发人 员而言,引发了一个问题,因为队列中的任务无法得到及时地处理。要处理此问题,可以调用setminthreads方法设置线程池中拥有 线程的最低数量。调用该方法后,线程池将很快地创建这么多的线程,并且当队列的任务继续增加,所创建的所有线程都被使用后 ,线程池还会按照每隔500ms的时间继续创建额外的线程。默认情况下,线程池中工作线程和i/o线程的最小数量被设为2,这个值可 以通过调用getminthreads方法获得。


  最后,可以通过调用getavailablethreads方法来获得线程池中可以增加的额外线程的数量。该方法的返回值为线程池中可 以拥有的线程的最大数量减去线程池中当前所拥有的线程数量。这个值仅在返回的那一刻有用,因为在方法返回后,线程池中可能 已经增加了许多线程,或有些线程可能已被销毁。


使用线程池执行受计算限制的异步操作


  受计算限制的操作是需要进行计算的操作。如,电子表格应用程序中可计算的单元。理想情况下,受计算限制的操作不会执 行任何异步i/o操作,因为所有的异步i/o操作在底层硬件执行工作时都将挂起调用线程。应该尽量使线程运行,因为挂起的线程不 再继续运行但仍然使用系统的资源。


  为了将一个受计算限制的异步操作加入到线程池的队列中,一般可以使用threadpool类中定义的下述方法:

 

 

static bool QueueUserWorkItem(WaitCallback callback);
static bool QueueUserWorkItem(WaitCallback callback, object state);
static bool UnsafeQueueUserWorkItem(WaitCallback callback, object state);


  上述方法将一个“工作项”(及可选的状态数据)加入到线程池的队列中,然后这些方法就会立即返回。工作项仅仅是一个 由callback参数标识的方法,线程池中的线程将调用该方法。该方法可以只传递一个单独的由state(状态数据)参数指定的参数。 没有state参数的QueueUserWorkItem方法为回调函数传递null。最终,线程池中的一些线程将执行工作项,从而导致我们的方法被 调用。我们写的回调方法必须匹配system.threading.WaitCallback委托类型,它的定义方式如下所示:

 

delegate void WaitCallback(object state);


  下面的代码演示了线程池中的线程如何异步调用一个方法:
 

 

using system;
using system.threading;

public static class program
{

public static void main()
{
console.writeline(
"main thread: queuing an asynchronous operation"); threadpool.QueueUserWorkItem(computeboundop, 5);
console.writeline(
"main thread: doing other work here ...");
thread.sleep(
10000); //模拟其他工作10秒钟
console.writeline("hit <enter> to end this program ...");
console.readline(); }


//该方法的签名必须与WaitCallback委托类型匹配
private static void computeboundop(object state)
{

//该方法由线程池中的线程执行
console.writeline("in computeboundop: state={0}", state);
thread.sleep(
1000); //模拟其他工作1秒钟
//在该方法返回后,线程就回到线程池中,然后等待执行另一个任务
}
}


  如果回调方法抛出的异常是未处理异常,那么clr将终止进程。


  threadpool类有一个UnsafeQueueUserWorkItem方法。该方法与平时调用的QueueUserWorkItem方法非常相似。下面先简单介 绍一下这两个方法的区别:试图访问一个受限资源(如打开一个文件)时,clr将执行一个代码访问安全(code access security, cas)检查。也就是说,clr将检查调用线程的调用堆栈中的所有程序集是否都有访问资源的许可权限。如果有一些程序集没有所需 的许可权限,clr将抛出一个securityexception异常。假设正在执行代码的线程所在的程序集没有打开文件的许可权限,那么在线 程试图打开文件时,clr将抛出一个securityexception异常。


  为让线程继续运行,线程可以在线程池的队列加入一个工作项,让线程池中的线程来执行打开文件的代码。当然这必须在拥 有合适许可权限的程序集中进行。这种“工作区”智取安全权限的现象可以允许怀恶意的代码对受限资源进行严重破坏。为阻止这 种获得安全权限的方式,QueueUserWorkItem方法内部遍历调用线程的堆栈,并捕获所有被授予的安全权限。然后,当线程池中的线 程开始执行时,这些权限再与线程结合。因此,线程池中的线程以调用QueueUserWorkItem方法的线程相同的权限集来完成运行。


  遍历线程的堆栈并捕获所有的安全权限与性能紧密相关。如果希望改进受计算限制的异步操作的排队性能,可以调用 UnsafeQueueUserWorkItem方法。该方法只将工作项加入到线程池的队列中,而不遍历调用线程的堆栈。最后结果是这个方法比 QueueUserWorkItem方法执行得快,但它在应用程序中打开了一个潜在的安全漏洞。仅当可以确认线程池中的线程执行的代码不触及 受限资源时,或确信接触这部分资源不会出现问题时,我们才可以调用unsafequeueuserwork-item方法。同样,还需注意调用该方 法需要使securitypermission的controlpolicy标记和controlevidence标记开启,可阻止未信任的代码偶然或故意提升它的许可权 限。


使用专用线程执行受计算限制的异步操作


  强烈建议大家尽量多用线程池来执行受计算限制的异步操作。但在有些情况下,我们可能希望显式创建一个线程,专门用于 执行特定的受计算限制的异步操作。一般情况下,如果即将执行的代码需要线程处于一个特定的状态(与线程池中线程的普通状态 不同),那么就希望创建一个专用的线程。如:希望线程以一个特殊的优先级运行(所有线程池中的线程都以普通优先级运行,而 且我们不应该修改线程池中线程的优先级),就需要创建一个专用的线程。再如:希望让一个线程成为前台线程(所有线程中的线 程都是后台线程),也可以考虑创建并使用自己的线程,从而阻止应用程序的“死亡”,直到线程完成任务。如果受计算限制的任 务运行的时间特别长,也应该使用专用线程,这样,我们就不必让线程池的逻辑去费力判断是否还需创建额外的线程。最后,如果 我们希望启动一个线程,然后通过调用thread的abort方法中断该线程的话,应该使用一个专用线程。


  为创建一个专用线程,我们可构建一个system.threading.thread类的实例(以方法的名称作为构造器的参数)。下面是构 造器的原型:
 

 

public sealed class thread : criticalfinalizerobject, ...
{

public thread(parameterizedthreadstart start);
}


  参数start用来标识专用线程的方法即将执行,这个方法必须与委托parameterizedthreadstart的签名相匹配:
 

 

delegate void parameterizedthreadstart(object obj);


  可看出,parameterizedthreadstart委托的签名与WaitCallback委托的签名相同。这意味着使用一个线程池中的线程或使用 一个专用线程就可以调用相同的方法。


  构建一个thread对象并不创建一个操作系统线程。为实际创建一个操作系统线程,并让它开始执行回调方法,我们必须调用 thread的start方法。如下所示:
 

 

using system;
using system.threading;

public static class program
{

public static void main()
{
console.writeline(
"main thread: starting a dedicated thread " + " to do an asynchronous operation");
thread dedicatedthread
= new thread(computeboundop);
dedicatedthread.start(
5); console.writeline("main thread: doing other work here...");
thread.sleep(
10000); //模拟其他工作10秒 dedicatedthread.join(); //等待线程终止 console.writeline("hit <enter> to end this program...");
console.readline();
}







//该方法的签名必须与parameterizedthreadstart委托匹配
private static void computeboundop(object state)
{

//该方法由一个专用线程执行
console.writeline("in computeboundop: state = {0}", state);
thread.sleep(
1000); //模拟其他工作1秒
}
}


  注意,main方法调用了join方法,而join方法导致调用线程停止执行任何代码,直到由dedicatedthread标识的线程自己销 毁自己或被终止。使用threadpool的QueueUserWorkItem方法将异步操作排队时,clr没有提供内置的方法来判断操作是否完成。而 join方法却在我们使用专用线程时为我们提供了这种能力。但是,如果需要知道操作是在什么时候完成的,就不应该使用专用线程 来取代QueueUserWorkItem方法,而应该使用apm。



转载文章,C/S框架网责任编辑

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/439424.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python学习】 - sklearn学习 - 评估指标precision_score的参数说明

函数声明&#xff1a; precision_score(y_true, y_pred, labelsNone, pos_label1, averagebinary, sample_weightNone) 其中较为常用的参数解释如下&#xff1a; y_true&#xff1a;真实标签 y_pred&#xff1a;预测标签 average&#xff1a;评价值的平均值的计算方式。可…

ANSI X9.19 MAC算法介绍

(1) ANSI X9.19MAC算法只使用双倍长密钥&#xff0c;也就是16字节密钥&#xff1b; (2) MAC数据按8字节分组&#xff0c;表示为D0&#xff5e;Dn&#xff0c;如果Dn不足8字节时&#xff0c;尾部以字节00补齐&#xff1b; (3) 用MA…

GitHub.com上的那些东西你都知道什么意思吗?

GitHub初学入门者的图谱&#xff0c;介绍Github网站每个功能的意思 一、键盘快捷键 在GitHub中&#xff0c;很多页面都可以使用键盘快捷键。在各个页面按下“shift /”都可以打开键盘快捷键一览表&#xff0c;如下图&#xff1a; 快捷键 二、工具栏 工具栏 LOGO 点击GitHub…

【Python学习】 - sklearn学习 - 数据集分割方法 - 随机划分与K折交叉划分与StratifiedKFold与StratifiedShuffleSplit

一、随机划分 import numpy as np from sklearn import datasetsiris datasets.load_iris() X iris.data y iris.target# 1&#xff09;归一化前&#xff0c;将原始数据分割 from sklearn.model_selection import train_test_split X_train,X_test,y_train,y_test train_t…

CURLE_WRITE_ERROR

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp) { return 0; //return size * nmemb; } curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); res curl_easy_perform(curl); 如果应…

Azure 应用服务、虚拟机、Service Fabric 和云服务的比较

Azure 提供了几种托管网站的方式&#xff1a;Azure 应用服务、虚拟机、Service Fabric 和云服务。 本文可帮助你了解这几种方式&#xff0c;并针对 Web 应用程序做出正确的选择。 Azure 应用服务是大多数 Web 应用的最佳选择。 部署和管理都已集成到平台&#xff0c;站点可以快…

【Python学习】 - sklearn - PCA降维相关

1、PCA算法介绍 主成分分析&#xff08;Principal Components Analysis&#xff09;&#xff0c;简称PCA&#xff0c;是一种数据降维技术&#xff0c;用于数据预处理。一般我们获取的原始数据维度都很高&#xff0c;比如1000个特征&#xff0c;在这1000个特征中可能包含了很多…

【Python学习】 - sklearn - 用于生成数据的make_blobs模块

函数原型&#xff1a; sklearn.datasets.make_blobs(n_samples100, n_features2, centers3, cluster_std1.0, center_box(-10.0, 10.0), shuffleTrue, random_stateNone) 参数含义&#xff1a; n_samples: int, optional (default100) The total number of points equally di…

微服务架构及幂等性

微服务架构 微服务架构是一种架构概念&#xff0c;旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。它的主要作用是将功能分解到离散的各个服务当中&#xff0c;从而降低系统的耦合性&#xff0c;并提供更加灵活的服务支持。 和 微服务 相对应的&#xff0c;这…

【Python学习】 - Matplotlib二维绘图 - plt.matshow()和plt.imshow()区别对比

给定一个8*8的数据&#xff0c;用两种方式分别进行输出。 xx np.zeros((8,8),dtype np.uint8) xx[0,0] 13im Image.fromarray(xx) plt.imshow(im)plt.matshow(xx) plt.show() 输出&#xff1a; 得出结论&#xff1a; 首先我不知道为啥两个窗口是不一样大的。 其次发现图…

表达式主体定义

原文地址https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members 通过表达式主体定义&#xff0c;可采用非常简洁的可读形式提供成员的实现。 只要任何支持的成员&#xff08;如方法或属性&#xff0…

【机器学习】 - 数据预处理之数据归一化(标准化)与实战分析,正则化

一、为什么要进行数据归一化 定义&#xff1a;把所有数据的特征都归到 [0,1] 之间 或 均值0方差1 的过程。原则&#xff1a;样本的所有特征&#xff0c;在特征空间中&#xff0c;对样本的距离产生的影响是同级的&#xff1b;问题&#xff1a;特征数字化后&#xff0c;由于取值…

Java中Lambda表达式与方法引用和构造器引用

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 本文链接&#xff1a;https://blog.csdn.net/cjhc666/article/details/54948909 方法引用&#xff1a; 首先看 Timer t new Timer(1000, System.…

【基于Python】 - 人工智能机器学习深度学习数据分析 - 常见问题,常用的套路与操作(持续更新)

20200221&#xff1b; 1.做分类问题的时候&#xff0c;给定你标签&#xff0c;你想知道每一类标签的出现频数&#xff0c;可以使用这个函数&#xff1a;np.bincount()。 如果想分析一下数据样本是否均衡的时候&#xff0c;可以考虑这种操作&#xff0c;代码十分简明。 2. 当…

Entity Framework 简介

转贴&#xff1a;链接https://www.cnblogs.com/davidzhou/p/5348637.html 侵删&#xff0c;谢谢 第一篇&#xff1a;Entity Framework 简介 先从ORM说起吧&#xff0c;很多年前&#xff0c;由于.NET的开源组件不像现在这样发达&#xff0c;更别说一个开源的ORM框架&#xff0…

关于C#中的“?”

1. 可空类型修饰符&#xff08;T?&#xff09; 强类型语言中引用类型可以为空&#xff0c;如&#xff1a; string strnull;而你若是定义一个值类型为空&#xff0c;则是会报错的。 然而&#xff0c;在处理数据库和其他包含可能未赋值的元素的数据类型时&#xff0c;我们希望…

【Python学习】 - pyecharts包 - 地图可视化

安装&#xff1a; https://pan.baidu.com/s/1vAlSjVbHt0EDJY6C_38oEA 提取码&#xff1a;t9be 在这个链接中下载对应的.whl文件&#xff0c;放到下图所示的目录中。 然后打开anaconda prompt 找到对应的目录&#xff0c;输入&#xff1a; pip install pyecharts-0.1.9.4-py…

【Python学习】 - anaconda中spyder的常用快捷键总结

熟练spyder中的一些快捷键后&#xff0c;能极大提升code效率。 这里列出常用的快捷键。&#xff08;可以在spyder导航栏Tools-Preferences-Keyboard shortcut中查看有所有的快捷键&#xff09; Tab/ShiftTab:代码缩进/撤销代码缩进 Ctrl1:注释/撤销注释 Ctrl4/5:块注释/撤销块…

【机器学习】 - 关于图像质量评价IQA(Image Quality Assessment)

图像质量评价&#xff08;Image Quality Assessment,IQA&#xff09;是图像处理中的基本技术之一&#xff0c;主要通过对图像进行特性分析研究&#xff0c;然后评估出图像优劣&#xff08;图像失真程度&#xff09;。 主要的目的是使用合适的评价指标&#xff0c;使得评价结果…

【机器学习】 - CNN

什么是卷积神经网络&#xff0c;它为何重要&#xff1f; 卷积神经网络&#xff08;也称作 ConvNets 或 CNN&#xff09;是神经网络的一种&#xff0c;它在图像识别和分类等领域已被证明非常有效。 卷积神经网络除了为机器人和自动驾驶汽车的视觉助力之外&#xff0c;还可以成功…