关于labwindows的多线程技术.docx

上传人:b****4 文档编号:24783828 上传时间:2023-06-01 格式:DOCX 页数:26 大小:32.54KB
下载 相关 举报
关于labwindows的多线程技术.docx_第1页
第1页 / 共26页
关于labwindows的多线程技术.docx_第2页
第2页 / 共26页
关于labwindows的多线程技术.docx_第3页
第3页 / 共26页
关于labwindows的多线程技术.docx_第4页
第4页 / 共26页
关于labwindows的多线程技术.docx_第5页
第5页 / 共26页
点击查看更多>>
下载资源
资源描述

关于labwindows的多线程技术.docx

《关于labwindows的多线程技术.docx》由会员分享,可在线阅读,更多相关《关于labwindows的多线程技术.docx(26页珍藏版)》请在冰豆网上搜索。

关于labwindows的多线程技术.docx

关于labwindows的多线程技术

多任务、多线程和多处理这些术语经常被交替地使用,但是它们在本质上是不同的概念。

使用多线程技术,应用程序可以把它的任务分配到单独的线程中执行。

在多线程程序中,操作系统让一个线程的代码执行一段时间〔被称为时间片〕后,会切换到另外的线程继续运行。

暂停某个线程的运行而开始执行另一个线程的行为被称为线程切换。

通常情况下,操作系统进展线程切换的速度非常快,令用户觉得有多个线程在同时运行一样。

1.进展多线程编程的原因

2.选择适宜的操作系统

3.LabWindows/CVI中的多线程技术简介

4.在LabWindows/CVI的辅助线程中运行代码

5.保护数据

6. 防止死锁

7. 监视和控制辅助线程

8. 进程和线程优先级

9. 消息处理

10. 使用线程局部变量

11. 在线程局部变量中存储动态分配的数据

12. 在独立线程中运行的回调函数

13. 为线程设定首选的处理器

14. 额外的多线程技术资源

进展多线程编程的原因

在程序中使用多线程技术的原因主要有四个。

最常见的原因是多个任务进展分割,这些任务中的一个或多个是对时间要求严格的而且易被其他任务的运行所干预。

例如,进展数据采集并显示用户界面的程序就很适宜使用多线程技术实现。

在这种类型的程序中,数据采集是时间要求严格的任务,它很可能被用户界面的任务打断。

在程序中使用多线程技术的第二个原因是程序中可能需要同时进展低速的输入/输出操作。

例如,使用仪器来测试电路板的程序将从多线程技术中获得显著的性能提升。

在LabWindows/CVI程序中使用单线程技术时,程序员需要从串口发送数据,初始化电路板。

,程序需要等待电路板完成操作之后,再去初始化测试仪器。

必需要等待测试仪器完成初始化之后,再进展测量,。

在LabWindows/CVI程序中使用多线程技术时,你可以使用另一个线程来初始化测试仪器。

这样,在等待电路板初始化的同时等待仪器初始化。

低速的输入/输出操作同时进展,减少了等待所需要的时间总开销。

在程序中使用多线程技术的第三个原因是借助多处理器计算机来进步性能。

计算机上的每个处理器可以都执行一个线程。

这样,在单处理器计算机上,操作系统只是使多个线程看起来是同时执行的,而在多处理器计算机上,操作系统才是真正意义上同时执行多个线程的。

例如,进展数据采集、将数据写入磁盘、分析数据并且在用户界面上显示分析数据,这样的程序很可能通过多线程技术和多处理器计算机运行得到性能提升。

将数据写到磁盘上和分析用于显示的数据是可以同时执行的任务。

在程序中使用多线程技术的第四个原因是在多个环境中同时执行特定的任务。

例如,程序员可以在应用程序中利用多线程技术在测试舱进展并行化测试。

使用单线程技术,应用程序需要动态分配空间来保存每个舱中的测试结果。

应用程序需要手动维护每个记录及其对应的测试舱的关系。

而使用多线程技术,应用程序可以创立独立的线程来处理每个测试舱。

然后,应用程序可以使用线程局部变量为每个线程创立测试结果记录。

测试舱与结果记录间的关系是自动维护的,使应用程序代码得以简化。

选择适宜的操作系统

微软公司的Windows9x系列操作系统不支持多处理器计算机。

所以,你必须在多处理器计算机上运行WindowsVista/XP/2000/NT4.0系统来享受多处理器带来的好处。

而且,即使在单处理器计算机上,多线程程序在WindowsVista/XP/2000/NT4.0上的性能也比在Windows9x上好。

这要归功于WindowsVista/XP/2000/NT4.0系统有着更为高效的线程切换技术。

但是,这种性能上的差异在多数多线程程序中表达得并不是十清楚显。

对于程序开发,特别是编写和调试多线程程序而言,WindowsVista/XP/2000/NT4.0系列操作系统比Windows9x系列更为稳定,当运行操作系统代码的线程被暂停或终止的时候,操作系统的一些局部有可能出于不良状态中。

这种情况使得Windows9x操作系统崩溃的几率远远高于WindowsVista/XP/2000/NT4.0系统的几率。

所以,NI公司推荐用户使用运行WindowsVista/XP/2000/NT4.0操作系统的计算机来开发多线程程序。

LabWindows/CVI中的多线程技术简介

NILabWindows/CVI软件自二十世纪九十年代中期诞生之日起就支持多线程应用程序的创立。

如今,随着多核CPU的广泛普及,用户可以使用LabWindows/CVI来充分利用多线程技术的优势。

与WindowsSDKthreadingAPI〔Windows软件开发工具包线程API〕相比,LabWindows/CVI的多线程库提供了以下多个性能优化:

∙Threadpools帮助用户将函数调度到独立的线程中执行。

Threadpools处理线程缓存来最小化与创立和销毁线程相关的开销。

∙Thread-safequeues对线程间的数据传递进展了抽象。

一个线程可以在另一个线程向队列写入数据的同时,从队列中读取数据。

∙Thread-safevariables高效地将临界代码段和任意的数据类型结合在一起。

用户可以调用简单的函数来获取临界代码段,设定变量值,然后释放临界代码段。

∙Threadlocks提供了一致的API并在必要时自动选择适宜的机制来简化临界代码段和互斥量的使用。

例如,假设需要在进程间共享互斥锁,或者线程需要在等待锁的时候处理消息,LabWindows/CVI会自动使用互斥量。

临界代码段使用在其它场合中,因为它更加高效。

∙Thread-localvariables为每个线程提供变量实例。

操作系统对每个进程可用的线程局部变量的数量进展了限制。

LabWindows/CVI在实现过程中对线程局部变量进展了加强,程序中的所有线程局部变量只使用一个进程变量。

可以在UtilityLibrary»Multithreading下的LabWindows/CVI库函数树状图中找到所有的多线程函数。

在LabWindows/CVI的辅助线程中运行代码

单线程程序中的线程被称为主线程。

在用户告诉操作系统开始执行特定的程序时,操作系统将创立主线程。

在多线程程序中,除了主线程外,程序还通知操作系统创立其他的线程。

这些线程被称为辅助线程。

主线程和辅助线程的主要区别在于它们开始执行的位置。

操作系统从main或者WinMain函数开始执行主线程,而由开发人员来指定辅助线程开始执行的位置。

在典型的LabWindows/CVI多线程程序中,开发者使用主线程来创立、显示和运行用户界面,而使用辅助线程来进展其它时间要求严格的操作,如数据采集等。

LabWindows/CVI提供了两种在辅助进程中运行代码的高级机制。

这两种机制是线程池〔threadpools〕和异步定时器。

线程池适宜于执行假设干次的或者一个循环内执行的任务。

而异步定时器适宜于定期进展的任务。

使用线程池

为了使用LabWindows/CVI的线程池在辅助线程中执行代码,需要调用UtilityLibrary中的CmtScheduleThreadPoolFunction函数。

将需要在辅助线程中运行的函数名称传递进来。

线程池将这个函数调度到某个线程中执行。

根据配置情况和当前的状态,线程池可能会创立新的线程来执行这个函数、也可能会使用已存在的空闲进程执行函数或者会等待一个活泼的线程变为空闲然后使用该线程执行预定的函数。

传递给CmtScheduleThreadPoolFunction的函数被称为线程函数。

线程池中的线程函数可以选择任意的名称,但是必须遵循以下原型:

intCVICALLBACKThreadFunction(void*functionData);

下面的代码显示了如何使用CmtScheduleThreadPoolFunction函数在辅助进程中执行一个数据采集的线程。

intCVICALLBACKDataAcqThreadFunction(void*functionData);

intmain(intargc,char*argv[])

{

   intpanelHandle;

   intfunctionId;

 

   if(InitCVIRTE(0,argv,0)==0)

     return-1;/*outofmemory*/

   if((panelHandle=LoadPanel(0,"DAQDisplay.uir",PANEL))<0)

     return-1;

  DisplayPanel(panelHandle);

  CmtScheduleThreadPoolFunction(DEFAULT_THREAD_POOL_HANDLE,DataAcqThreadFunction,NULL,&functionId);

  RunUserInterface();

  DiscardPanel(panelHandle);

  CmtWaitForThreadPoolFunctionCompletion(DEFAULT_THREAD_POOL_HANDLE,functionId,0);

  return0;

}

intCVICALLBACKDataAcqThreadFunction(void*functionData)

{

  while(!

quit){

    Acquire(...);

    Analyze(...);

  }

  return0;

}

在前面的代码中,主线程调用了CmtScheduleThreadPoolFunction函数,使线程池创立了一个新的线程来运行DataAcqThreadFunction线程函数。

主线程从CmtScheduleThreadPoolFunction函数返回,而无须等待DataAcqThreadFunction函数完成。

在辅助线程中的DataAcqThreadFunction函数与主线程中的调用是同时执行的。

CmtScheduleThreadPoolFunction函数的第一个参数表示用于进展函数调度的线程池。

LabWindows/CVI的UtilityLibrary中包含了内建的默认线程池。

传递常数DEFAULT_THREAD_POOL_HANDLE表示用户希望使用默认的线程池。

但是用户不能对默认线程池的行为进展自定义。

用户可以调用CmtNewThreadPool函数来创立自定义的线程池。

CmtNewThreadPool函数返回一个线程池句柄,这个句柄将作为第一个参数传递给CmtScheduleThreadPoolFunction函数。

程序员需要调用CmtDiscardThreadPool函数来释放由CmtNewThreadPool函数创立的线程池资源。

CmtScheduleThreadPoolFunction函数中的最后一个参数返回一个标识符,用于在后面的函数调用中引用被调度的函数。

调用CmtWaitForThreadPoolFunctionCompletion函数使得主线程等待线程池函数完毕后再退出。

假设主线程在辅助线程完成之前退出,那么可能会造成辅助线程不能正确地清理分配到的资源。

这些辅助线程使用的库也不会被正确的释放掉。

使用异步定时器

为了使用LabWindows/CVI的异步定时器在辅助线程中运行代码,需要调用Toolslib中的NewAsyncTimer函数。

需要向函数传递在辅助线程中运行的函数名称和函数执行的时间间隔。

传递给NewAsyncTimer的函数被称为异步定时器回调函数。

异步定时器仪器驱动程序会按照用户指定的周期调用异步定时器回调函数。

异步定时器回调函数的名称是任意的,但是必须遵循下面的原型:

 intCVICALLBACKFunctionName (intreserved,inttimerId,intevent,void*callbackData,inteventData1,inteventData2);

由于LabWindows/CVI的异步定时器仪器驱动使用Windows多媒体定时器来实现异步定时器回调函数,所以用户可指定的最小间隔是随使用的计算机不同而变化的。

假设用户指定了一个比系统可用的最大分辨率还小的时间间隔,那么可能会产生不可预知的行为。

不可预知的行为通常发生在设定的时间间隔小于10ms时。

同时,异步定时器仪器驱动使用一个多媒体定时器线程来运行单个程序中注册的所有异步定时器回调函数。

所以,假设用户希望程序并行地执行多个函数,那么NI公司推荐使用LabWindows/CVIUtilityLibrary中的线程池函数来代替异步定时器函数。

保护数据

在使用辅助线程的时候,程序员需要解决的一个非常关键的问题是数据保护。

在多个线程同时进展访问时,程序需要对全局变量、静态局部变量和动态分配的变量进展保护。

不这样做会导致间歇性的逻辑错误发生,而且很难发现。

LabWindows/CVI提供了各种高级机制帮助用户对受到并发访问的数据进展保护。

保护数据时,一个重要的考虑就是防止死锁。

假设一个变量被多个线程访问,那么它必须被保护,以确保它的值可靠。

例如下面一个例子,一个多线程程序在多个线程中对全局整型counter变量的值进展累加。

count=count+1;

这段代码按照以下CPU指令顺序执行的:

1.将变量值移入处理器的存放器中

2.增加存放器中的变量值

3.把存放器中的变量值写回count变量

由于操作系统可能在线程运行过程中的任意时刻打断线程,所以执行这些指令的两个线程可能按照如下的顺序进展〔假设count初始值为5〕:

线程1:

将count变量的值移到存放器中。

(count=5,存放器=5),然后切换到线程2(count=5,存放器未知)。

线程2:

将count变量的值移到存放器中(count=5,存放器=5)。

线程2:

增加存放器中的值(count=5,存放器=6)。

线程2:

将存放器中的值写回count变量(count=6,存放器=6),然后切换回线程1.(count=6,存放器=5)。

线程1:

增加存放器的值。

(count=6,存放器=6)。

线程1:

将存放器中的值写回count变量(count=6,register=6)。

由于线程1在增加变量值并将其写回之前被打断,所以变量count的值被设为6而不是7。

操作系统为系统中地每一个线程的存放器都保存了副本。

即使编写了count++这样的代码,用户还是会遇到一样的问题,因为处理器会将代码按照多条指令执行。

注意,特定的时序状态导致了这个错误。

这就意味着程序可能正确运行1000次,而只有一次故障。

经历告诉我们,有着数据保护不当问题的多线程程序在测试的过程中通常是正确的,但是一到客户安装并运行它们时,就会发生错误。

需要保护的数据类型

只有程序中的多个线程可以访问到的数据是需要保护的。

全局变量、静态局部变量和动态分配内存位于通常的内存空间中,程序中的所有线程都可以访问它们。

多个线程对内存空间中存储的这些类型的数据进展并发访问时,必须加以保护。

函数参数和非静态局部变量位于堆栈上。

操作系统为每个线程分配独立的堆栈。

因此,每个线程都拥有参数和非静态局部变量的独立副本,所以它们不需要为并发访问进展保护。

下面的代码显示了必须为并发访问而保护的数据类型。

intglobalArray[1000];//Mustbeprotected

staticstaticGlobalArray[500];//Mustbeprotected

intglobalInt;//Mustbeprotected

voidfoo(inti)//idoesNOTneedtobeprotected

{

  intlocalInt;//DoesNOTneedtobeprotected

  intlocalArray[1000];//DoesNOTneedtobeprotected

  int*dynamicallyAllocdArray;//Mustbeprotected

  staticintstaticLocalArray[1000];//Mustbeprotected

  dynamicallyAllocdArray=malloc(1000*sizeof(int));

}

如何保护数据

通常说来,在多线程程序中保存数据需要将保存数据的变量与操作系统的线程锁对象关联起来。

在读取或者设定变量值的时候,需要首先调用操作系统API函数来获取操作系统的线程锁对象。

在读取或设定好变量值后,需要将线程锁对象释放掉。

在一个特定的时间内,操作系统只允许一个线程获得特定的线程锁对象。

一旦线程调用操作系统API函数试图获取另一个线程正在持有的线程锁对象,那么试图获取线程锁对象的线程回在操作系统API获取函数中等待,直到拥有线程锁对象的线程将它释放掉后才返回。

试图获取其它线程持有的线程锁对象的线程被称为阻塞线程。

LabWindows/CVIUtilityLibrary提供了三种保护数据的机制:

线程锁、线程平安变量和线程平安队列。

线程锁对操作系统提供的简单的线程锁对象进展了封装。

在三种情况下,你可能要使用到线程锁。

假设有一段需要访问多个共享数据变量的代码,那么在运行代码前需要获得线程锁,而在代码运行后释放线程锁。

与对每段数据都进展保护相比,这个方法的好处是代码更为简单,而且不容易出错。

缺点是减低了性能,因为程序中的线程持有线程锁的时间可能会比实际需要的时间长,这会造成其它线程为获得线程锁而阻塞(等待)的时间变长。

使用线程锁的另一种情况是需要对访问非线程平安的第三方库函数时进展保护。

例如,有一个非线程平安的DLL用于控制硬件设备而你需要在多个线程中调用这个DLL,那么可以在线程中调用DLL前创立需要获得的线程锁。

第三种情况是,你需要使用线程锁来保护多个程序间共享的资源。

共享内存就是这样一种资源。

线程平安变量技术将操作系统的线程锁对象和需要保护的数据结合起来。

与使用线程锁来保护一段数据相比,这种方法更为简单而且不容易出错。

你必须使用线程平安变量来保护所有类型的数据,包括构造体类型。

线程平安变量比线程锁更不容易出错,是因为用户需要调用UtilityLibraryAPI函数来访问数据。

而API函数获取操作系统的线程锁对象,防止用户不小心在未获取OS线程锁对象的情况下对数据进展访问的错误。

线程平安变量技术比线程锁更简单,因为用户只需要使用一个变量(线程平安变量句柄),而线程锁技术那么需要使用两个变量(线程锁句柄和需要保护的数据本身)。

线程平安队列是一种在线程间进展平安的数组数据传递的机制。

在程序中有一个线程生成数组数据而另外一个线程对数组数据进展处理时,需要使用线程平安队列。

这类程序的一个例子就是在一个线程中采集数据,而在另一个线程中分析数据或者将数据显示在LabWindows/CVI的用户界面上。

与一个数组类型的线程平安变量相比,线程平安队列有着如下的优势:

∙线程平安队列在其内部使用了一种锁策略,一个线程可以从队列读取数据而同时另一个线程向队列中写入数据(例如,读取和写入线程不会互相阻塞)。

∙用户可以为基于事件的访问配置线程平安队列。

用户可以注册一个读取回调函数,在队列中有一定数量的数据可用时,调用这个函数,并且/或者注册一个写入回调函数,在队列中有一定的空间可用时,调用这个函数。

∙用户可以对线程平安队列进展配置,使得在数据增加而空间已满时,队列可以自动生长。

线程锁技术

在程序初始化的时候,调用CmtNewLock函数来为每个需要保护的数据集合创立线程锁。

这个函数返回一个句柄,用户可以使用它在后续的函数调用中指定线程锁。

在访问由锁保护的数据和代码前,线程必须调用CmtGetLock函数来获取线程锁。

在访问数据后,线程必须调用CmtReleaseLock函数来释放线程锁。

在同一个线程中,可以屡次调用CmtGetLock〔不会对后续调用产生阻塞〕,但是用户每一次调用CmtGetLock都需要调用一次CmtReleaseLock来释放。

在程序退出时,调用CmtDiscardLock函数来释放线程锁资源。

下面的代码演示了如何使用LabWindows/CVIUtilityLibrary中的线程锁来保护全局变量。

intlock;

intcount;

intmain(intargc,char*argv[])

{

  intfunctionId;

  CmtNewLock(NULL,0,&lock);

  CmtScheduleThreadPoolFunction(DEFAULT_THREAD_POOL_HANDLE,ThreadFunction,NULL,&functionId);

  CmtGetLock(lock);

  count++;

  CmtReleaseLock(lock);

  CmtWaitForThreadPoolFunctionCompletion(DEFAULT_THREAD_POOL_HANDLE,functionId,0);

  CmtDiscardLock(lock);

}

intCVICALLBACKThreadFunction(void*functionData)

{

  CmtGetLock(lock);

  count++;

  CmtReleaseLock(lock);

  return0;

}

线程平安变量

线程平安变量技术将数据和操作系统线程锁对象结合成为一个整体。

这个方法防止了多线程编程中一个常见的错误:

程序员在访问变量时往往忘记首先去获得锁。

这种方法还使得在函数间传递保护的数据变得容易,因为只需要传递线程平安变量句柄而不需要既传递线程锁句柄又要传递保护的变量。

LabWindows/CVIUtilityLibraryAPI中包含了几种用于创立和访问线程平安变量的函数。

利用这些函数可以创立任何类型的线程平安变量。

因为,传递到函数中的参数在类型上是通用的,而且不提供类型平安。

通常,你不会直接调用LabWindows/CVIUtilityLibrary中的线程平安变量函数。

LabWindows/CVIUtilityLibrary中的头文件中包含了一些宏,它们提供了配合UtilityLibrary函数使用的类型平安的封装函数。

除了提供类型平安,这些宏还帮助防止了多线程编程中的其它两个常见错误。

这些错误是在访问数据后忘记释放锁对象,或者是在前面没有获取锁对象时试图释放锁对象。

使用DefineThreadSafeScalarVar和DefineThreadSafeArrayVar宏来创立线程平安变量和类型平安的函数供使用和访问。

假设需要从多个源文件中访问线程平安变量,请在include(.h)文件中使用DeclareThreadSafeScalarVar或者DeclareThreadSafeArrayVar宏来创立访问函数的声明。

DefineThreadSafeScalarVar (datatype,VarName,maxGetPointerNestingLevel)宏创立以下访

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > IT计算机 > 电脑基础知识

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

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