《labview 学习笔记》由会员分享,可在线阅读,更多相关《labview 学习笔记(80页珍藏版)》请在金锄头文库上搜索。
1、reference 死锁问题LabVIEW中的引用经常需要和“In Place Element Structure”配合使用。In Place Element Structure 对一个引用的数据进行处理时,为了保证多线程安全,它会锁住引用指向的数据;其它线程若需对同一数据做操作,必须能这个In Place Element Structure中所有代码执行完毕才可,这样就避免了多线程读写同一内存数据所产生的竞争问题。举例来说,下面这段程序的执行时间是1秒:而下面这段程序的执行时间则是2秒:因为第二段程序中的两个In Place Element Structure必须顺序执行。有了“锁住”这个操
2、作,就有不小心造成死锁的可能。比如对于同一数据的引用,千万不能嵌套使用In Place Element Structure,否则就会死锁:在上面这个示例中,程序运行至内层的In Place Element Structure,就会停在这里等外层In Place Element Structure运行结束,释放它锁住的数据;而对于外层In Place Element Structure来说,它内部的全部代码要运行结束,它才结束。因而它们相互等待,造成了死锁。Packed Project Libraries 2 与Library的比较acked Project Library 从名字上来看,就是被
3、包装好了的Project Library。Project Library 是编程时候由程序员创建出来的。比如下图这个工程,我在里面创建了一个叫做“My Algorithm Library.lvlib”的工程库。它包含两个VI,其中一个是私有的。Packed Project Library 并不是手工创建的,他是通过一个项目的生成规范,从 Project Library 编译而来的。比如上图的项目,我创建了一个Packed Library类型的生成规范。我在这个生成规范中指定把“My Algorithm Library.lvlib”编译成Packed Project Library 。编译的结果
4、是在我指定的路径下生成了一个名为“My Algorithm Library.lvlibp”的文件。它的后缀名仅比Packed Library多了一个字母p。双击这个文件,可以打开它,看到他里面包含的VI:如果需要在其它项目中使用到这个Packed Project Library,我们可以直接把它加到另一个项目中去,下图是一个演示项目:Packed Project Library 看上去和 Project Library 非常相似,用法也完全相同。Packed Project Library 与 Project Library 都是将功能相关的一组VI封装起来的方法; 库中的VI可以具有层次机构
5、; 库中的VI都带有名字空间,名字空间是带有后缀名的库名; 都可以方便的放在项目管理器里使用尽管它们十分相似,Packed Project Library 与 Project Library 相比,还是有一些明显区别的: Packed Project Library 是通过编译生成的; Packed Project Library 中的VI是编译后产生的,它们不能被修改; Packed Project Library 包含有私有VI,但用户无法看到也不能使用它们; Packed Project Library 把VI,.lvlib以及其它用到的文件都打成一个压缩包,用户在磁盘上就只能看到一个.
6、lvlibp文件,看不到VI文件; Packed Project Library 很适合作为最终产品发布给用户使用; 在项目中使用Packed Project Library 可以缩短编译时间,因为Packed Project Library 中的VI是已编译好的,不会再随项目编译一遍。(这一条先这样写上,但我还需要再深入研究一下)LabVIEW中LVClass数据转换成XML格式的问题前一段时间,一个同事的程序出了问题。他在程序中把一个LVClass类型的数据转换成XML格式,再保存成文件。但是从文件中把数据转回成 LVClass时,却出了问题:在调用“Unflatten XML”这个函数时
7、,程序有时出错,有时又不出错。他的程序中使用了大量的LVClass,并且它们之间有着复杂的继承与包含关系,以至于花了两三天的事 件,才找出问题所在。其实是个简单的问题,只是在设计程序时他没有意识到。我做了一个简化的程序,可以重现这个问题:首先,给一个子类的对象设置一些数据。然后把它当做父类类型的数据,平化成XML文本,存盘:关闭LabVIEW,然后重新打开LabVIEW。再编写一个反向程序,把XML数据转换成父类类型的数据:发现Unflatten From XML函数返回一个错误,value中是一个空的数据。错误产生原因如下:在把子类数据转换成父类数据类型,这个类型虽然是父类的,但其数据仍然是
8、子类的。再转换成XML格式,XML格式中记录的仍然是子类的数据。在反向过程中,Unflatten From XML拿到的数据是子类的,但它企图转换时,却发现内存中没有子类的类型信息,因此它也就不知道如何转换这个数据,所以报错。如果这个程序稍微改动一下,把XML数据直接转换成子类的数据,就不会出错了:实际上,子类的数据总是可以用父类来表示的。因此这个XML数据亦可以直接被转换成父类的类型,但前提是,一定要保证子类的类型别家在到内存中去了。只要在程序中放置一个子类的对象,自然就可以把子类加载至内存。像下面这个程序就可以正常工作:这个实验反映出两个问题:1. 把XML中的内容如果是属于某个LVCla
9、ss类型的数据,把这些数据转换回LVClass数据时,那个LVClass一定要已经存在于内存才行。2. 在之前的一篇文章“LvClass 的一个效率问题”中提到过:当子类被加载如内存时,它所有的父类也会被加载入内存。但反过来并不成立。因为一个类有哪些父类是确定的,父类的地址就记录在子类中。但一个类并不知道他会有多少子类,任何人都可以从它派生出不同的子类来,因此它在装入内存时,不可能把自己的子类也都装进来。LabVIEW中实现链表、树等数据结构LabVIEW自带的数据结构只有数组和队列。多数情况下,这两种数据结构足够开发者使用了。但是,我平时使用C+和C#语言更多一些,所以编写程序时常常会想到使
10、用其它编程语言中常见的数据结构比如链表(List)、树(Tree)等。LabVIEW中也可以编程实现这些数据结构,一个比较直观易懂的编程方法是基于LabVIEW中的类和引用来实现各类数据结构。我在我和 LabVIEW一书的第13.3.5节中介绍了一个简单的链表容器的实现方法,它是基于LvClass编写的,数据流驱动的一种容器。但是正如我在书中 提到的,它虽然和有一些和文本编程语言中的链表相类似的地方,但本质并不相同。文本编程语言中的链表,树等数据结构离不开引用(或指针),节点之间是通过 引用来相互关联的。LabVIEW可以为数据创建引用,因此也可以方便的实现与文本语言中功能相同的数据结构。这里
11、插一段,介绍一下数据结构和数据容器的关系,我自己理解是这样的:数据结构侧重于数据的存储方式,比如如何排序;数据结构在加上与此结构相关的 操作方法,比如添加删除数据等方法,就构成了一个数据容器。脱离了操作方法,单纯的数据结构用处非常有限。因此,我文章中在提到数据结构或者数据容器时, 指的都是同一回事:数据结构和相关的方法。为了介绍如何在LabVIEW中实现一个数据结构,我打算以双向链表为例,讲解一下如何编写它。双向链表中每个节点都会记录上一个节点和下一个节点的位置。因此,在双向链表中,可以从一个节点直接跳转到它的上一个或下一个节点上去,也就是正向 或反向遍历整个链表。可以直观的想到,使用LvCl
12、ass实现这样的节点,只要为这个节点创建一个类ListNode,并且这个类有两个成员变量,它们的 类型都是ListNode的引用,分别用于指向前一个和后一个节点就可以了:这样的设计在文本编程语言中是没有问题的,但在LabVIEW中行不通。其它编程语言中,程序运行时,才会对类的对象进行初始化。LabVIEW 中,VI一打开,它上面的控件和常量就需要被初始化了。某个对象在初始化时,它的成员变量也要被初始化,若它的成员变量的类型还是这个类,这以初始化的过 程就陷入了死锁:类需要它的成员变量先初始化;它成员变量需要这个类先初始化。基于同样的原因,一个类的成员变量的数据类型也不可以是这个类的子类:子类初
13、始化需要先对它的父类进行初始化。但是,一个类的成员变量的数据类型可以是这个类的父类:父类在初始化的时候,不需要理会它的任何子类。既然父类初始化时,不依赖于子类的初始化;而子类的对象又可以被当做父类的类型来保存,咱们就可以利用这一特性在LabVIEW中实现可以数据结构 的节点了。只不过LabVIEW实现链表的节点要多一个步骤:我们需要为ListNode类再定义一个父类ListNodeVirtual。这个父类不做 任何实质性的工作,它仅用于保存相邻节点的引用。以上两个类是针对链表节点的双向链表本身也需要做成一个类:DoubleLinkedList类,这个类中封装有链表的属性和方法。比如它需要一个指
14、向链表表头的引用,需要有为链表添加删除数据的方法,为遍历链表中的数据,还需要有一个迭代器作为演示,我只实现了链表的几个简单功能。演示程序工程结构如下:ListNode的成员变量包括一个数据,和两个指向前后节点的引用:DoubleLinkedList类的成员变量包括指向链表头节点的引用,迭代器指向的节点的引用,并记录了链表长度下面看一下链表中几个主要方法是如何实现的。首先是Append after Enumerator.vi这个方法,它是链表里最复杂的一个方法。它的输入是链表中一个新的节点,它把这个新节点添加在链表迭代器指向的那个节点的后面。在给链表添加数据时,会遇到两种情况。首先,这个链表是一
15、个空链表,那么被添加的节点就是这个链表的首节点,链表的迭代器也应当指向这一唯一的节点。我设计的这个链表是一个环状链表。当链表中只有一个节点的时候,这个链表的上一个和下一个节点都是它自己。如果链表不是空的,就把新节点插在迭代器指向的节点的后面。因此:新节点的前一节点指向的应当是迭代器指向的那个节点;新节点的后一节点是迭代器指向节点原来的后一节点。迭代器指向节点的新的后一节点应当是这个新 节点;原来迭代器的后一节点的前一节点也应当换成这个新的节点。最后,我把迭代器也指向了这个新的节点,这样连续添加新节点时,它们会按照先后顺序插入链 表。我的演示程序还用到了其它几个方法。Reset Enumerator.vi 负责把迭代器复位,也就是指向链表的头节点:Enumerator go Next.vi 用于让迭代器向后移动一个节点:Enumerator Value.vi 返回迭代器指向的那个节点:使用这几个方法就可以搭建出一个简单的演示程序来看一下链表如何工作了。下面这个演示程序中,分两部分:第一部分是左面那个循环,每次循环迭代就会 创建出一个新的ListNode对象,它的数值是当前迭代的次数;右半部分使用链表的迭代器遍历链表中的节点。在这个演示程序中,迭代器移动次数比链表长 度多了两次,因为链表是环状的,转着圈访问