C多线程技术实用教案

上传人:壹****1 文档编号:569413714 上传时间:2024-07-29 格式:PPT 页数:24 大小:525.50KB
返回 下载 相关 举报
C多线程技术实用教案_第1页
第1页 / 共24页
C多线程技术实用教案_第2页
第2页 / 共24页
C多线程技术实用教案_第3页
第3页 / 共24页
C多线程技术实用教案_第4页
第4页 / 共24页
C多线程技术实用教案_第5页
第5页 / 共24页
点击查看更多>>
资源描述

《C多线程技术实用教案》由会员分享,可在线阅读,更多相关《C多线程技术实用教案(24页珍藏版)》请在金锄头文库上搜索。

1、9.2.NET对多线程的支持(zhch)在.NET程序设计中,线程是使用Thread类来处理的,该类在命名空间中。一个Thread实例管理一个线程,即执行序列。通过简单实例化一个Thread对象,就可以创建一个线程,然后通过Thread对象提供的方法对线程进行管理。9.2.1线程的建立与启动假定我们需要编写一个文件压缩软件,用户点击压缩按钮后开始压缩指定的文件。因为整个压缩过程需要一定的时间才能完成,而用户此时还可能需要移动或缩放程序的窗口,甚至暂停或中止当前文件的压缩。此时一般需要创建一个单独的线程来处理这个压缩过程使得在压缩过程中可以不中断用户界面的响应。因此,我们需要实例化一个Threa

2、d对象来创建这个线程:/假设DoCompress是前面已经声明了的一个ThreadStart委托(witu)ThreadcompressThread=NewThread(entryPoint);这段代码指定线程对象的实例名为compressThread。在一个应用程序中创建另一个线程,执行一些任务,通常称为工作线程(workerthread),这里compressThread就是一个工作线程,而Main()方法所在的线程常被称为主线程。第1页/共23页第一页,共24页。9.2.1线程的建立(jinl)与启动从代码可以看出,Thread构造函数需要一个参数,用于指定线程的入口即线程开始执行的方法

3、,因为我们传送的是方法的详细信息,所以需要使用委托。实际上,该委托已经在命名空间中定义好了。它称为ThreadStart,其声明如下所示:publicdelegatevoidThreadStart();传送给构造函数的参数必须是这种类型的委托。上面的例子中是entryPoint,我们来看如何定义这个委托:/实际线程执行的方法staticvoidDoCompress()/压缩代码ThreadStartentryPoint=newThreadStart(DoCompress);线程对象建立完成后,新线程实际上并没有执行任务,它只是在等待(dngdi)执行。我们需要显式地调用Thread对象的Sta

4、rt()方法来启动线程:();此外还可以使用Thread对象的Name属性给线程赋予一个友好的名称。第2页/共23页第二页,共24页。9.2.2线程的挂起、恢复(huf)与终止启动了一个线程后,线程将运行到所在的方法结束为止,在此期间还可以挂起、恢复或中止它。挂起一个线程就是让它进入睡眠状态,此时,线程仅是停止运行某段时间,不占用任何处理器时间,以后还可以恢复,从被挂起的那个状态重新运行。如果线程被中止,就是停止运行,Windows会永久(yngji)地删除该线程的所有数据,所以该线程不能重新启动。继续上面的文件压缩例子,假定由于某些原因,用户界面线程显示一个对话框,允许用户选择临时暂停压缩过

5、程。在主线程中编写如下响应:();如果用户以后要求恢复该线程,可以使用下面的方法:()最后,如果用户决定不需要继续压缩的话,单击取消按钮,可以使用下面的方法:()第3页/共23页第三页,共24页。9.4线程的优先级如果在应用程序中有多个线程在运行,但一些线程比另一些线程重要因而(ynr)需要分配更多的CPU时间该怎么办?在这种情况下,可以在一个进程中为不同的线程指定不同的优先级。一般情况下,如果有优先级较高的线程在工作,就不会给优先级较低的线程分配任何时间片,其优点是可以保证给接收用户输入的线程指定较高的优先级。在大多数的时间内,这个线程什么也不做,而其他线程则执行它们的任务。但是,如果用户输

6、入了信息,这个线程就立即获得比应用程序中其他线程更高的优先级,在短时间内处理用户输入控件。线程的优先级定义为ThreadPriority枚举类型,取值如表所示:表9.1 线程的优先级及其含义(hny)第4页/共23页第四页,共24页。9.4线程的优先级高优先级的线程可以完全阻止低优先级的线程执行(zhxng),因此在改变线程的优先级时要特别小心以免造成某些线程得不到CPU时间。此外,每个进程都有个基本优先级,这些值与进程的优先级是有关系的。给线程指定较高的优先级,可以确保它在该进程内比其他线程优先执行(zhxng),但系统上可能还运行着其他进程,它们的线程有更高的优先级。如Windows给自己

7、的操作系统线程指定高优先级。在【例9.1】中,对Main()方法做如下修改,就可以看出修改线程的优先级的效果:/建立新线程对象ThreadStartworkerStart=newThreadStart(DisplayNumbers);ThreadworkerThread=newThread(workerStart);=WorkerThread;=AboveNormal;第5页/共23页第五页,共24页。9.4线程的优先级其中通过(tnggu)代码设置工作线程的优先级比主线程高,运行结果如下所示:请输入一个数字:1000000线程:MainThread已开始运行.MainThread:当前计数为

8、1000000MainThread:当前计数为2000000MainThread:当前计数为3000000MainThread:当前计数为4000000MainThread:当前计数为5000000MainThread:当前计数为6000000线程:WorkerThread已开始运行.WorkerThread:当前计数为1000000WorkerThread:当前计数为2000000WorkerThread:当前计数为3000000第6页/共23页第六页,共24页。9.4线程的优先级WorkerThread:当前计数为4000000WorkerThread:当前计数为5000000Worker

9、Thread:当前计数为6000000WorkerThread:当前计数为7000000WorkerThread:当前计数为8000000线程WorkerThread完成.MainThread:当前计数为7000000MainThread:当前计数为8000000线程MainThread完成.这说明,当工作线程的优先级为AboveNormal时,一旦工作线程被启动,主线程就不再运行,直到工作线程结束后主线程才重新计算。让我们继续试验(shyn)操作系统如何对线程分配CPU时间:第7页/共23页第七页,共24页。9.4线程的优先级在DisplayNumbers()方法(fngf)的循环体中加上一

10、句代码,:if(i%interval=0)+:当前计数为+i);Thread.Sleep(10);/让当前工作线程暂停10毫秒现在来看运行结果:请输入一个数字:1000000线程:MainThread已开始运行.MainThread:当前计数为1000000线程:WorkerThread已开始运行.WorkerThread:当前计数为1000000MainThread:当前计数为2000000MainThread:当前计数为3000000WorkerThread:当前计数为2000000MainThread:当前计数为4000000WorkerThread:当前计数为3000000Worker

11、Thread:当前计数为4000000第8页/共23页第八页,共24页。9.4线程的优先级MainThread:当前计数为5000000WorkerThread:当前计数为5000000WorkerThread:当前计数为6000000MainThread:当前计数为6000000WorkerThread:当前计数为7000000WorkerThread:当前计数为8000000线程WorkerThread完成.MainThread:当前计数为7000000MainThread:当前计数为8000000线程MainThread完成.此时的结果与前面有很大的不同,虽然(surn)工作线程仍然早于

12、主线程完成,但是在工作线程的计算过程中,主线程也获到了CPU时间。这是因为在DisplayNumbers()方法中使用的Thread静态方法Sleep()放弃了CPU时间,即使当前线程具有较高的优先级,操作系统也会把时间片分配给其他优先级低的线程。如果我们把Sleep()的参数加到100毫秒,运行结果又会有很大的不同,甚至可能两个线程是几乎并行完成的。第9页/共23页第九页,共24页。9.5线程同步(tngb)使用线程的一个重要方面是同步访问多个线程访问的任何变量。所谓同步,是指在某一时刻只有一个线程可以访问变量。如果不能确保对变量的访问是同步的,就可能会产生错误或不可预料的结果。一般情况下,

13、当一个线程写入一个变量,同时有其他线程读取或写入这个变量时,就应同步变量。本节将简要介绍同步的一些主要内容。9.5.1同步的含义(hny)同步问题的产生,主要是由于在高级语言的源代码中,大多数情况下看起来是一条语句,但在最后编译好的汇编语言机器码中则会被翻译为许多条语句,从而在操作系统调度时被划分到不同的时间片中。第10页/共23页第十页,共24页。9.5.1同步(tngb)的含义看看下面这个语句,假设message是一个string对象,已经保存(bocn)了一个字符串:message+=Helloworld!;这条语句在C#语法上是一条语句,但在执行代码时,实际上它涉及到许多操作。需要重新

14、分配内存以存储更长的新字符串,需要设置变量message使之指向新的内存,需要复制实际文本等。显然,这里选择了一种复杂字符串,但即使在基本数字类型上执行算术操作,后台进行的操作也比从C#代码中看到的要多。而且,许多操作不能直接在存储于内存空间中的变量上进行,它们的值必须单独复制到处理器的特定位置上,即寄存器。只要一个C#语句翻译为多个本机代码命令,线程的时间片就有可能在执行该语句的进程中终止,如果是这样,同一个进程中的另一个线程就会获得一个时间片,如果涉及到这条语句的变量访问(在上面的示例中,是message)不是同步的,那么另一个线程可能读写同一个变量。第11页/共23页第十一页,共24页。

15、9.5.2在C#中处理(chl)同步通过对指定对象的加锁和解锁可以同步代码段的访问。在.NET的命名空间(kngjin)中提供了Monitor类来实现加锁与解锁。这个类中的方法都是静态的,所以不需要实例化这个类。表中一些静态的方法提供了一种机制用来同步对象的访问从而避免死锁和维护数据的一致性。表9.2 Monitor类的主要(zhyo)方法第12页/共23页第十二页,共24页。9.5.2在C#中处理(chl)同步以下是使用Monitor类的简单例子:publicvoidsome_method()/获取锁Monitor.Enter(this);/处理需要同步的代码./释放锁Monitor.Exi

16、t(this);上面的代码运行可能会产生问题。当代码运行到获取锁与释放锁之间时一旦发生异常,将不会返回。这段程序将挂起,其他的线程也将得不到锁。解决方法是:将代码放入tryfinally内,在finally调用,这样的话最后(zuhu)一定会释放锁。第13页/共23页第十三页,共24页。9.5.2在C#中处理(chl)同步C#lock关键字提供了与和同样的功能,这种方法用在你的代码段不能被其他独立的线程中断的情况。通过对Monitor类的简易封装,lock为同步访问变量提供了一个非常简单的方式,其用法如下:lock(x)/使用x的语句lock语句把变量放在圆括号中,以包装对象,称为独占锁或排它

17、锁。当执行带有lock关键字的复合(fh)语句时,独占锁会保留下来。当变量被包装在独占锁中时,其他线程就不能访问该变量。如果在上面的代码中使用独占锁,在执行复合(fh)语句时,这个线程就会失去其时间片。如果下一个获得时间片的线程试图访问变量,就会被拒绝。Windows会让其他线程处于睡眠状态,直到解除了独占锁为止。第14页/共23页第十四页,共24页。9.5.2在C#中处理(chl)同步【例9.2】使用lock同步(tngb)线程。本示例建立了10个线程usingSystem;using;/银行帐户类classAccountintbalance;/余额Randomr=newRandom();p

18、ublicAccount(intinitial)balance=initial;/取钱intWithdraw(intamount)if(balance=amount)(原有余额:+balance);(支取金额:-+amount);balance=balance-amount;(现有余额:+balance);returnamount;elsereturn0;/拒绝( jju)交易第16页/共23页第十六页,共24页。【例9.2】/测试交易( jioy)publicvoidDoTransactions()/支取随机的金额100次for(inti=0;i100;i+)Withdraw(r.Next(

19、1,100);classTestApppublicstaticvoidMain()/建立10个线程同时进行交易( jioy)Threadthreads=newThread10;Accountacc=newAccount(1000);for(inti=0;i10;i+)第17页/共23页第十七页,共24页。【例9.2】Threadt=newThread(new);threadsi=t;for(inti=0;i10;i+)threadsi.Start();在这个示例中,10个线程同时进行交易,如果不加控制,很可能发生在支取金额时对balance字段的访问冲突。假设当前余额为100,有两个线程都要支

20、取60,则各自检查余额时都认为可以支取,造成(zochn)的后果则是总共被支取120,从而导致余额为负值。读者可以试着将lock语句注释掉再运行,此时将产生余额为负的异常。第18页/共23页第十八页,共24页。9.5.3同步时要注意(zhy)的问题同步线程在多线程应用程序中非常重要。但是,这是一个需要详细讨论的内容,因为很容易出现微妙且难以察觉的问题,特别是死锁。线程同步非常重要,但只在需要时使用也是非常重要的。因为这会降低性能。原因有两个:首先,在对象上放置和解开锁会带来某些系统开销,但这些系统开销都非常小。第二个原因更为重要,线程同步使用得越多,等待释放对象的线程就越多。如果一个线程在对象

21、上放置了一个锁,需要访问该对象的其他线程就只能暂停执行,直到该锁被解开,才能继续执行。因此,在lock块内部编写的代码越少越好,以免出现线程同步错误。lock语句(yj)在某种意义上就是临时禁用应用程序的多线程功能,也就临时删除了多线程的各种优势。另一方面,使用过多的同步线程的危险性(性能和响应降低)并没有在需要时不使用同步线程那么高(难以跟踪的运行时错误)。第19页/共23页第十九页,共24页。9.5.3同步(tngb)时要注意的问题死锁是一个(y)错误,在两个线程都需要访问被互锁的资源时发生。假定一个(y)线程运行下述代码,其中a和b是两个线程都可以访问的对象引用:lock(a)(lock

22、(b)/dosomething同时,另一个(y)线程运行下述代码:lock(b)(lock(a)/dosomething第20页/共23页第二十页,共24页。9.5.3同步时要注意(zhy)的问题根据线程遇到不同(btn)语句的时间,可能会出现下述情况:第一个线程在a上有一个锁,同时第二个线程在b上有一个锁。不久,线程A遇到lock(b)语句,立即进人睡眠状态,等待b上的锁被解开。之后,第二个线程遇到lock(a)语句,也立即进人睡眠状态,等待Windows在a上的锁被解开时唤醒它。但a上的锁永远不会解开,因为第一个线程拥有这个锁,目前正处于睡眠状态,在b上的锁被解开前是不会醒的,而在第二个线

23、程被叫醒之前,b上的锁不会解开,结果就是一个死锁。两个线程都不会做任何事,而仅是等待另一个线程解开它们的锁。这类问题会使整个应用程序挂起,不能执行任何操作,除非使用“任务管理器”中断整个进程(在这种情况下,另一个线程不可能解开锁:独占锁只能由定义它的线程解开)。让两个线程以相同的顺序在对象上声明加锁,就可以避免发生死锁:在上面的示例中,如果第二个线程声明加锁的顺序与第一个线程相同:a先b后,则无论哪个线程先在a上加锁,都会先完成它的任务后,才启动另一个线程。这样就不会发生死锁了。第21页/共23页第二十一页,共24页。9.5.3同步时要注意(zhy)的问题在上面的代码中发生死锁是非常明显的,在

24、编码中很容易避免,因为程序员肯定不会编写这样的代码,但记住不同的锁可以(ky)发生在不同的方法调用中。在这个示例中,第一个线程实际执行下述代码:lock(a)(CallSomeMethod();CallSomeMethod()可以(ky)调用其他方法,其中有一个lock(b)语句,此时是否会发生死锁就不那么明显了。事实上出现死锁的条件常常不明显,如果有这样的条件,也很难识别错误。一般情况下这需要一定的经验。但是在编写多线程应用程序时,如果需要同步,就必须考虑代码的所有部分,检查是否有可能发生死锁的条件。必须记住:不可能预见不同线程遇到不同语句的确切时间。第22页/共23页第二十二页,共24页。感谢您的观看(gunkn)!第23页/共23页第二十三页,共24页。内容(nirng)总结9.2 .NET对多线程的支持。假定我们需要编写一个文件压缩软件,用户点击压缩按钮后开始压缩指定的文件。这段程序将挂起,其他的线程也将得不到锁。当变量被包装在独占锁中时,其他线程就不能访问该变量。读者可以试着将lock语句注释掉再运行,此时将产生余额为。线程同步非常重要,但只在需要时使用也是非常重要的。必须记住:不可能预见(yjin)不同线程遇到不同语句的确切时间。第22页/共23页。感谢您的观看第二十四页,共24页。

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 研究生课件

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号