LabVIEW中的线程

                             LabVIEW中的线程

. LabVIEW 是自动多线程语言

     一般情况下,运行一个 VILabVIEW 至少会在两个线程内运行它:一个界面线程(UI Thread),用于处理界面刷新,用户对控件的操作等等;还有一个执行线程,负责 VI 除界面操作之外的其它工作。LabVIEW 是自动多线程的编程语言,只要 VI 的代码可以并行执行,LabVIEW 就会将它们分配在多个执行线程内同时运行。

    1 是一个正在运行的简单 VI,它由单独一个一直在运行的循环组成。在此情况下,这个执行循环的线程运算负担特别重,其它线程则基本空闲。在单 CPU 计算机上,这个线程将会占用几乎 100% CPU 时间。图1 中的任务管理器是在一个双核 CPU 计算机上截取的。这个循环虽然在每一个时刻只能运行在一个线程上,但这并不表示他始终不变的就固定在一个线程上。他可能在这个时刻运行在这个线程上,另一时刻又被调度到其他线程上去运行了。(关于这一段,在看完本文第二章:LabVIEW 的执行系统,会有更深刻的理解。)

    因此,图1 这个程序最多只能占用两个 CPU 内核 50% 的总 CPU 时间,两个 CPU 内核各被占用一些。

1:双核 CPU 计算机执行一个计算繁重的任务

 

    2 是当程序有两个并行的繁重计算任务时的情况,这时 LabVIEW 会自动把两个任务分配到两个线程中去。这时即便是双核 CPU 也会被 100% 占用。

2:双核 CPU 计算机执行两个计算繁重的任务

 

    从上面的例子,我们可以得出如下两个结论。

    1. LabVIEW 上编写多线程程序非常方便,我们应该充分利用这个优势。一般情况下,编写程序时应当遵循这样的原则:可以同时运行的模块就并排摆放,千万不要用连线,顺序框等方式强制它们依次执行。在并行执行时, LabVIEW 会自动地把它们安排在在不同线程下同时运行,以提高程序的执行速度,节省程序的运行时间。今后多核计算机将成为主流配置,多线程的优势会更为明显。

    特殊的情况也是有的,即用多线程时,运行速度反而慢。 以后我们再来详细介绍此类特殊情况。

 

   2. 假如有一个或某几个线程占用了 100% CPU,此时系统对其他线程就会反应迟钝。例如,程序的执行线程占用了100% CPU,那么用户对界面的操作就会迟迟得不到响应,甚至于用户会误认为程序死锁了。所以在程序中要尽量避免出现 100% 占用 CPU 的情况。 目前大多数的计算机还是单核单个 CPU 的,因此要避免任何一个线程试图 100% 占用 CPU 的情况(如图1、图2 所示的程序)。

    此类问题最简单的解决方法就是在循环内加一个延时。在图1、图2 的例子中,如果在每个循环内加上 100 毫秒的延时,CPU 占用率就会接近为 0

    对于总运行时间较短的循环(假如CPU 占用总时间不足 100毫秒)就没有必要再加延时了。

    在很多情况下,运行时间很长的循环往往都只是为了等待某一个任务的完成,在此类循环体的内部几乎没有耗时较多的、又有意义的运算,所以必须在循环框内加延时。

 

    对于那些确实非常耗费 CPU资源 的运算(如需要 100% 地占用 CPU 几秒钟甚至更长的时间),最好也在循环内插入少量延时,从而让 CPU 至少 空出 10% 的时间给其它线程或进程。你的程序会因此而多运行 10% 的时间。 但是由于 CPU 可以及时处理其他线程的需求,比如界面操作等,其他后台程序也不会被打断,用户反而会感觉到程序似乎运行得更加流畅。反之,假如你的程序太霸道了,CPU长期被某些运算所霸占,而别的什么都不能做,这样的程序,用户是不可能满意的。

    还有这样一种情况,比如某些运算可能需要程序循环 1,000,000次,每执行一次仅需要 0.1 毫秒。此时如果在每次循环里都插入延时,即使是 1 毫秒的延时,也会令程序速度减慢 10 倍。 这当然是不能容忍的。这种情况下,就不能在每次循环都加延时了,但可以采用每一千次循环后加上 10 毫秒延时的策略。此时,程序仅减慢 10% 左右,而 CPU 也有处理其他工作的时间了。

 

    在处理界面操作的 VI 中,常常会使用到 While 循环内套一个 Event Structure 这种结构形式。在这种情况下,就没有必要再在循环内添加延时了。因为程序在执行到 Event Structure 时,如果没有事件产生,程序不再继续执行下去,而是等待某一事件的发生。这是,运行这段代码的线程会暂时休眠,不占用任何 CPU 资源,一直等到有事件发生,这个线程才会重新被唤醒,继续工作。

 如何使用 VI 的重入属性(Reentrant

    VI Properties -> Execution 中可以选择 VI Reentrant Execution属性(中文译为:可重入执行)。 我们在《LabVIEW 程序的内存优化》一文中讨论过,尽量不要把 VI 设置为重入属性,因为这样就多占用了内存,降低了运行效率。此外,如果不加注意的话,还可能引发多线程不安全的问题。 尽管可重入 VI LabVIEW 中不是必须的,但是在某些情况下使用可重入 VI 可以简化我们的程序。那么在什么情况下可以使用 Reentrant VI 呢?

    首先看一下图 1 所示的程序,程序中调用的两个子 VI 是同一个 VI,并且不是可重入的 VILabVIEW 是自动多线程的语言,那么图中的两个子VI会不会同时执行呢。一定不会的。如果程序中调用的是两个不同的子 VILabVIEW 有可能会同时在不同的线程执行它们,但对于两次调用相同的子 VILabVIEW 一定要等一个执行完,再执行另一个。

1:并行的两个相同子 VI

    其原因是,LabVIEW 会为每个 VI 都开辟一块内存用于数据存储。作为子 VI,每次被调用,它的局部变量的数据都是被存在同一地址的。与 C 语言相对照,在默认情况下,VI 是不可重入的,VI 中所有的局部变量都是静态变量。如果 LabVIEW 在不同的线程下执行同一 VI,那么两个线程就会同时对这一块数据地址进行读写,就会导致这一块地址内数据的混乱。为避免此类不安全情况的出现,LabVIEW 必须等待一个子 VI 执行结束,再执行另一个子 VI

    如果需要图1 中的两个子 VI 同时运行,比如子 VI 所做的工作是读取文件这样一类耗时多、但CPU占用不大的操作,则并行执行可以大大提高效率。这时,就需要把子 VI 设置为可重入了。LabVIEW 在不同的地方调用一个可重入 VI 时,会给它另外分配一个独立的数据地址空间。这样就做到了线程安全。在两个线程执行的子 VI 使用两份在不同的地址存储的数据,也就不会造成混乱。但是千万要注意, 这个在不同的地方调用:不可重入的 VI 的局部变量与 C 语言中非静态变量的含义是不同的。在后面提到的计数器的例子可以验证这一点。

 

    我觉得我说得挺清楚了,出道题目给大家测试一下:

2:延时子 VI

 

3:计算延时的主 VI

    2 是一个子 VI 的代码,功能是延时 1000 毫秒。图3 是主 VI 的代码,并行调用同一子 VI 两次,并计算程序的执行时间。运行主 VItotal time 的值是多少?

    答案在文章最后。

    这是可重入 VI 的一种用途,即希望在不同的线程里同时执行同一个子 VI

    另外还有一种情况下,也可以用到可重入 VI:即需要使用到子 VI 中局部变量保存的数据,而在不同的调用处,这些数据是独立不同的。这句话可能解释得不那么清楚,看下面例子就会比较容易理解些。

4:计数子 VI

 

5:测试计数的主 VI

    4 是一个可重入子 VI 的代码,功能是计算这个VI被运行的次数,每运行一次,输出的 count 值就增加1。图5 是调用它的主VI,用于演示这个计数器。执行主VI一次,output 1 output 2 的值分别是 10 20,表示这个子 VI 在两处分别被调用了 10 次和 20 次。

    如果把图 4 中的 VI 改为不可重入,则 output 1 output 2 的输出值是不确定的。大家可以自己试一试,再想一下原因。

     测试题目答案:如果图2的子 VI 没有设置为可重入,则 total time = 2000;如果设置为可重入则 total time = 1000

 

二、LabVIEW 的执行系统

1. 什么是执行系统

    早期 LabVIEW VI 都是单线程运行的,LabVIEW 5.0 后才引入了多线程运行。其实,对于并排摆放的LabVIEW 函数模块而言,即使LabVIEW 不为它们分配不同的线程,通常也是并行执行的。LabVIEW 会把它们拆成片断,轮流执行:这有一点像是 LabVIEW 为自己设计了一套多线程调度系统,在系统的单个线程内并行执行多个任务。
    LabVIEW
中这样一套把 VI 代码调度、运行起来的机制叫做执行系统。现在的 LabIVEW 有六个执行系统,分别是:用户界面执行系统、标准执行系统、仪器I/O执行系统、数据采集执行系统、以及其他1、其他2系统。一个应用程序中使用到的众多子 VI 可以是分别放在不同的执行系统里运行的。用户可以VI 属性面板上选择 Execution 页面,可以在这个页面指定或更改某个 VI 的首选执行系统。

2. 执行系统与线程的关系

    LabVIEW 在支持多线程以后,不同的执行系统中的代码肯定是运行在不同线程下的。用户界面执行系统只有一个线程,并且是这个程序的主线程。 这一点与其他执行系统都不一样,其他的执行系统都可以开辟多个线程来执行代码。用户除了可以设置 VI 的执行系统,还可以设置它的优先级。优先级分 5 个档次(暂先不考虑 subroutine)。在 LabVIEW 7.0 之前, LabVIEW 在默认情况下为同一个执行系统下每个档次的优先级开启一条独立的线程;而在LabVIEW 7.0 之后,LabVIEW 在默认会默认的为每个执行系统下每个档次的优先级开启 4 条线程。当然你使用 /vi.lib/Utility/sysinfo.llb/threadconfig.vi 可以更改这一设置。但是对于普通用户来说最好不要改动它。
   
在用 C 语言编写多线程程序时,你还要注意不能开辟太多的线程,因为线程开辟、销毁、切换等也是有消耗的。线程太多可能效率反而更差。但是使用 LabVIEW 就方便多了。在使用默认设置的情况下,LabVIEW 最多为你的程序开辟 5 条线程:一条用户界面线程,四条标准执行系统标准优先级下的线程。五条线程不会引起明显的效率损失。

3. 用户界面执行系统

    程序中所有与界面相关的代码都是放在用户界面执行系统下执行的。就算你为一个 VI 设置了其他的执行系统,这个 VI 的前面板被打开后,他上面的数据更新的操作也会被放在用户界面执行系统下运行。还有一些工作,比如利用 Open VI Reference 节点动态的把一个 VI 加载到内存的工作,也是在用户界面执行系统下运行的。
   
前面提到了,用户界面执行系统一个最特殊的执行系统,因为它只有一个线程(我们就给这个线程起名叫用户界面线程好了)。LabVIEW 一启动,这个线程就被创建出来了,而其他执行系统下的线程只有在被使用到时才会被 LabVIEW 创建。

    在图1 中的例子中,如果是运行在其他的线程下,都会把我的双核 CPU 占满。原因参考本文第一章(LabVIEW 是自动多线程语言)的图2。但是如果我们把 VI 的执行系统改为用户界面执行系统,那么这两个循环就会运行在同一线程下,我的双核 CPU 其中一个核将被占用 100%,另一个则基本空闲。

    2 VI 在运行过程中的一幅截图,虽然程序在单线成下运行,两个循环仍然是并行运行的,两个显示控件的数据会交替增加。

    
12:在界面线程-单线程下运行的并行任务

    因为 LabVIEW 是自动多线程的,如果一些模块不能保证多线程安全,就需要把他们设定为在用户界面线程运行。这样就等于强制他们在同一个线程下执行,以保证安全。具体例子在下一节讨论。

4. 其他几个执行系统

    执行系统一栏还有其他几个条目可选。
    “same as caller”
是默认选项,它表示这个 VI 沿用调用它的上层 VI 设置的执行系统。如果顶层 VI 也选择“same as caller”,那么就等于它选择了标准执行系统。
    “standard”
标准执行系统是最常用的配置方式。
    “Instrument I/O”
仪器I/O执行系统一般用于发送命令到外部仪器,或从仪器中读取数据。这是程序中较为重要的操作,需要及时运行。所以仪器I/O执行系统中的线程的优先级比其他执行系统中的线程要高一些。
    “data acquisition”
数据采集执行系统一般用于快速数据采集。数据采集执行系统中的线程的数据堆栈区比较大。
    “other 1”
“other 2”其他1、其他2执行系统没什么特别之处。如果你一定要让某些 VI 运行在独立的线程内,则可以使用这两个选项。
   
绝大多数情况下,用户使用界面执行系统、标准执行系统就已经足够了。

  • 1
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值