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

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

使用LabWindows/CVI Utility Library的线程安全队列,可以在线程间安全地传递数据。当需要用一个线程来采集数据而用另一个线程来处理数据时,这种技术非常有用。线程安全队列在其内部处理所有的数据锁定。通常说来,应用程序中的辅助线程获取数据,而主线程在数据可用时读取数据然后分析并/或显示数据。下面的代码显示了线程如何使用线程安全队列将数据传递到另外一个线程。在数据可用时,主线程利用回调函数来读取数据。

int queue; int panelHandle; int main (int argc, char *argv[]) { if (InitCVIRTE (0, argv, 0) == 0) return -1; /* out of memory */ if ((panelHandle = LoadPanel(0, \ return -1; /* create queue that holds 1000 doubles and grows if needed */ CmtNewTSQ(1000, sizeof(double), OPT_TSQ_DYNAMIC_SIZE, &queue); CmtInstallTSQCallback (queue, EVENT_TSQ_ITEMS_IN_QUEUE, 500, QueueReadCallback, 0, CmtGetCurrentThreadID(), NULL); CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, DataAcqThreadFunction, NULL, NULL); DisplayPanel (panelHandle); RunUserInterface(); . . . return 0; } void CVICALLBACK QueueReadCallback (int queueHandle, unsigned int event, int value, void *callbackData) { double data[500]; CmtReadTSQData (queue, data, 500, TSQ_INFINITE_TIMEOUT, 0); } 避免死锁

当两个线程同时等待对方持有的线程锁定对象时,代码就不能继续运行了。这种状况被称为死锁。如果用户界面线程发生死锁,那么它就不能响应用户的输入。用户必须非正常地结束程序。下面的例子解释了死锁是如何发生的。 线程1:调用函数来获取线程锁A(线程1:无线程锁,线程2:无线程锁)。 线程1:从获取线程锁的函数返回(线程1:持有线程锁A,线程2:无线程锁)。

切换到线程2:(线程1:持有线程锁A,线程2:无线程锁)。

线程2:调用函数来获取线程锁B(线程1:持有线程锁A,线程2:无线程锁)。 线程2:从获取线程锁的函数返回(线程1:持有线程锁A,线程2:持有线程锁B)。

线程2:调用函数来获取线程锁A(线程1:持有线程锁A,线程2:持有线程锁B)。

线程2:由于线程1持有线程锁A而被阻塞(线程1:持有线程锁A,线程2:持有线程锁B)。

切换到线程1:调用函数来获取线程锁B(线程1:持有线程锁A,线程2:持有线程锁B)。

线程1:调用函数来获取线程锁B(线程1:持有线程锁A,线程2:持有线程锁B)。

线程1:由于线程2持有线程锁A而被阻塞(线程1:持有线程锁A,线程2:持有线程锁B)。

与不对数据进行保护时产生的错误相似,由于程序运行的情况不同导致线程切换的时序不同,死锁错误间歇性地发生。例如,如果直到线程1持有线程锁A和B后才切换到线程2,那么线程1就可以完成工作而释放掉这些线程锁,让线程2在晚些时候获取到。就像上面所说的那样,死锁现象只有在线程同时获取线程锁时才会发生。所以你可以使用简单的规则来避免这种死锁。当需要获取多个线程锁对象时,程序中的每个线程都需要按照相同的顺序来获取线程锁对象。下面的LabWindows/CVI Utility Library函数获取线程锁对象,并且返回时并不释放这些对象。

CmtGetLock

? CmtGetTSQReadPtr ? CmtGetTSQWritePtr

?

注意事项:通常说来,不需要直接调用CmtGetTSVPtr函数。它是通过

DeclareThreadSafeVariable宏创建的GetPtrToVarName函数调用的。因此,对于调用的GetPtrToVarName函数需要将它作为线程锁对象获取函数来对待,应该注意死锁保护的问题。

The following Windows SDK functions can acquire thread-locking objects without releasing them before returning. Note: This is not a comprehensive list.

下面的Windows SDK函数可以获取线程锁对象但在返回时并不释放这些对象。注意,这不是完整的列表。

? ? ? ? ? ?

EnterCriticalSection CreateMutex

CreateSemaphore

SignalObjectAndWait WaitForSingleObject

MsgWaitForMultipleObjectsEx

监视和控制辅助线程

在把一个函数调度到独立的线程中运行时,需要对被调度函数的运行状态进行监视。为了获得被调度函数的运行状态,调用CmtGetThreadPoolFunctionAttribute来获得

ATTR_TP_FUNCTION_EXECUTION_STATUS属性的值。也可以注册一个回调函数,线程池调用之后立即运行被调度的函数和/或开始运行后立即由线程池调用。如果需要注册这样的回调函数,必须使用CmtScheduleThreadFunctionAdv来对函数进行调度。

通常说来,辅助进程需要在主线程结束程序前完成。如果主线程在辅助线程完成之前结束,那么辅助线程将不能够将分配到的资源清理掉。同时,可能导致这些辅助线程所使用的库函数也不能被正确清除。

可以调用CmtWaitForThreadPoolFunctionCompletion函数来安全地等待辅助线程结束运行,然后允许主线程结束。

在一些例子中,辅助线程函数必须持续完成一些工作直到主线程让它停止下来。在这类情况下,辅助线程通常在while循环中完成任务。while循环的条件是主线程中设定的整数变量,当主线程需要告知辅助线程停止运行时,将其设为非零整数。下面的代码显示了如何使用while循环来控制辅助线程何时结束执行。 volatile int quit = 0; int main (int argc, char *argv[]) { int functionId; CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, NULL, &functionId); // This would typically be done inside a user interface // Quit button callback. quit = 1; CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, functionId, 0); return 0; } int CVICALLBACK ThreadFunction (void *functionData) { while (!quit) { . . . } return 0; } 注意事项:如果使用volatile关键字,这段代码在经过优化的编译器(如

Microsoft Visual C++)后功能是正常的。优化的编译器确定while循环中的代码不会修改quit变量的值。因此,作为优化,编译器可能只使用quit变量在while循环条件中的初始值。使用volatile关键字是告知编译器另一个线程可能会改变quit变量的值。这样,编译器在每次循环运行时都使用更新过后的quit变量值。

有些时候,当主线程进行其他任务的时候需要暂停辅助线程的运行。如果你暂停正在运行操作系统代码的线程,可能会使得操作系统处于非法状态。因此,在需要暂停的线程中需要始终调用Windows SDK的SuspendThreadfunction函数。这样,可以确保线程在运行关键代码时不被暂停。在另一个线程中调用Windows SDK的ResumeThreadfunction是安全的。下面的代码展示了如何使用它们。 volatile int quit = 0; int main (int argc, char *argv[]) { int functionId; CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, NULL, &functionId); // This would typically be done inside a user interface // Quit button callback. quit = 1; CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, functionId, 0); return 0; } int CVICALLBACK ThreadFunction (void *functionData) { while (!quit) { . . . } return 0; }