ImageVerifierCode 换一换
格式:DOCX , 页数:43 ,大小:1.14MB ,
资源ID:7288795      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/7288795.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(7线程的调度优先级和亲缘性.docx)为本站会员(b****6)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

7线程的调度优先级和亲缘性.docx

1、7线程的调度优先级和亲缘性第7章 线程的调度、优先级和亲缘性抢占式操作系统必须使用某种算法来确定哪些线程应该在何时调度和运行多长时间。本章将要介绍Microsoft Windows 98和Windows 2000使用的一些算法。上一章介绍了每个线程是如何拥有一个上下文结构的,这个结构维护在线程的内核对象中。这个上下文结构反映了线程上次运行时该线程的C P U寄存器的状态。每隔2 0 m s左右,Wi n d o w s要查看当前存在的所有线程内核对象。在这些对象中,只有某些对象被视为可以调度的对象。Wi n d o w s选择可调度的线程内核对象中的一个,将它加载到C P U的寄存器中,它的值

2、是上次保存在线程的环境中的值。这项操作称为上下文转换。Wi n d o w s实际上保存了一个记录,它说明每个线程获得了多少个运行机会。使用M i c r o s o f t S p y + +这个工具,就可以了解这个情况。图7 - 1显示了一个线程的属性。注意,该线程已经被调度了37 379次。目前,线程正在执行代码,并对它的进程的地址空间中的数据进行操作。再过2 0 m s左右,Wi n d o w s就将C P U的寄存器重新保存到线程的上下文中。线程不再运行。系统再次查看其余的可调度线程内核对象,选定另一个线程的内核对象,将该线程的上下文加载到C P U的寄存器中,然后继续运行。当系统

3、引导时,便开始加载线程的上下文,让线程运行,保存上下文和重复这些操作,直到系统关闭。 图7-1 线程的属性总之,这就是系统对线程进行调度的过程。这很简单,是不是? Wi n d o w s被称为抢占式多线程操作系统,因为一个线程可以随时停止运行,随后另一个线程可进行调度。如你所见,可以对它进行一定程度的控制,但是不能太多。记住,无法保证线程总是能够运行,也不能保证线程能够得到整个进程,无法保证其他线程不被允许运行等等。注意程序员常常问我,如何才能保证线程在某个事件的某个时间段内开始运行,比如,如何才能确保某个线程在数据从串行端口传送过来的1 m s内开始运行呢?我的回答是,办不到。实时操作系统

4、才能作出这样的承诺,但是Wi n d o w s不是实时操作系统。实时操作系统必须清楚地知道它是在什么硬件上运行,这样它才能知道它的硬盘控制器和键盘等的等待时间。M i c r o s o f t对Wi n d o w s规定的目标是,使它能够在各种不同的硬件上运行,即能够在不同的C P U、不同的驱动器和不同的网络上运行。简而言之,Wi n d o w s没有设计成为一种实时操作系统。尽管应强调这样一个概念,即系统只调度可以调度的线程,但是实际情况是,系统中的大多数线程是不可调度的线程。例如,有些线程对象的暂停计数大于1。这意味着该线程已经暂停运行,不应该给它安排任何C P U时间。通过调用

5、使用C R E AT E _ S U S P E N D E D标志的C r e a t e P r o c e s s或C r e a t e T h r e a d函数,可以创建一个暂停的线程。(本章后面还要介绍S u s p e n dT h r e a d和R e s u m e T h r e a d函数。)除了暂停的线程外,其他许多线程也是不可调度的线程,因为它们正在等待某些事情的发生。例如,如果运行N o t e p a d,但是并不键入任何数据,那么N o t e p a d的线程就没有什么事情要做。系统不给无事可做的线程分配C P U时间。当移动N o t e p a d的窗

6、口时,或者N o t e p a d的窗口需要刷新它的内容,或者将数据键入N o t e p a d,系统就会自动使N o t e p a d的线程成为可调度的线程。这并不意味着N o t e p a d的线程立即获得了C P U时间。它只是表示N o t e p a d的线程有事情可做,系统将设法在某个时间(不久的将来)对它进行调度。7.1 暂停和恢复线程的运行在线程内核对象的内部有一个值,用于指明线程的暂停计数。当调用C r e a t e P r o c e s s或C r e a t e T h r e a d函数时,就创建了线程的内核对象,并且它的暂停计数被初始化为1。这可以防止线程

7、被调度到C P U中。当然,这是很有用的,因为线程的初始化需要时间,你不希望在系统做好充分的准备之前就开始执行线程。当线程完全初始化好了之后, C r e a t e P r o c e s s或C r e a t e T h r e a d要查看是否已经传递了C R E ATE_ SUSPENDED标志。如果已经传递了这个标志,那么这些函数就返回,同时新线程处于暂停状态。如果尚未传递该标志,那么该函数将线程的暂停计数递减为0。当线程的暂停计数是0的时候,除非线程正在等待其他某种事情的发生,否则该线程就处于可调度状态。在暂停状态中创建一个线程,就能够在线程有机会执行任何代码之前改变线程的运行环

8、境(如优先级)。一旦改变了线程的环境,必须使线程成为可调度线程。要进行这项操作,可以调用R e s u m e T h r e a d,将调用C r e a t e T h r e a d函数时返回的线程句柄传递给它(或者是将传递给C r e a t e P r o c e s s的p p i P r o c I n f o参数指向的线程句柄传递给它):DWORD ResumeThread(HANDLE hThread);如果R e s u m e T h r e a d 函数运行成功,它将返回线程的前一个暂停计数,否则返回0 x F F F F F F F F。单个线程可以暂停若干次。如果一

9、个线程暂停了3次,它必须恢复3次,然后它才可以被分配给一个C P U。当创建线程时,除了使用C R E AT E _ S U S P E N D E D外,也可以调用S u s p e n dT h r e a d函数来暂停线程的运行:DWORD SuspendThread(HANDLE hThread);任何线程都可以调用该函数来暂停另一个线程的运行(只要拥有线程的句柄)。不用说,线程可以自行暂停运行,但是不能自行恢复运行。与R e s u m e T h r e a d一样,S u s p e n d T h r e a d返回的是线程的前一个暂停计数。线程暂停的最多次数可以是M A X

10、I M U M _ S U S P E N D _ C O U N T次(在Wi n N T. h中定义为1 2 7)。注意, S u s p e n d T h r e a d与内核方式的执行是异步进行的,但是在线程恢复运行之前,不会发生用户方式的执行。在实际环境中,调用S u s p e n d T h r e a d时必须小心,因为不知道暂停线程运行时它在进行什么操作。如果线程试图从堆栈中分配内存,那么该线程将在该堆栈上设置一个锁。当其他线程试图访问该堆栈时,这些线程的访问就被停止,直到第一个线程恢复运行。只有确切知道目标线程是什么(或者目标线程正在做什么),并且采取强有力的措施来避免因

11、暂停线程的运行而带来的问题或死锁状态,S u s p e n d T h r e a d才是安全的(死锁和其他线程同步问题将在第8、9和1 0章介绍)。7.2 暂停和恢复进程的运行对于Wi n d o w s来说,不存在暂停或恢复进程的概念,因为进程从来不会被安排获得C P U时间。但是,曾经有人无数次问我如何暂停进程中的所有线程的运行。Wi n d o w s确实允许一个进程暂停另一个进程中的所有线程的运行,但是从事暂停操作的进程必须是个调试程序。特别是,进程必须调用Wa i t F o r D e b u g E v e n t和C o n t i n u e D e b u g E v

12、e n t之类的函数。由于竞争的原因,Wi n d o w s没有提供其他方法来暂停进程中所有线程的运行。例如,虽然许多线程已经暂停,但是仍然可以创建新线程。从某种意义上说,系统必须在这个时段内暂停所有新线程的运行。M i c r o s o f t已经将这项功能纳入了系统的调试机制。虽然无法创建绝对完美的S u s p e n d P r o c e s s函数,但是可以创建一个该函数的实现代码,它能够在许多条件下出色地运行。下面是我的S u s p e n d P r o c e s s函数的实现代码: VOID SuspendProcess(DWORD dwProcessID, BOOL

13、 fSuspend) /Get the list of threads in the system. HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, dwProcessID); if(hSnapshot != INVALID_HANDLE_VALUE) /Walk the list of threads. THREADENTRY32 te = sizeof(te) ; BOOL fOk = Thread32First(hSnapshot, &te); for(; fOk; fOk = Thread32Next(hS

14、napshot, &te) /Is this thread in the desired process? if(te.th32OwnerProcessID = dwProcessID) /Attempt to convert the thread ID into a handle. HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID); if(hThread != NULL) /Suspend or resume the thread. if(fSuspend) SuspendThread(hTh

15、read); else ResumeThread(hThread); CloseHandle(hThread); CloseHandle(hSnapshot); 我的S u s p e n d P r o c e s s函数使用To o l H e l p函数来枚举系统中的线程列表。当我找到作为指定进程的组成部分的线程时,我调用O p e n T h r e a d: HANDLE OpenThread(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadID);这个新Windows 2000函数负责找出带有匹配的线程I D的线程

16、内核对象,对内核对象的使用计数进行递增,然后返回对象的句柄。运用这个句柄,我调用S u s p e n d T h r e a d ( 或R e s u m e T h r e a d )。由于O p e n T h r e a d在Windows 2000中是个新函数,因此我的S u s p e n d P r o c e s s函数在Windows 95或Windows 98上无法运行,在Windows NT 4.0或更早的版本上也无法运行。也许你懂得为什么S u s p e n d P r o c e s s不能总是运行,原因是当枚举线程组时,新线程可以被创建和撤消。因此,当我调用C r

17、 e a t e To o l h e l p 3 2 S n a p s h o t后,一个新线程可能会出现在目标进程中,我的函数将无法暂停这个新线程。过了一些时候,当调用S u s p e n d P r o c e s s函数来恢复线程的运行时,它将恢复它从未暂停的一个线程的运行。更糟糕的是,当枚举线程I D时,一个现有的线程可能被撤消,一个新线程可能被创建,这两个线程可能拥有相同的I D。这将会导致该函数暂停任意些个(也许在目标进程之外的一个进程中的)线程的运行。当然,这些情况不太可能出现。如果非常了解目标进程是如何运行的,那么这些问题也许根本不是问题。我提供这个函数供酌情使用。7.3

18、 睡眠方式线程也能告诉系统,它不想在某个时间段内被调度。这是通过调用S l e e p函数来实现的: VOID Sleep(DWORD dwMilliseconds);该函数可使线程暂停自己的运行,直到d w M i l l i s e c o n d s过去为止。关于S l e e p函数,有下面几个重要问题值得注意: 调用S l e e p,可使线程自愿放弃它剩余的时间片。 系统将在大约的指定毫秒数内使线程不可调度。不错,如果告诉系统,想睡眠1 0 0 m s,那么可以睡眠大约这么长时间,但是也可能睡眠数秒钟或者数分钟。记住, Wi n d o w s不是个实时操作系统。虽然线程可能在规定

19、的时间被唤醒,但是它能否做到,取决于系统中还有什么操作正在进行。 可以调用S l e e p,并且为d w M i l l i s e c o n d s参数传递I N F I N I T E。这将告诉系统永远不要调度该线程。这不是一件值得去做的事情。最好是让线程退出,并还原它的堆栈和内核对象。 可以将0传递给S l e e p。这将告诉系统,调用线程将释放剩余的时间片,并迫使系统调度另一个线程。但是,系统可以对刚刚调用S l e e p的线程重新调度。如果不存在多个拥有相同优先级的可调度线程,就会出现这种情况。7.4 转换到另一个线程系统提供了一个称为S w i t c h To T h r

20、 e a d的函数,使得另一个可调度线程(如果存在能够运行):BOOL SwitchToThread();当调用这个函数的时候,系统要查看是否存在一个迫切需要C P U时间的线程。如果没有线程迫切需要C P U时间,S w i t c h To T h r e a d就会立即返回。如果存在一个迫切需要C P U时间的线程,S w i t c h To T h r e a d就对该线程进行调度(该线程的优先级可能低于调用S w i t c h To T h r e a d的线程)。这个迫切需要C P U时间的线程可以运行一个时间段,然后系统调度程序照常运行。该函数允许一个需要资源的线程强制另一个

21、优先级较低、而目前却拥有该资源的线程放弃该资源。如果调用S w i t c h To T h r e a d函数时没有其他线程能够运行,那么该函数返回FA L S E,否则返回一个非0值。调用S w i t c h To T h r e a d函数与调用S l e e p是相似的,并且传递给它一个0 m s的超时。差别是S w i t c h To T h r e a d允许优先级较低的线程运行。即使低优先级线程迫切需要C P U时间,S l e e p也能够立即对调用线程重新进行调度。Windows 98 Windows 98没有配备该函数的非常有用的实现代码。7.5 线程的运行时间有时想要

22、计算线程执行某个任务需要多长的时间。许多人采取的办法是编写类似下面的代码:/Get the current time (start time).DWORD dwStartTime = GetTickCount();/Perform complex algorithm here./Subtract start time from current time to get duration.DWORD dwElapsedTime = GetTickCount() - dwStartTime;这个代码做了一个简单的假设:即它不会被中断。但是,在抢占式操作系统中,永远无法知道线程何时被赋予C P U时间

23、。当取消线程的C P U时间时,就更难计算线程执行不同任务时所用的时间。我们需要一个函数,以便返回线程得到的C P U时间的数量。幸运的是,Wi n d o w s提供了一个称为G e t T h r e a d Ti m e s的函数,它能返回这些信息:BOOL GetThreadTimes(HANDLE hThread, PFILETIME pftCreationTime, PFILETIME pftExitTime, PFILETIME pftKernelTime, PFILETIME pftUserTime);G e t T h r e a d Ti m e s函数返回4个不同的时间值

24、,这些值如表7 - 1所示。表7-1 GetThreadTimes 函数的返回时间值 时间值含义创建时间用英国格林威治时间1 6 0 1年1月1日午夜后1 0 0 n s的时间间隔表示的英国绝对值,用于指明线程创建的时间退出时间用英国格林威治时间1 6 0 1年1月1日午夜后1 0 0 n s的时间间隔表示的英国绝对值,用于指明线程退出的时间。如果线程仍然在运行,退出时间则未定义内核时间一个相对值,用于指明线程执行操作系统代码已经经过了多少个1 0 0 n s的C P U时间用户时间一个相对值,用于指明线程执行应用程序代码已经经过了多少个1 0 0 n s的C P U时间使用这个函数,可以通过

25、使用下面的代码确定执行复杂的算法时需要的时间量:_int64 FileTimeToQuadWord(PFILETIME pft) return (Int64ShllMod32( pft-dwHighDateTime, 32) | pft-dwLowDateTime);void PerformLongOperation() FILETIME ftKernelTimeStart, ftKernelTimeEnd; FILETIME ftUserTimeStart, ftUserTimeEnd; FILETIME ftDummy; _int64 qwKernelTimeElapsed, qwUser

26、TimeElapsed, qwTotalTimeElapsed; /Get starting times. GetThreadTimes(GetCurrentThread(), &ftDummy, &ftDummy, &ftKernelTimeStart, &ftUserTimeStart); /Perform complex algorithm here. /Get ending times. GetThreadTimes(GetCurrentThread(), &ftDummy, &ftDummy, &ftKernelTimeEnd, &ftUserTimeEnd); /Get the e

27、lapsed kernel and user times by /converting the start and end times /from FILETIMEs to quad words, and then /subtract the start times from the end times. qwKernelTimeElapsed = FileTimeToQuadWord(&ftKernelTimeEnd) - FileTimeToQuadWord(&ftKernelTimeStart); qwUserTimeElapsed = FileTimeToQuadWord(&ftUse

28、rTimeEnd) - FileTimeToQuadWord(&ftUserTimeStart); /Get total time duration by adding the kernel /and user times. qwTotalTimeElapsed = qwKernelTimeElapsed + qwUserTimeElapsed; /The total elapsed time is in /qwTotalTimeElapsed.注意,G e t P r o c e s s Ti m e s是个类似G e t T h r e a d Ti m e s的函数,适用于进程中的所有线

29、程:BOOL GetProcessTimes(HANDLE hProcess, PFILETIME pftCreationTime, PFILETIME pftExitTime, PFILETIME pftKernelTime, PFILETIME pftUserTime);G e t P r o c e s s Ti m e s返回的时间适用于某个进程中的所有线程(甚至是已经终止运行的线程)。例如,返回的内核时间是所有进程的线程在内核代码中经过的全部时间的总和。Windows 98 遗憾的是, G e t T h r e a d Ti m e s和G e t P r o c e s s Ti

30、 m e s这两个函数在Wi n d o w s9 8中不起作用。在Windows 98中,没有一个可靠的机制可供应用程序来确定线程或进程已经使用了多少C P U时间。对于高分辨率的配置文件来说, G e t T h r e a d Ti m e s并不完美。Wi n d o w s确实提供了一些高分辨率性能函数:BOOL QueryPerformanceFrequency( LARGE_INTEGER* pliFrequency);BOOL QueryPerformanceCounter( LARGE_INTEGER* pliCount);虽然这些函数认为,正在执行的线程并没有得到抢占的机会

31、,但是高分辨率的配置文件是为短期存在的代码块设置的。为了使这些函数运行起来更加容易一些,我创建了下面这个C + +类:class CStopwatch public: CStopwatch() QueryPerformanceFrequency(&m_liPerfFreq); Start(); void Start() QueryPerformanceCounter(&m_liPerfStart); _int64 Now() const /Returns # of milliseconds since /Start was called LARGE_INTEGER liPerfNow; QueryPerformanceCounter(&liPerfNow); return (liPerfNow.QuadPart - m_liPerfStart.QuadPart) * 1000)/ m_liPerfFreq.QuadPart); private: /Counts per second LARGE_INTEGER m_liPerfFreq; /Starting count LARGE_INTEGER m_liPerfStart; ;使用这个类如下:/C

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1