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