模块接口 API 的两种设计方案

    假如你要设计一个程序模块,它的功能是读写 INI 文件。用户调用这个模块,就可以方便的把信息写入 INI 文件,或从其中读出信息。 
    你将如何设计这个模块的接口呢?LabVIEW 中常见的方式有两种,第一,为模块的每个方法都做一个子 VI,比如写数值型数据的方法做一个 VI,写字符串的做一个 VI,读字符串的一个 VI 等等;另一种方案:把所有的方法都放到一个子 VI 里去,用户通过一个变量来选择运行哪个方法。

    这两种方案各有优缺点。第一种方案符合一般人的思维模式,更容易让用户理解和学会使用。现在 LabVIEW 中处理 INI 文件的模块采用的就是这种方案。每个用户可能用到的方法(甚至是每一种数据类型),都有一个对应的 VI。维护起来也容易,哪个方法有 bug,到它对应的那个 VI 中去调试就可以了。

    但是打开这些处理 INI 文件的 VI,他们调用了一个更底层的模块,这个模块采用的是第二种接口方案。所有对 INI 文件底层的操作,都被放到了一个子 VI(Config Data Registry.vi)里。用输入参数("function")来控制执行不同的功能。
    这种方案也有它的好处,我看过一本叫做《软件工程方法在LabVIEW中的应用》的书,它的内容用一句话来概括,就是号召大家把模块都写成上述的第二种方案。不过我们先来说一下着第二种方案的弊端。

    首先,给外部用户的感觉就不如第一种方案那么清晰易学。如果把所有方法分开成独立的 VI,用户可以只专注学习自己可能会用到的功能对应的 VI;而第二种方案,所有功能在一个接口 VI 里,那就强迫用户把所有功能都要了解一下。
    其次,每种不同功能所用到的参数都不尽相同。采用第二种方案,就意味着这个唯一的接口 VI 要包含所有方法时用到的控件(参数)。所以这个 VI 上的控件会比较多。并且,有的控件在调用不同功能时,用途(或者说所表达的意思)不同。这样不但会造成用户学习的困难,在使用时,也非常容易出错。
    还有一条,第二种方案的效率在某些情况下非常低下。我们把一个模块提供给用户,但用户不见得会使用这个模块中所有的功能。第一种方案,用户程序是在编译时选择使用模块中的那些方法;而第二种方案是在运行时选择使用什么方法。如果用户只用到一个模块中的一两个功能,采用第二种方案,只用用户用到的方法相关的代码才会被链接到它的程序中;而采用第二个方案,不论用户是否需要,整个模块都会被链接到它的程序中去。
    这是因为这几个缺点,造成现在 LabVIEW 提供给用户的库中,几乎都是采用的第一种接口方案。

    但是,着第二种方案,一度是 LabVIEW 程序设计中一个非常流行的方法,自然也有他的优点。
    其一是更好的解决模块封装的问题。在 LabVIEW 8 之前,LabVIEW 本身不支持面向对象编程,也没有提供对一个模块进行封装的功能。我如果编写一个功能模块给用户,我这个模块中所有的 VI,即便是我只把它当作内部使用,都可以被用户调用。这是很不安全的,因为内部 VI 随时都可能被改变调整,从而引起客户应用程序的错误。如果所有的功能都通过一个 VI 暴露给用户,则用户更容易搞清楚只有这个 VI 他可以用,其它的 VI 都是不能被他直接使用的。并且这样也可以使自己编写的一大堆 VI 看上去也更像是一个模块或组件。
    LabVIEW 的另一个问题是,它作为数据流驱动的编程语言,不像文本语言那样可以方便的使用全局或局部变量。在 LabVIEW 中使用全局或局部变量不但效率查,还会严重影响程序的可维护性。我编写的模块,它所用到的内部数据如何组织呢?全局变量既然不好,那就只能考虑使用移位寄存器了。
    LabVIEW 程序如果设计的不好,数据在不同节点间传递时会产生很多份拷贝,造成效率低下。为了解决这个问题,最好是我内部使用数据,就不要再在 VI 之间传来传去了。打开 Config Data Registry.vi,你会发现这个 VI 的主体框架是一个只运行一次的循环。凡是这种只运行一次的循环,程序真正想利用的都是循环上的移位寄存器。这个 VI 里的多个移位寄存器都是既无输入又无输出的,它们的功能是用来保存模块的私有数据。 

    用移位寄存器保存模块的全部私有数据,模块的所有方法都在移位寄存器之间完成。这样数据始终在一个 VI 内,避免了数据在不同 VI 之间传递可能会引起的复制。这是很长一段时间内都相当流行的 LabVIEW 程序模块设计思路,不过我觉得也许现在可以放弃这个方案了。
    首先,这个实现方法只适合功能简单的小模块,模块的大部分代码都放到一个 VI 中。如果模块数据功能较多,还用这个方法编出来的 VI 就很难读懂,没法维护了。Config Data Registry.vi 虽然功能并不复杂,但代码已经不那么清晰易懂了。
    如果这个模块在程序中只有一个实例还好办,若要支持多个实例,那数据部分就要设计个更为复杂以确保模块不同实例之间的数据不会混乱。
    最重要的是现在 LabVIEW 自身已经开始支持面向对象的功能了。在 LVClass 中,既可以有数据,也可以有方法;方法可以被定义为是私有的或共有的;另外之支持继承、多态等。所有这些都为功能模块的封装和接口提供了更好的解决方案。与其费尽心机的自己想办法把格模块包装的更合理,不如直接利用 LVOOP 已有的功能。把自己的的模块都设计为 LVClass。

《我和 LabVIEW》

编辑

父母与同伴对孩子的影响

    人是天性和教养(基因和环境影响)共同的结果。虽然基因很重要,但他是我们所不能改变的。而教养的影响使得我们有可能通过自己的努力,使自己或帮助别人取得更大成绩。所以,不论基因的影响有多大,作为一个个体,把精力关注到后天教养上,才会对自己更有好处。

    做父母的总是希望尽量帮自己的孩子成功。父母的确在某些方面对孩子有着决定性的影响,比如宗教信仰。一般来说,父母皈依哪个教派,他们的孩子也都会入那个教。但是,在更多的方面,父母的影响都不及同伴的影响大。
    比如说,父母想让小孩吃某种食物,但小孩无论如何都不听话。但是,如果这个小孩发现周围的小朋友都在津津有味的吃这样东西,他就也会去吃。
    父母大多不希望自己孩子吸烟,而青少年的吸烟原因,主要是因为自己的同伴中有人吸烟。如果孩子的同伴中有人吸毒,他很可能也被影响。
    在移民家庭中,如果父母和同伴使用的的是不同语言或口音,孩子会选择使用同伴的语言和口音,而不是父母的。

    也许人的天性就是这样的,大父母与同伴的影响有冲突,我们更倾向于吸收同伴的影响而不是父母。但做父母的并非对此束手无策,聪明的父母反而可以利用这一点。
    明智的父母通过为孩子选择同伴来影响孩子向自己希望的方向发展,所以为孩子挑选好的学校和社区尤为重要。

    想起了孟母三迁的故事。孟妈妈肯定没做过大量的调查研究,但她的直觉还真是不错。

《生活随笔》

编辑

LabVIEW 对多核 CPU 的支持

    以前,在计算机领域有个摩尔定律,是说每一年半,CPU 的主频都会提高一倍。但是近几年这个定律在CPU主频上已经失效了,我 4 年前用的计算机 CPU 主频是 2G,我前几天换了一台新电脑,CPU 主频还是 2G。
    现在主要两个 CPU 生产商都意识到单纯通过提高处理器主频来提升性能的办法行不通了。他们的新策略是通过增加 CPU 的内核来提升系统整体性能。

    现在双核 CPU 是商用电脑的主流配置,也有高端电脑采用了四核 CPU。Intel 更是宣称他们用不了5年就会做出有 80 个核的 CPU 来。
    多个 CPU 同时工作,效率固然是高。但是,为了充分发挥多核的优势,为了发挥多核的威力,还要你的软件针对多核进行一定的优化才行。首先,你的程序至少是多线程运行的。

    使用常用的文本语言,比如 C++ 编写一个多线程的程序并不是一项简单的工作。除了要非常熟悉 C++
的基本编程方法,程序员还需要了解 Windows 多线程的运行机制,熟悉 Windows API 的调用方法,或者 MFC 的架构等等。在
C++ 上调试多线程程序,更是被许多程序员视为噩梦。
    但如果使用 LabVIEW 编写多线程程序,情况就大为不同了。LabVIEW 是自动多线程的编程语言,LabVIEW
程序员可以不需要了解任何与多线程相关概念与知识。只要他在 VI 的程序框图上,并排放上两段没有先后关系的代码,LabVIEW
就会自动把这两段代码放在不同的线程中,并行运行。而在多核 CPU 的计算机上,操作系统会自动为这两个线程分配两个 CPU
内核。这样就有效地利用了多核 CPU 可以并行运算的优势。LabVIEW 的程序员不知不觉中就完成了一段支持多核系统的程序。

    有操作系统来分配 CPU 也许效率还不是最高的。
   
比如我现在有这样一个程序(图1),有数据采集、显示和分析三个模块。三个模块是并行执行的。我的电脑是双核的,于是操作系统分配 CPU0
先做数据采集,CPU1 先做数据显示,等数据采集做完了,CPU0
又会去做数据处理(图2)。数据处理是个相对任务较为繁重的线程,而电脑一个CPU做数据处理时,另一个 CPU
却空闲在那里。这种负载不均衡就造成了程序对于整体系统的CPU利用率不高。

 
图1, 2:操作系统为多线程程序自动分配CPU

    对于效率要求极为苛刻的程序,还需要更高效的解决方案。LabVIEW 8.5 提供了一种解决方案,就是利用它的定时结构来有程序员人为指定 CPU 的分配方案。
   
定时结构包括定时循环结构(Time Loop)和定时顺序结构(Time
Sequence),他们的主要用于在程序中精确的定时执行某段代码,但是在 LabVIEW 8.5
中它们又多了一个新的功能,就是指定结构内的代码运行在哪一个 CPU
上。在图3中,定时顺序结构左边四边带小爪的黑方块所代表的接线柱就是用来指定哪一个CPU或CPU内核的。


图3:一个时间数序结构


图2:时间顺序结构的输入配置面板

    这个CPU设置可以在配置面板(图2)中静态的指定好,也可以像图1这样,在程序运行时指定。执行图1所示的程序,在0和1之间切换结构内代码运行的CPU,就可以在系统监视器中看到指定的CPU被占用的情况了。

    还是以刚才那段程序为例,这一次我手工为每个任务指定他们运行的 CPU。


图4:手工指定每个任务运行的 CPU

    这样一来,两个耗时较少的任务占用同一个 CPU,耗时较多的任务单独占用一个 CPU。不同 CPU 被分配到的任务比较均衡,程序整体运行速度大大加快,如图5所示:


图5:两个CPU负载均衡

阮奇桢

《我和 LabVIEW》

几种简单的测试程序流程模型

    大多数测试程序主要步骤就是以下几步:采集数据、处理数据、显示数据、保存数据。这几个步骤可以顺序执行。在一次实验中,通常要多次循环这一过程,因此,这种测试程序的模型如图1所示。图1中最后一个子 VI 是用来判断实验是否结束,是否进行下一次循环用的。在这个模型中,各个程序模块是单线程顺序执行的,它的好处是程序逻辑简单,容易设计和理解。


图1:顺序测试程序的模型

    但是对于单线程的程序,计算机必须执行完一个任务,才能再进行下一步工作。比如,尽管数据存储是一个相对比较慢的过程,但计算机必须还是要等到它执行完,才能去做下一步的采集数据工作。
    对于速度要求较高的测试程序,最好把这两样工作同时进行,以节约时间。这样,我们可以在两个循环内分别做数据采集,和其它的工作。因为数据采集的速度一般来说高于处理和存储的速度。当新数据被采集来,上次的数据可能还没处理完呢。所以可以先把每次采集到的来不及处理的数据放在缓存里。这种模型如图2所示。它实质上也就是 LabVIEW 在新建VI的模板中的“Producer/Consumer Design Pattern”。
    这个模型的实际应用程序会更加复杂,相比第一个模型不是那么好理解和维护。

图2:数据采集和后续工作并行执行的模型

    不过还有一个折衷的方案,既保证各个任务同时运行,又不至于太复杂。如图3所示,在这个模型下,所有的任务同时运行:采集新的数据、处理上一次采到的数据,显示保存上一次处理好的数据。在这个模型下,要注意第一次循环运行时处理的数据,和循环头两次运行显示存储的数据是无效的,实际循环终止条件式也要考虑到,采集的数据再两次循环后才被保存下来。


图3:并行执行每一任务的模型

《我和 LabVIEW》

编辑

基因对人的影响

    最近在读《心理学》这是一本非常厚的书,估计要读上一阵子。这几天读的章节是进化心理学和行为心理学。里面列举了一些调查结果,和我曾经想像的还是有很大差别的。

    以前读《魔鬼经济学》的时候,里面有一个问题:
    孩子的学习和下面两个因素中的哪一个密切相关?一、父母经常体罚孩子;二、孩子是领养的。
    我当时觉得应该是第一个因素,而实际的调查结果,在父母从不或经常体罚孩子的家庭中,都有孩子学习好或不好。就平均成绩而言,这种两种家庭里的孩子们相差无几;而如果孩子是领养的,不论孩子自己是否知道,虽然他们当中也有优秀者,但他们的平均学习成绩却比一般家庭的孩子有明显差距。《魔鬼经济学》对此的解释是,基因对孩子学习成绩的影响远大于环境的影响。被收养的孩子,他们的亲生父母平均智商比较低(否则也不至于遗弃自己的孩子)。所以这些孩子的遗传因素相对较差,导致他们的学习成绩也较差。
    从感情上说,这个结论是让我比较难以接受的。我宁愿相信每个人生下来的时候差距并不大,后天的环境影响才是塑造他们人生的主要因素,而父母对孩子的影响是最大的。

    这本《心理学》上列举的一些调查结果,竟然与上面提到的问题不谋而合。其中专门有一章讨论了父母对孩子的影响有多大,稳中引用的调查结果完全不像我预期的那样。
    书中主要提及的实验是对同卵双生子(两个人的基因完全相同)和一般兄弟姐妹(基因有所不同)之间异同点的比较。同卵双生子的两个人总是非常相似的,包括他们的智商、人格、性情、喜好等等方面。即便是从小就分离,在不同环境相长大的同卵双生子,他们之间还是有着惊人的相似。书中的一个例子是一对孪生兄弟,他们刚出生就分开了,一个被纳粹分子抚养长大,一个被一户犹太人收养。70多年后重逢,发现两个人的脾气、秉性、爱好等等都极为相似。
    而一般同一个家庭长大的兄弟或姐妹,他们虽然生活的环境完全相同,但性格却可以完全相反。一般兄弟姐妹之间的差距,并不比随机抽取两个陌生人之间的差距更小。这是因为,父母并不能主导对孩子的影响;教养这一过程,是由孩子和父母共同完成的。
    比如说,父母同样委婉的劝说两个孩子不要做一件事。哥哥也许觉得父母很和蔼,这是父母对自己的关爱;而弟弟可能感觉完全相反,他会觉得父母很霸道,干涉自己的自由。对父母感觉的不同,会导致两人对在同一环境下产生完全不同的反应。这也许正是教育的困难所在吧,没有哪一种教育方式是对多有人都最优的。
    与此相对照。有一对从小就被不同人家领养的同卵双生子兄弟,他们都非常爱干净,有轻度的洁癖。研究人员问哥哥,他为什么会比常人更注重整洁。哥哥说:“这很简单啊,我是受了我母亲的影响,她是一个非常爱干净的人。”研究人员问了弟弟同样的问题,弟弟说:“我是受了我母亲的影响,因为我的母亲非常邋遢。”

《生活随笔》

编辑

定时结构

    定时结构是从 LabVIEW 7.1 开始出现的。一眼就能看出来,它在外观与其它结构的风格完全不同。倒是和 LabVIEW 7 力推的 Express VI,风格一致。打开定时结构的函数面板(图1),最上面两个分别是定时循环,和定时顺序结构。下面的是与控制时间结构相关的的一些VI。


图1:时间结构的函数面板

    定时结构,顾名思义,与时间控制有关。LabVIEW 中原本有一些用于延时或定时的函数,比如 Wait, Delay Time 等,他们都位于 Time&Dialog 面板中。利用这些函数,基本可以实现与使用时间结构相同的功能。定时结构的最大改进在于,它可以选择使用哪个时间源(硬件)来定时。尤其是当你的 LabVIEW 程序运行在 RT、FPGA 等设备上时,这一点就特别有用了。使用定时结构指定使用硬件设备上,而不是PC机上的时钟来定时,可以使使运行时序更精准。
    即便同样都是在普通PC上使用,定时间结构的定时效果也要比 Wait 等函数精确的多。我曾经参与过的一个测试程序,开始使用 Wait 函数定时,运行一小时后,时间误差有几分钟。改用定时结构后,误差缩短到了几秒钟。

 我和 LabVIEW

强迫神经症

     生活中的神经官能强迫行为还是挺常见的,比如说反复洗手,反复查看煤气关没关等等。一般人有这种行为,都算不上病。只有有非常严重的症状表现,才算是“病”了。前些天,在电视上看到了一些病例,比如一个患者:
    检查完煤气关没关。过了一会,又担心自己刚才没检查好,于是重新检查,并且再手上画个圈,表示认真检查过了。可是再过一会,又担心起来了,于是又检查,又画圈。没多一会,手上画满了圈。
    每天穿衣法的顺序必须是一致的,比如先穿秋衣,然后秋裤,然后左脚袜子,然后右脚袜子,然后外裤……。有一天早上穿完衣服,突然觉得似乎刚才穿的顺序不对,于是非常不舒服。只好脱掉衣服,安正确的顺序重穿一遍。穿完又有点不放心,于是再脱,再穿。
    从宿舍到教室的路上有一个转弯,每次走到这里都必须走出一个90度直角转弯。某天走到这里溜号了,走过去突然觉得刚才的转弯不够标准,于是再回去重新转个90度的弯……

    强迫症的病因也很多,比如遗传、胆小,没有安全感、极度自卑、对自己要求过于苛刻、精神紧张等等,都可能引起强迫症。我现在在琢磨,这病说不定也会“传染”。

    我是个比较大大咧咧,没啥远大抱负的人。像我这种人是肯定得不上强迫症的。不过我老婆平时强迫行为倒是很多,这可能跟她所处的环境有关。她本来是个有点洁癖特别爱干净的人,而现在却不得不每天接触很多有毒的化学试剂和放射性物质;加上她现在读的博士,毕业难度非常大。心情一紧张,强迫行为就表现出来了。
    比如她每次出门都要检查好几遍水电煤气等关没关。如果我在场,就一定会让我给她复查一遍。每天晚上给我打电话,必问的问题之一就是煤气关了没?水关了没?煤气灶上的阀门关了没?总阀关了没?厨房水龙头关了没?厕所水龙头关了没…… 如果我记不清了,就必须拿着电话去查看。
    为了每天晚上不必从被窝里爬出来去看煤气,我只好睡觉前自己检查好了。有时候一时想不起来刚才检查过没有,就得再重查一遍。我这强迫行为也出来了。

    今天早上出了门,一溜号,突然想不起来有没有锁门了。于是只好掏出钥匙,开了门再重锁。

《生活随笔》

编辑