已阅读5页,还剩18页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
NI多核编程基础系列白皮书多核编程基础系列白皮书) o9 t$ 7 E5 i. c Z% 多任务、多线程和多处理这些术语经常被交替地使用,但是它们在本质上是不同的概念。多任务是指操作系统具有在任务间快速切换使得这些任务看起来是在同步执行的能力。在一个抢占式多任务系统中,应用程序可以随时被暂停。使用多线程技术,应用程序可以把它的任务分配到单独的线程中执行。在多线程程序中,操作系统让一个线程的代码执行一段时间(被称为时间片)后,会切换到另外的线程继续运行。暂停某个线程的运行而开始执行另一个线程的行为被称为线程切换。通常情况下,操作系统进行线程切换的速度非常快,令用户觉得有多个线程在同时运行一样。多处理指的是在一台计算机上使用多个处理器。在对称式多处理(SMP)系统中,操作系统自动使用计算机上所有的处理器来执行所有准备运行的线程。借助于多处理的能力,多线程应用程序可以同时执行多个线程,在更短的时间内完成更多的任务。9 ?# . : _1 y1 E9 * j% o3 f1 |; e% # 单线程应用程序移植到多核处理器上运行不会获得性能上的改进,这是因为它们只能在其中一个处理器上运行,而不能像多线程应用程序那样在所有的处理器上同时运行。而且单线程应用程序需要承受操作系统在处理器间切换所需要的开销。为了在多线程操作系统和/或多处理器计算机上获得最优异的性能,我们必须使用多线程技术来编写应用程序。 4 P d0 J7 V$ p* K. K, J 2 k# D0 9 P目录 1. 进行多线程编程的原因 2. 选择合适的操作系统 3. LabWindows/CVI中的多线程技术简介 4. 在LabWindows/CVI的辅助线程中运行代码 5. 保护数据 6. 避免死锁 7. 监视和控制辅助线程 8. 进程和线程优先级 9. 消息处理 10. 使用线程局部变量 11. 在线程局部变量中存储动态分配的数据 12. 在独立线程中运行的回调函数 13. 为线程设定首选的处理器 14. 额外的多线程技术资源. s( x Q2 C1 x # z进行多线程编程的原因在程序中使用多线程技术的原因主要有四个。最常见的原因是多个任务进行分割,这些任务中的一个或多个是对时间要求严格的而且易被其他任务的运行所干涉。例如,进行数据采集并显示用户界面的程序就很适合使用多线程技术实现。在这种类型的程序中,数据采集是时间要求严格的任务,它很可能被用户界面的任务打断。在LabWindows/CVI程序中使用单线程方法时,程序员可能需要从数据采集缓冲区读出数据并将它们显示到用户界面的曲线上,然后处理事件对用户界面进行更新。当用户在界面上进行操作(如在图表上拖动光标)时,线程将继续处理用户界面事件而不能返回到数据采集任务,这将导致数据采集缓冲区的溢出。而在LabWindows/CVI程序中使用多线程技术时,程序员可以将数据采集操作放在一个线程中,而将用户界面处理放在另一个线程中。这样,在用户对界面进行操作时,操作系统将进行线程切换,为数据采集线程提供完成任务所需的时间。3 i1 5 a1 / O7 w( ?在程序中使用多线程技术的第二个原因是程序中可能需要同时进行低速的输入/输出操作。例如,使用仪器来测试电路板的程序将从多线程技术中获得显著的性能提升。在LabWindows/CVI程序中使用单线程技术时,程序员需要从串口发送数据,初始化电路板。,程序需要等待电路板完成操作之后,再去初始化测试仪器。必须要等待测试仪器完成初始化之后,再进行测量,。在LabWindows/CVI程序中使用多线程技术时,你可以使用另一个线程来初始化测试仪器。这样,在等待电路板初始化的同时等待仪器初始化。低速的输入/输出操作同时进行,减少了等待所需要的时间总开销。 |9 m4 / P+ a$ + a在程序中使用多线程技术的第三个原因是借助多处理器计算机来提高性能。计算机上的每个处理器可以都执行一个线程。这样,在单处理器计算机上,操作系统只是使多个线程看起来是同时执行的,而在多处理器计算机上,操作系统才是真正意义上同时执行多个线程的。例如,进行数据采集、将数据写入磁盘、分析数据并且在用户界面上显示分析数据,这样的程序很可能通过多线程技术和多处理器计算机运行得到性能提升。将数据写到磁盘上和分析用于显示的数据是可以同时执行的任务。: V7 C( l8 ; V( c% : F s R在程序中使用多线程技术的第四个原因是在多个环境中同时执行特定的任务。例如,程序员可以在应用程序中利用多线程技术在测试舱进行并行化测试。使用单线程技术,应用程序需要动态分配空间来保存每个舱中的测试结果。应用程序需要手动维护每个记录及其对应的测试舱的关系。而使用多线程技术,应用程序可以创建独立的线程来处理每个测试舱。然后,应用程序可以使用线程局部变量为每个线程创建测试结果记录。测试舱与结果记录间的关系是自动维护的,使应用程序代码得以简化。7 E. G. Q3 I; Q3 v5 P选择合适的操作系统微软公司的Windows 9x系列操作系统不支持多处理器计算机。所以,你必须在多处理器计算机上运行Windows Vista/XP/2000/NT 4.0系统来享受多处理器带来的好处。而且,即使在单处理器计算机上,多线程程序在Windows Vista/XP/2000/NT 4.0上的性能也比在Windows 9x上好。这要归功于Windows Vista/XP/2000/NT 4.0系统有着更为高效的线程切换技术。但是,这种性能上的差别在多数多线程程序中体现得并不是十分明显。7 G1 G- F( Y- _$ v) e7 g对于程序开发,特别是编写和调试多线程程序而言,Windows Vista/XP/2000/NT 4.0系列操作系统比Windows 9x系列更为稳定,当运行操作系统代码的线程被暂停或终止的时候,操作系统的一些部分有可能出于不良状态中。这种情况使得Windows 9x操作系统崩溃的几率远远高于Windows Vista/XP/2000/NT 4.0系统的几率。所以,NI公司推荐用户使用运行Windows Vista/XP/2000/NT 4.0操作系统的计算机来开发多线程程序。 L V9 Y2 ab5 I) NLabWindows/CVI中的多线程技术简介NI LabWindows/CVI软件自二十世纪九十年代中期诞生之日起就支持多线程应用程序的创建。现在,随着多核CPU的广泛普及,用户可以使用LabWindows/CVI来充分利用多线程技术的优势。6 R- E z+ B+ E+ F与Windows SDK threading API(Windows 软件开发工具包线程API)相比,LabWindows/CVI的多线程库提供了以下多个性能优化:+ B+ _6 q5 m. K Thread pools帮助用户将函数调度到独立的线程中执行。Thread pools处理线程缓存来最小化与创建和销毁线程相关的开销。 Thread-safe queues对线程间的数据传递进行了抽象。一个线程可以在另一个线程向队列写入数据的同时,从队列中读取数据。 Thread-safe variables高效地将临界代码段和任意的数据类型结合在一起。用户可以调用简单的函数来获取临界代码段,设定变量值,然后释放临界代码段。 Thread locks提供了一致的API并在必要时自动选择合适的机制来简化临界代码段和互斥量的使用。例如,如果需要在进程间共享互斥锁,或者线程需要在等待锁的时候处理消息,LabWindows/CVI会自动使用互斥量。临界代码段使用在其它场合中,因为它更加高效。 Thread-local variables为每个线程提供变量实例。操作系统对每个进程可用的线程局部变量的数量进行了限制。LabWindows/CVI在实现过程中对线程局部变量进行了加强,程序中的所有线程局部变量只使用一个进程变量。可以在Utility LibraryMultithreading下的LabWindows/CVI库函数树状图中找到所有的多线程函数。6 V0 3 s0 B e; I在LabWindows/CVI的辅助线程中运行代码单线程程序中的线程被称为主线程。在用户告诉操作系统开始执行特定的程序时,操作系统将创建主线程。在多线程程序中,除了主线程外,程序还通知操作系统创建其他的线程。这些线程被称为辅助线程。主线程和辅助线程的主要区别在于它们开始执行的位置。操作系统从main或者WinMain函数开始执行主线程,而由开发人员来指定辅助线程开始执行的位置。* E. Y V. g c在典型的LabWindows/CVI多线程程序中,开发者使用主线程来创建、显示和运行用户界面,而使用辅助线程来进行其它时间要求严格的操作,如数据采集等。LabWindows/CVI提供了两种在辅助进程中运行代码的高级机制。这两种机制是线程池(thread pools)和异步定时器。线程池适合于执行若干次的或者一个循环内执行的任务。而异步定时器适合于定期进行的任务。5 R C# U. Z5 M V$ J$ m使用线程池为了使用LabWindows/CVI的线程池在辅助线程中执行代码,需要调用Utility Library中的CmtScheduleThreadPoolFunction函数。将需要在辅助线程中运行的函数名称传递进来。线程池将这个函数调度到某个线程中执行。根据配置情况和当前的状态,线程池可能会创建新的线程来执行这个函数、也可能会使用已存在的空闲进程执行函数或者会等待一个活跃的线程变为空闲然后使用该线程执行预定的函数。传递给CmtScheduleThreadPoolFunction的函数被称为线程函数。线程池中的线程函数可以选择任意的名称,但是必须遵循以下原型:( J* g. xint CVICALLBACK ThreadFunction (void *functionData);6 Z, Q C6 D$ Yk* Y# w7 N下面的代码显示了如何使用CmtScheduleThreadPoolFunction函数在辅助进程中执行一个数据采集的线程。int CVICALLBACK DataAcqThreadFunction (void *functionData);7 O/ U) 3 + M* ?int main(int argc, char *argv)1 R4 J* K) q; U$ K v+ i x! T2 N7 Y+ q) J6 m9 P1 G int panelHandle;9 L; b( 1 L% B$ U int functionId;* t! C# N% w T: n9 w8 k4 B3 m9 k! y4 B$ |/ T if (InitCVIRTE (0, argv, 0) = 0) 3 J4 M _6 P2 L. _0 z$ return -1; /* out of memory */ u( O9 $ h- e( ! s8 ; L if (panelHandle = LoadPanel(0, DAQDisplay.uir, PANEL) datasafeBufPtr-count = val;0 x# X + k i safeBufPtr-count+;% y D! 4 |. u1 f ReleasePointerToSafeBuf();* w+ 7 q5 h; sH7 yx* x9 L& f1 P2 y3 _1 p+ p检测对GetPointerToVarName的不匹配调用可以通过DefineThreadSafeScalarVar和DefineThreadSafeArrayVar的最后一个参数(maxGetPointerNestingLevel),来指定最大数目的嵌套调用。通常可以把这个参数设为0,这样GetPointerToVarName在检测到同一线程中对GetPointerToVarName的两次连续调用而中间没有对ReleasePointerToVarName进行调用时,就会报出一个运行错误。例如,下面的代码在第二次执行的时候会报出run-time error的错误,因为它忘记了调用ReleasePointerToCount函数。 ) 2 r5 z t# + j int IncrementCount (void), ( O4 6 w0 F/ ; X# T% i# c8 t5 Z2 k# int *countPtr; R g9 U0 i. y7 Ec7 7 b# g8 W s + Z6 y countPtr = GetPointerToCount(); /* run-time error on second execution */* R6 j4 E( + J3 : p3 : A* R! X (*countPtr)+;. H! w: M0 q M2 l, z /* Missing call to ReleasePointerToCount here */; g1 W; k# u; i, return 0;+ ?. T| , f) r+ i2 u4 U# o8 |: y- k/ T, P) Y) T; ( F如果代码中必须对GetPointerToVarName进行嵌套调用时,那么可将maxGetPointerNestingLevel参数设为一个大于零的整数。例如,下面的代码将maxGetPointerNestingLevel参数设定为1,因此它允许对GetPointerToVarName进行一级嵌套调用。9 M, vf- J+ fDefineThreadSafeScalarVar (int, Count, 1);0 W2 Y7 v- Z/ j; D, Q/ t( xint Count (void)8 A2 c u7 . H% k* W7 S; y% ?, l. W d! ae7 J; V int *countPtr;0 9 y* - j7 C9 W countPtr = GetPointerToCount();! i) F) B7 L+ X) f% B (*countPtr)+;W3 y. g* b* jG DoSomethingElse(); /* calls GetPointerToCount */9 v/ j3 d8 q! t8 8 ; k ReleasePointerToCount ();1 h8 l$ 5 H E4 K; R* B) Y% g( 0 c, W) B return 0;* K) B# a$ v. R! i) k! I4 r) a- G5 S* d/ q3 t) 7 H M8 void DoSomethingElse(void). V, S; + ! i% e* a$ y# 3 j ; a, g2 D int *countPtr;) x2 o+ I4 B+ 9 b: U countPtr = GetPointerToCount(); /* nested call to GetPointerToCount */ E L D, J2 Z I: L . /* do something with countPtr */$ D: v; x & s2 i0 n4 f ReleasePointerToCount (); j. l- K% q H0 v8 C: ?: v% P9 z+ a) ?5 p/ f3 m3 F5 c3 g9 o* J) H, s9 r3 u8 2 a) M2 d如果不知道GetPointerToVarName的最大嵌套级别,那么请传递TSV_ALLOW_UNLIMITED_NESTING来禁用对GetPointerToVarName函数的不匹配调用检查。 H+ f; S* S0 % + K6 线程安全队列使用LabWindows/CVI Utility Library的线程安全队列,可以在线程间安全地传递数据。当需要用一个线程来采集数据而用另一个线程来处理数据时,这种技术非常有用。线程安全队列在其内部处理所有的数据锁定。通常说来,应用程序中的辅助线程获取数据,而主线程在数据可用时读取数据然后分析并/或显示数据。下面的代码显示了线程如何使用线程安全队列将数据传递到另外一个线程。在数据可用时,主线程利用回调函数来读取数据。T6 q- R4 ; n, W7 int queue;|6 f b+ i i Yint panelHandle; y% o( G0 K- d! U3 |+ y. B1 r5 E! U; m0 6 int main (int argc, char *ar
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2020-2025年一级建造师之一建建设工程经济模拟题库及答案下载
- 胆囊粘液囊肿的护理
- 雨课堂学堂在线学堂云《财务管理(重庆邮电大学 )》单元测试考核答案
- 房地产 -建设工程在竣工验收之前发包人擅自投入使用发包人能否以使用部分工程质量不符合合同约定为由向承包人主张权利
- 七上语文名著《西游记》中考真题及答案
- 小说《西游记》阅读练习题(附答案)
- 2026年陕西省选调生招录(面向西安电子科技大学)备考公基题库带答案解析
- 2026年劳务员之劳务员基础知识考试题库200道及一套参考答案
- 2026年劳务员之劳务员基础知识考试题库200道带答案(模拟题)
- 2025福州工业园区开发集团有限公司勘察设计分公司招聘2人备考题库带答案解析
- 制氢技术与工艺 课件 第7章 氨制氢
- GB/T 44340-2024粮食储藏玉米安全储藏技术规范
- ME工程师基础知识单选题100道及答案
- 2024年合肥客运服务考试题
- 学前教育普及普惠督导评估内容和标准量化评分表
- JT-T-1051-2016城市轨道交通运营突发事件应急预案编制规范
- JBT 5300-2024 工业用阀门材料 选用指南(正式版)
- 2024年高考英语(读后续写)模拟真题 (新高考专用)(含解析)
- 临床医学导论习题与答案1
- 2024年资本市场行业培训资料
- 口腔医学技术大学生职业生涯规划
评论
0/150
提交评论