《第7章 多线程编程.ppt》由会员分享,可在线阅读,更多相关《第7章 多线程编程.ppt(17页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第第7章章 多线程编程多线程编程 2版权华华清清远见远见嵌入式培嵌入式培训训中心版中心版权权所有;所有;未未经华经华清清远见远见明确明确许许可,不能可,不能为为任何目的以任何形式复制任何目的以任何形式复制或或传传播此文档的任何部分;播此文档的任何部分;本文档包含的信息如有更改,恕不另行通知;本文档包含的信息如有更改,恕不另行通知;保留所有保留所有权权利。利。7.1 Windows CE的下的多任务体系 Windows CE是一个多任务操作系统。Windows CE采用了一种新的任务调度策略,也就是将一个进程划分成多个线程,每个线程轮流占用CPU的运行时间和资源。在这种策略下,操作系统将不断地将
2、线程挂起、唤醒、再挂起、再唤醒,如此循环,直至最终完成某个任务。由于 CPU 的处理速度非常快,而且每个时间片又很短,因此给人的感觉是多个线程在同时运行。同样在编写基于Windows CE平台的应用程序时,也会用到多线程解决问题。7.1.1 进程与线程Windows CE操作系统中每个应用程序启动后,就会变成一个单独的进程,并且每个进程都有自己的虚拟内存空向。操作系统可以列举系统的活动进程,并且可以根据进程句柄终止进程或激活进程。由于每个进程都有自己的虚拟内存空间,因此各进程间相互独立,互不干扰。进程是由线程构成的,即线程是Windows CE操作系统中最基本的执行单元。线程有自己独有的堆栈和
3、处理器环境。当线程被挂起时,寄存器将被推到线程的堆栈中,活动的堆栈将变为要运行的下一个线程,该线程的CPU状态将从它的堆栈中被推出,这样新的线程就将开始执行指令。进程中的线程可以共享进程地址空间,进程中的所有线程都能访问给线程分配的内存,不管是文件句柄、内存对象句柄还是同步对象句柄,线程都对其具有相同的访问权限。7.1.2 线程并行运行与优先级Windows CE系统中以以抢占方式调度线程。在HPC和掌上PC中通常时间片单位是25ms,线程时间片为基本的运行单元。当线程运行一个时间片单位后,系统将判断该线程的状态,如果线程不是处于急需运行的状态,操作系统将挂起当前线程,调度运行其他的线程。Wi
4、ndows CE根据优先级方法来决定要运行的线程。高优先级的线程将在低优先级的线程前面被调度。由于Windwos CE 没有进程优先级的概念,因此进程间都是平等的,而进程中的单个线程可以拥有不同的优先级。所有高优先级的线程都将在低优先级的线程之前运行,同一优先级的线程会以循环优先级方式运行。如果线程具有优先级THREAD_PRIORITY_TIME_CRITICAL,它就永远不会被抢占,这个优先级是为编写设备驱动程序中的中断服务线程而保留的,要谨慎使用。如果低优先级的线程拥有高优先级线程正在等待的资源,低优先级的线程将暂时拥有高优先级线程的优先级,这种方案被称为“优先级倒置”(Priority
5、 Inversion)。在高优先级占用资源的情况下,低优先级的线程永远不会有机会去运行,此时线程几乎总是被阻塞,并在调度它们之前等待释放资源。正常情况下应该使线程以THREAD_PRIORITY_NORMAL优先级创建,并拥有同样的优先级,以免造成某些低优先级的线程永远也不会有机会被运行。7.2 多线程的使用 7.2.1 创建线程 在MFC编程中,创建线程应使用AfxBeginThread()函数。在基于SDK的API编程中,应使用CreateThread()函数创建线程。HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
6、DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);7.2.2线程间同步 在使用线程时,会经常遇到两个概念,即线程冲突和线程死锁。线程冲突:线程A读写数据时,同时另外一个线程B也对这个数据进行读写,这时将会导致数据冲突,引起数据混乱。线程死锁:线程A需要线程B中的数据才能够运行,而线程B也需要线程A中的数据才能运行。此时由于双方互相等待,而进入的无法执行的状态成为死锁。经常会有多个线程对同一个共享数据区或资源进行访
7、问的情况,此时还应处理好线程之间的访问顺序。为了解决上述的这些问题,可以采用线程间的同步对象。7.2.3使用事件对象 Windows CE中,最常用到的同步对象就是事件,即Event。事件类似于一个逻辑变量,有两种状态:有信号状态和无信号状态。可以在程序中通过改变事件对象的状态,来触发线程的运行。创建事件可以使用函数CreateEvent。调用SetEvent使事件处于有信号状态。调用ResetEvent使事件处于无信号状态。在线程中可以调用API函数WaitForSingleObject来等待事件对象。此时线程会进入阻塞状态,只消耗很少的CPU时间。当事件对象被触发时,即事件从无信号状态变为
8、有信号状态时,线程会从阻塞状态恢复并继续运行。7.2.4使用互斥体对象 互斥体即Mutex。可以使用函数CreateMute来创建一个互斥体对象。互斥体被创建后,线程可以调用该WaitForSingleObject来等待互斥体。如果互斥体不被任何线程拥有,此时它处于有信号状态。当某个线程获得互斥体对象后,它将变为无信号状态,此时其他调用WaitForSingleObject等待该互斥体对象的线程将阻塞,即同一时刻只有一个线程可以拥有互斥体对象。当拥有互斥体对象的线程执行完它的任务后,可以调用ReleaseMutex函数释放互斥体,函数原型如下所示:ReleaseMutex(hMutex);互斥
9、体被释放后,会重新变为有信号状态。其他某个等待该互斥体的线程获得该互斥体后,即可从阻塞状态恢复,继续运行。7.2.5.使用信号对象 如果在应用程序中,允许固定数目的线程对某个共享资源进行访问,那么可以采用信号对象。创建信号对象的时候,可以指定同时访问该资源的最大线程数目,此时会建立一个计数器,其数值即为最大线程数目。当某个线程获得该信号对象后,计数器中的数字减1。当线程释放信号对象后,该计数器中的数值加1。当计数器中的数值变为0后,申请信号对象的线程会处于阻塞状态,直到计数器中的数值不为0,即其他线程释放了一个信号对象后,这个申请信号对象的线程才能够继续运行。创建信号对象:CreateSema
10、phore();打开信号对象:OpenSemaphore();释放对信号对象:ReleaseSemaphore();7.2.6.使用临界区对象临界区对象和互斥体对象比较类似,不同的是使用临界区对象即可以在线程之间同步,也可以在进程间进行同步。使用函数InitializeCriticalSection创建临界区变量。在线程中,调用函数EnterCriticalSection来申请临界区变量,如果此时临界区变量被其他线程占用,线程进入阻塞状态,直到获得临界区变量。线程执行完成后,应调用LeaveCriticalSection释放临界区变量。如果获得临界区变量的线程在退出时没有释放临界区变量,将导致
11、其他等待该临界区变量的线程死锁。在 使 用 临 界 区 变 量 时,EnterCriticalSection和LeaveCriticalSection函数总是成对出现。7.2.7关闭和退出线程线程执行完毕后会自动退出,也可以在线程执行过程中调用ExitThread函数终止线程。另外还可以在线程外部调用函数TerminateThread来终止线程。如果被终止的线程是进程内的主线程,则该进程也会被终止。7.2.7.事件变量编程实例下面是在Windows CE程序中使用事件变量的例子:(1)创建一个MFC程序,该程序基于基本视图类。将该工程命名为ThreadTest,其余使用默认设置。(2)添加自定义消息MY_MSG01,该消息用于通知系统重画窗口。(3)添加子线程处理函数ThreadProc():(4)创建两个菜单项BEGINTHREAD和KILLTHREAD,分别用作启动和终止子线程(5)重载程序的OnDraw()函数 7.3 思考与练习1 试说明进程和线程的概念。2 线程间同步通常使用哪些同步对象?它们之间的区别都是什么?3 编写一个多线程访问共享数据的程序,使用互斥体对象来同步多个线程。16Q&A17
限制150内