Thrad 目录: 目录: 1线程基础的简单介绍 2线程同步与线程异步的简单介绍 3前台线程与后台线程的简单介绍 4细说下Thrad最为关键的构造函数 5细说下Thrad的Slp方法 6细说下Thrad的join方法 7细说下Thrad的Abort和Intrrupt方法 8细说下Thrad的Suspnd,Rsum方法 9简单了解下Thrad的一些重要属性 10简单示例 多线程从一个图片中截取部分图片 11本章总结 1线程基础的简单介绍 首先让我们翻开书本来了解下线程的一些基础知识: 1线程有时被称为轻量级进程,是程序执行流的最小单元 2线程时由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。 3线程自身不能拥有系统资源,但是可以使用线程所属进程所占有的系统资源 4线程可以创建和撤销另一个线程 5线程可以拥有自身的状态,例如运行状态,挂起状态,销毁释放状态等等 6线程具有优先级,每个线程都分配了0-31级别的其中一个优先级,数字越大,优先级越高,然而手动分配优先级过于复杂, 所以微软为我们的Thrad类提供一个优先级的枚举,ThradPriority枚举便是优先级枚举,我们可以利用thrad.Priority属性来进行设置 7线程开销,这个是个复杂的话题,希望有机会的话能够单独写一遍文章解释下
那么多线程有什么实际好处呢? 首先让我们了解下多线程的概念:一个程序或者进程中同时运行多个线程完成不同的工作 从概念中我们便可知道多线程的优点了 1能够实现并行操作,也就是说多个线程可以同时进行工作 2利用多线程后许多复杂的业务或者是计算可以交给后台线程去完成,从而提高整体程序的性能 3类似于第一条利用多线程可以达到异步的作用(注意,实现异步的一种方式是多线程)当然多线程也有一定的问题需要注意,那就是线程同步问题,关于这个问题我会今后的文章中详细说明 2线程同步与线程异步的简单介绍 *1线程同步 关于线程同步的概念最简单的理解就是 同步方法调用在程序继续执行之前,需要等待同步方法执行完毕返回结果 很有可能多个线程都会对一个资源进行访问,从而导致资源被破坏,所以必须采用线程的同步机制,例如为共享资源加锁 ,当其中一个线程占有了锁之后,其余线程均不能使用共享资源,只有等其释放锁之后,接下来的其中一个线程会占有该 锁,本系列会从Thrad类开始讲起,以后多章都会讨论线程同步机制,例如锁机制,临界区,互斥,信号量同步事件等待句柄;等等 *2线程异步 线程异步指的是一个调用请求发送给被调用者,而调用者不用等待其结果的返回,一般异步执行的任务都需要比较长的时间, 所以为了不影响主线程的工作,可以使用多线程或者新开辟一个线程来实现异步,同样,异步和线程池也有着非常紧密的联系, 这点我会在今后有关线程池的文章中详细叙述,线程池和异步线程将在第二章中详细阐述下 3前台线程与后台线程的简单介绍 前台线程: 诸如我们Consol程序的主线程,wpf或者slivrlight的界面线程等等,都属于前台线程,一旦前台线程奔溃或者终止,相应的后台 线程都会终止,本章中通过Thrad类产生的线程默认都是前台线程,当然我们可以设置Thrad的属性让该对象成为后台线程,必须 注意的是,一旦前台线程全部运行完毕,应用程序的进程也会释放,但是假设Consol程序中main函数运行完毕,但是其中几个前台 线程还处在运行之中,那么这个Consol程序的进程是不会释放的,仍然处于运行之中,直到所有的前台线程都释放为止 后台线程: 和前台线程唯一的区别是,后台线程更加默默无闻,甚至后台线程因某种情况,释放销毁时不会影响到进程,也就是说后台线程释放时 不会导致进程的释放 用一个例子再来说明下前后台线程的区别: 有时我们打开outlook后接受邮件时,程序会失去响应或被卡住,这时候我们去点击outlook时系统会提示outlook失去响应,是否等待或者关闭, 当我们点击关闭时,其实在程序中关于outlook的所有运行的前台线程被终止,导致了outlook被关闭了,其进程也随之释放消失。但是,当我们在 outlook中点击更新邮件时,后台线程会去收取邮件的工作,我们可以在此期间关闭outlook接受新邮件的后台线程,而不会导致整个outlook的关闭 4细说下Thrad最为关键的构造函数 相信大家再看过前几章对于线程的介绍后,对线程应该有一个温故的感觉,那么让我们开始对thrad这个线程类进行深层次的研究下, 首先要启动一个线程必须将该线程将要做的任务告诉该线程,否则,线程会不知道干什么事导致线程无意义的开启,浪费系统资源,果然, Thrad类的构造函数提供了以下的版本 ThradStart和ParamtrThradStart参数都是委托,所以可以看出委托其实就是方法的抽象,前者用于不带参数的并且无返回值的 方法的抽象,后者是带objct参数的方法的抽象,大家通过以下简单的方法注意下线程如何调用带参数的方法 publicclassThradStartTst { //无参数的构造函数 Thradthrad=nwThrad(nwThradStart(ThradMthod)); //带有objct参数的构造函数 Thradthrad2=nwThrad(nwParamtrizdThradStart(ThradMthodWithPara)); publicThradStartTst() { //启动线程1 thrad.Start(); //启动线程2 thrad2.Start(nwParamtr{paraNam=Tst}); } staticvoidThradMthod() { //.... } staticvoidThradMthodWithPara(objcto) { if(oisParamtr) { //(oasParamtr).paraNam............. } } } publicclassParamtr { publicstringparaNam{gt;st;} } 不带参数的方法似乎很简单的能被调用,只要通过第一个构造函数便行,对于带参数的方法,大家注意下参数是如何传入线程所调用的方法, 当启动线程时,参数通过thrad.Start方法传入,于是我们便成功启动了thrad线程,大伙可千万不要小看基础啊,往往在复杂的项目中很多 就是因为一些基础导致,所以一定不要忽视它。。。 5细说下Thrad的Slp方法 话说微软对Thrad.Slp方法的解释过于简单,导致许多人会误认为这个方法并不重要,其实这是错误的,其实线程是非常复杂的, 而且我们围绕这个方法来温故下windows系统对于CPU竞争的策略: 所谓抢占式操作系统,就是说如果一个进程得到了CPU时间,除非它自己放弃使用CPU,否则将完全霸占CPU。因此可以看出, 在抢占式操作系统中,操作系统假设所有的进程都是“人品很好”的,会主动退出CPU。 发现写到这里貌似真的已经比较复杂了,由于本人对操作系统底层的知识比较匮乏,决定还是引用下别人的理解,顺便自己也学习下 引用: 假设有源源不断的蛋糕(源源不断的时间),一副刀叉(一个CPU),10个等待吃蛋糕的人(10个进程)。如果是Unix操作系统来负责分蛋糕, 那么他会这样定规矩:每个人上来吃1分钟,时间到了换下一个。最后一个人吃完了就再从头开始。于是,不管这10个人是不是优先级不同、饥饿 程度不同、饭量不同,每个人上来的时候都可以吃1分钟。当然,如果有人本来不太饿,或者饭量小,吃了30秒钟之后就吃饱了,那么他可以跟操 作系统说:我已经吃饱了(挂起)。于是操作系统就会让下一个人接着来。如果是Windows操作系统来负责分蛋糕的,那么场面就很有意思了。 他会这样定规矩:我会根据你们的优先级、饥饿程度去给你们每个人计算一个优先级。优先级最高的那个人,可以上来吃蛋糕——吃到你不想吃为止。 等这个人吃完了,我再重新根据优先级、饥饿程度来计算每个人的优先级,然后再分给优先级最高的那个人。这样看来,这个场面就有意思了—— 可能有些人是PPMM,因此具有高优先级,于是她就可以经常来吃蛋糕。可能另外一个人的优先级特别低,于是好半天了才轮到他一次(因为随着时间 的推移,他会越来越饥饿,因此算出来的总优先级就会越来越高,因此总有一天会轮到他的)。而且,如果一不小心让一个大胖子得到了刀叉,因为他 饭量大,可能他会霸占着蛋糕连续吃很久很久,导致旁边的人在那里咽口水。。。而且,还可能会有这种情况出现:操作系统现在计算出来的结果,是 5号PPMM总优先级最高——高出别人一大截。因此就叫5号来吃蛋糕。5号吃了一小会儿,觉得没那么饿了,于是说“我不吃了”(挂起)。因此操作系 统就会重新计算所有人的优先级。因为5号刚刚吃过,因此她的饥饿程度变小了,于是总优先级变小了;而其他人因为多等了一会儿,饥饿程度都变大了, 所以总优先级也变大了。不过这时候仍然有可能5号的优先级比别的都高,只不过现在只比其他的高一点点——但她仍然是总优先级最高的啊。因此操作 系统就会说:5号mm上来吃蛋糕……(5号mm心里郁闷,这不刚吃过嘛……人家要减肥……谁叫你长那么漂亮,获得了那么高的优先级)。那么, Thrad.Slp函数是干吗的呢?还用刚才的分蛋糕的场景来描述。上面的场景里面,5号MM在吃了一次蛋糕之后,觉得已经有8分饱了,她觉得在未来 的半个小时之内都不想再来吃蛋糕了,那么她就会跟操作系统说:在未来的半个小时之内不要再叫我上来吃蛋糕了。这样,操作系统在随后的半个小时 里面重新计算所有人总优先级的时候,就会忽略5号mm。Slp函数就是干这事的,他告诉操作系统“在未来的多少毫秒内我不参与CPU竞争”。 6细说下Thrad的join方法 为什么我要把Thrad.Join()方法单独细说下,个人认为join方法非常重要, 在细说前我想再次强调下主线程和子线程的区别: 首先大家肯定知道在Consol程序中,主线程自上而下着运行着main函数,假如我们在main函数中新增一个线程thrad对象的话, 也就是说,在主线程中再开启一个子线程,同时子线程和主线程可以同时工作(前提是子线程使用Start方法),同理,假如我在这 个子线程中再开辟一个属于这个子线程的子线程,同理这3个爷爷,父亲,儿子线程也可以使用Start()方法一起工作,假如在主线 程中添加2个thrad对象并开启,那么这2线程便属于同一层次的线程(兄弟线程)(和优先级无关,只同一位置层次上的兄弟), 有可能上述的让你觉得郁闷或者难以理解?没关系看简单例子就能够理解了 publicstaticvoidShowFathrAndSonThrad(ThradgrandFathrThrad) { Consol.WritLin(爷爷主线程名:{0},grandFathrThrad.Nam); ThradbrothrThrad=nwThrad(nwThradStart(()={Consol.WritLin(兄弟线程名:{0},Thrad.CurrntThrad.Nam);})); ThradfathrThrad=nwThrad(nwThradStart( ()= { Consol.WritLin(父亲线程名:{0},Thrad.CurrntThrad.Nam); ThradsonThrad=nwThrad(nwThradStart(()= { Consol.WritLin(儿子线程名:{0},Thrad.CurrntThrad.Nam); })); sonThrad.Nam=SonThrad; sonThrad.Start(); } )); fathrThrad.Nam=FathrThrad; brothrThrad.Nam=BrothrThrad; fathrThrad.Start(); brothrThrad.Start(); } 言归正传让我们温故下Jion方法,先看msdn中是怎么解释的: 继续执行标准的COM和SndMssag消息泵处理期间,阻塞调用线程,直到某个线程终止为止。 大家把注意力移到后面红色的部分,什么是“调用线程”呢?如果你理解上述线程关系的话,可能已经理解了,主线程(爷爷辈)的调用了父亲线程, 父亲线程调用了儿子线程,假设现在我们有一个奇怪的需求,必须开启爷爷辈和父亲辈的线程但是,爷爷辈线程必须等待父亲线程结束后再进行, 这该怎么办?这时候Join方法上场了,我们的目标是阻塞爷爷线程,那么后面的工作就明确了,让父亲线程(thrad)对象去调用join方法就行 一下是个很简单的例子,让大家再深入理解下。 publicstaticvoidThradJoin() { Consol.WritLin(我是爷爷辈线程,子线程马上要来工作了我得准备下让个位给他。); Thradt1=nwThrad( nwThradStart ( ()= { for(inti=0;i10;i++) { if(i==0) Consol.WritLin(我是父亲线层{0},完成计数任务后我会把工作权交换给主线程,Thrad.CurrntThrad.Nam); ls { Consol.WritLin(我是父亲线层{0},计数值:{1},Thrad.CurrntThrad.Nam,i); } Thrad.Slp(); } } ) ); t1.Nam=线程1; t1.Start(); //调用join后调用线程被阻塞 t1.Join(); Consol.WritLin(终于轮到爷爷辈主线程干活了); } 代码中当父亲线程启动后会立即进入Jion方法,这时候调用该线程爷爷辈线程被阻塞,直到父亲线程中的方法执行完毕为止,最后父亲线程将控制 权再次还给爷爷辈线程,输出最后的语句。聪明的你肯定会问:兄弟线程怎么保证先后顺序呢?很明显如果不使用join,一并开启兄弟线程后结果 是随机的不可预测的(暂时不考虑线程优先级),但是我们不能在兄弟线程全都开启后使用join,这样阻塞了父亲线程,而对兄弟线程是无效的, 其实我们可以变通一下,看以下一个很简单的例子: publicstaticvoidThradJoin2() { IListThradthrads=nwListThrad(); for(inti=0;i3;i++) { Thradt=nwThrad( nwThradStart( ()= { for(intj=0;j10;j++) { if(j==0) Consol.WritLin(我是线层{0},完成计数任务后我会把工作权交换给其他线程,Thrad.CurrntThrad.Nam); ls { Consol.WritLin(我是线层{0},计数值:{1},Thrad.CurrntThrad.Nam,j); } Thrad.Slp(); } })); t.Nam=线程+i; //将线程加入集合 thrads.Add(t); } forach(varthradinthrads) { thrad.Start(); //每次按次序阻塞调用次方法的线程 thrad.Join(); } } 输出结果: 但是这样我们即便能达到这种效果,也会发现其中存在着不少缺陷: 1:必须要指定顺序 2:一旦一个运行了很久,后续的线程会一直等待很久 3:很容易产生死锁 从前面2个例子能够看出jion是利用阻塞调用线程的方式进行工作,我们可以根据需求的需要而灵活改变线程的运行顺序,但是在复杂的项目或业务中 对于jion方法的调试和纠错也是比较困难的。 7细说下Thrad的Abort和Intrrupt方法 白癜风医院白癜风医院转载请注明原文网址:http://www.helimiaopu.com/cxtx/1200.html |