关于Labwindows的多线程技术 联系客服

发布时间 : 星期日 文章关于Labwindows的多线程技术更新完毕开始阅读4b70c40c7cd184254b35356c

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

CmtWaitForThreadPoolFunctionCompletion函数使得主线程等待线程池函数结束后再退出。如果主线程在辅助线程完成之前退出,那么可能会造成辅助线程不能正确地清理分配到的资源。这些辅助线程使用的库也不会被正确的释放掉。

使用异步定时器

为了使用LabWindows/CVI的异步定时器在辅助线程中运行代码,需要调用Toolslib中的NewAsyncTimer函数。需要向函数传递在辅助线程中运行的函数名称和函数执行的时间间隔。传递给NewAsyncTimer的函数被称为异步定时器回调函数。异步定时器仪器驱动程序会按照用户指定的周期调用异步定时器回调函数。异步定时器回调函数的名称是任意的,但是必须遵循下面的原型: int CVICALLBACK FunctionName (int reserved, int timerId, int event, void *callbackData, int eventData1, int eventData2); 由于LabWindows/CVI的异步定时器仪器驱动使用Windows多媒体定时器来实现异步定时器回调函数,所以用户可指定的最小间隔是随使用的计算机不同而变化的。如果用户指定了一个比系统可用的最大分辨率还小的时间间隔,那么可能会产生不可预知的行为。不可预知的行为通常发生在设定的时间间隔小于10ms时。同时,异步定时器仪器驱动使用一个多媒体定时器线程来运行单个程序中注册的所有异步定时器回调函数。所以,如果用户希望程序并行地执行多个函数,那么NI公司推荐使用LabWindows/CVI Utility Library中的线程池函数来代替异步定时器函数。

保护数据

在使用辅助线程的时候,程序员需要解决的一个非常关键的问题是数据保护。在多个线程同时进行访问时,程序需要对全局变量、静态局部变量和动态分配的变量进行保护。不这样做会导致间歇性的逻辑错误发生,而且很难发现。

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次,而只有一次故障。经验告诉我们,有着数据保护不当问题的多线程程序在测试的过程中通常是正确的,但是一到客户安装并运行它们时,就会发生错误。

需要保护的数据类型

只有程序中的多个线程可以访问到的数据是需要保护的。全局变量、静态局部变量和动态分配内存位于通常的内存空间中,程序中的所有线程都可以访问它们。多个线程对内存空间中存储的这些类型的数据进行并发访问时,必须加以保护。函数参数和非静态局部变量位于堆栈上。操作系统为每个线程分配独立的堆栈。因此,每个线程都拥有参数和非静态局部变量的独立副本,所以它们不需要为并发访问进行保护。下面的代码显示了必须为并发访问而保护的数据类型。 int globalArray[1000];// Must be protected static staticGlobalArray[500];// Must be protected int globalInt;// Must be protected void foo (int i)// i does NOT need to be protected { int localInt;// Does NOT need to be protected int localArray[1000];// Does NOT need to be protected int *dynamicallyAllocdArray;// Must be protected static int staticLocalArray[1000];// Must be protected dynamicallyAllocdArray = malloc (1000 * sizeof (int)); } 如何保护数据

通常说来,在多线程程序中保存数据需要将保存数据的变量与操作系统的线程锁对象关联起来。在读取或者设定变量值的时候,需要首先调用操作系统API函数来获取操作系统的线程锁对象。在读取或设定好变量值后,需要将线程锁对象释放掉。在一个特定的时间内,操作系统只允许一个线程获得特定的线程锁对象。一旦线程调用操作系统API函数试图获取另一个线程正在持有的线程锁对象,那么试图获取线程锁对象的线程回在操作系统API获取函数中等待,直到拥有线程锁对象的线程将它释放掉后才返回。试图获取其它线程持有的线程锁对象的线程被称为阻塞线程。LabWindows/CVI Utility Library提供了三种保护数据的机制:线程锁、线程安全变量和线程安全队列。

线程锁对操作系统提供的简单的线程锁对象进行了封装。在三种情况下,你可能要使用到线程锁。如果有一段需要访问多个共享数据变量的代码,那么在运行代码前需要获得线程锁,而在代码运行后释放线程锁。与对每段数据都进行保护相比,这个方法的好处是代码更为简单,而且不容易出错。缺点是减低了性能,因为程序中的线程持有线程锁的时间可能会比实际需要的时间长,这会造成其它线程为获得线程锁而阻塞(等待)的时间变长。使用线程锁的另一种情况是需要对访问非线程安全的第三方库函数时进行保护。例如,有一个非线程安全的DLL用于控制硬件设备而你需要在多个线程中调用这个DLL,那么可以在线程中调用DLL前创建需要获得的线程锁。第三种情况是,你需要使用线程锁来保护多个程序间共享的资源。共享内存就是这样一种资源。

线程安全变量技术将操作系统的线程锁对象和需要保护的数据结合起来。与使用线程锁来保护一段数据相比,这种方法更为简单而且不容易出错。你必须使用线程安全变量来保护所有类型的数据,包括结构体类型。线程安全变量比线程锁更不容易出错,是因为用户需要调用Utility Library API函数来访问数据。而API函数获取操作系统的线程锁对象,避免用户不小心在未获取OS线程锁对象的情况下对数据进行访问的错误。线程安全变量技术比线程锁更简单,因为用户只需要使用一个变量(线程安全变量句柄),而线程锁技术则需要使用两个变量(线程锁句柄和需要保护的数据本身)。

线程安全队列是一种在线程间进行安全的数组数据传递的机制。在程序中有一个线程生成数组数据而另外一个线程对数组数据进行处理时,需要使用线程安全队列。这类程序的一个例子就是在一个线程中采集数据,而在另一个线程中分析数

据或者将数据显示在LabWindows/CVI的用户界面上。与一个数组类型的线程安全变量相比,线程安全队列有着如下的优势:

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

? 用户可以为基于事件的访问配置线程安全队列。用户可以注册一个读取回

调函数,在队列中有一定数量的数据可用时,调用这个函数,并且/或者注册一个写入回调函数,在队列中有一定的空间可用时,调用这个函数。 ? 用户可以对线程安全队列进行配置,使得在数据增加而空间已满时,队列

可以自动生长。

?

线程锁技术

在程序初始化的时候,调用CmtNewLock函数来为每个需要保护的数据集合创建线程锁。这个函数返回一个句柄,用户可以使用它在后续的函数调用中指定线程锁。在访问由锁保护的数据和代码前,线程必须调用CmtGetLock函数来获取线程锁。在访问数据后,线程必须调用CmtReleaseLock函数来释放线程锁。在同一个线程中,可以多次调用CmtGetLock(不会对后续调用产生阻塞),但是用户每一次调用CmtGetLock都需要调用一次CmtReleaseLock来释放。在程序退出时,调用CmtDiscardLock函数来释放线程锁资源。下面的代码演示了如何使用LabWindows/CVI Utility Library中的线程锁来保护全局变量。 int lock; int count; int main (int argc, char *argv[]) { int functionId; 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); } int CVICALLBACK ThreadFunction (void *functionData) { CmtGetLock(lock); count++; CmtReleaseLock(lock);