LabVIEW程序中的线程4-动态连接库函数的线程四、动态连接库函数的线程1.CLN中的线程设置LabVIEW可以通过CLN(CallLibraryFunctionNode)节点来调用动态连接库中的函数,在Windows下就是指.DLL文件中的函数。用户可以通过CLN节点的配置面板来指定被调用函数运行所在的线程。相对于VI的线程配置,CLN的线程选项非常简单,只有两项:界面线程(RuninUIthread)和可重入方式(reentrant)。(新版本LabVIEW把这里的reentrant改为Runinanythread了)图1:在CLN的配置面板上选择函数运行的线程在LabVIEW的程序框图上直接就可以看出一个CLN节点是选用的什么线程。如果是在界面线程,则节点颜色是较深的橘红色的;如果是可重入方式的,则节点是比较淡的黄色。图2:不同颜色表示CLN不同的线程设置2.如何选择合适的线程对于在CLN中选取何种线程,有一个简单的判断方法。如果你要使用的动态连接库是多线程安全的,就选择可重入方式;否则,动态连接库不是多线程安全的,就选择界面线程方式。判断一个动态连接库是不是线程安全的,也比较麻烦。如果这个动态连接库文档中没用明确说明它是多线程安全的,那么就要当他是非线性安全的;如果能看到动态连接库的源代码,代码中存在全局变量、静态变量或者代码中看不到有lock一类的操作,这个动态连接库也就肯定不是多线程安全的。选择了可重入方式,LabVIEW会在最方便的线程内运行动态连接库函数,一般会与调用它的VI运行在同一个线程内。因为LabVIEW是自动多线程的语言,它也很可能会把动态连接库函数分配一个单独的线程运行。如果程序中存在没有直接或间接先后关系的两个CLN节点,LabVIEW很可能会同时在不同的线程内运行它们所调用的函数,也许是同一函数。对于非多线程安全的动态连接库,这是很危险的操作。很容易引起数据混乱,甚至是程序崩溃。选择界面线程方式:因为LabVIEW只有一个界面线程,所以如果所有的CLN设置都是界面线程,那么就可以保证这些CLN调用的函数肯定全部都运行在同一线程下,肯定不会被同时调用。对于非多线程安全的动态连接库,这就保证了它的安全。3.与VI的线程选项相配合如果你的程序中大量频繁的调用了动态连接库函数,那么效率就是一个非常值得注意的问题了。我曾经编写过一个在LabVIEW中使用OpenGL的演示程序(为了演示我们开发的“ImportSharedLibrary功能”),对OpenGL的调用全部是通过CLN方式完成的。由于OpenGL的全部操作必需在同一线程内完成,我把所有的CLN都设置为在界面线程运行的方式。对VI的线程选项没有修改,还是默认的选项。结果程序运行极慢,每秒钟只能刷新一帧图像,CPU占用100%。但是作为动画每秒至少25帧才能看着比较流畅。我开始试图用LabVIEW的profile工具来查找效率低下的VI,结果居然查找不到。在ProfilePerformanceandMemory工具上显示的CPU占用时间只有一点点。这个工具竟然显示不出程序中最耗时的操作在哪里,自然我也对如何优化这个程序无从下手了。后来这个演示程序被搁置了一段时间。直到有一天我从同事给我的提供的一些信息中得到了启发,才突然想通,这些CPU全部被消耗在线程切换中了。我们调用OpenGL方法是为每个OpenGLAPI函数包装一个APIVI,这些APIVI非常简单,程序框图就只有一个CLN节点,调用相应的OpenGL函数。由于每个VI都是在默认的执行线程中运行,而CLN调用的函数却是在界面线程下运行的。所以每次执行一次这样的APIVI,LabVIEW都要做两次线程切换,从执行线程切换到界面线程,执行完函数,在切换回执行线程。线程切换是比较耗时的。我的演示程序刷新一帧要调用大约两千次OpenGLAPIVI,总耗时接近一秒。解决这个问题,要么把所有APIVI中的CLN都改为可重入方式,但编写程序时要保证所有被调用的函数都运行在同一线程内,这比较困难。比较容易实现的是,把程序中对OpenGL操作相关的VI也全部都设置为在界面线程下运行。我选择的就是后一种方法。改进后的程序,每秒钟画30帧图像也不会占满CPU。由此,我也想通了另一个问题。就是我曾经发现调用WindowsAPI函数遇到的错误信息丢失的问题。在调用某一WindowsAPI函数返回值为0时,表示有错误发生了。这时你可以调用GetLastErr和FormatMessage得到错误代码和信息。但是我经常遇到的问题是:前一个函数明明返回值为0,但是随后调用的GetLastErr函数却无法查到错误代码。我想这一定是看上去两个函数是先后被LabVIEW调用的,但实际上LabVIEW在它们之间还要做两次线程切换才行。错误代码就是在线程切换的过程中被丢失了。解决这个问题的办法也是:把调用这三个函数的CLN和调用它们的VI全部设置为在界面线程下运行就可以了。