LabWindowCVI中的多线程技术

多线程,多任务和多处理这些术语经常被交替使用,但是他们在本质上不同的概念,多任务指的是操作系统具有在任务间快速切换使得这些任务看起来是在同步执行的能力。在一个抢占式多任务系统中,应用程序可以随时被暂停。使用多线程技术,应用程序可以把它的任务分配到单独的线程中执行。在多线程程序中,操作系统让一个线程的代码执行一段时间后会切换到另外的线程继续运行,暂停某个线程的运行而开始执行另一个线程的行为被称为线程切换。通常情况下,操作系统进行线程切换的速度非常快,令用户觉得有多个线程在同时运行一样。多处理指的是在一台计算机上使用多个处理器。在对称式多处理(SMP)系统中,操作系统自动使用计算机上所有的处理器来执行所有准备运行的线程。借助多处理的能力,多线程应用程序可以同时执行多个线程,在更短时间内完成更多的任务。

 

1.进行多线程编程的原因

在程序中使用多线程技术的原因主要有四个,最常见的原因是多个任务进分割,这些任务中的一个或多个是对时间要求严格的而且易被其他任务的运行所干涉。例如,进行数据采集并显示用户界面的程序就很适合使用多线程技术实现。在这种类型的程序中,数据采集是时间要求严格的任务,它很可能被用户界面的任务打断。在LabWindows/CVI程序中使用单线程方法时,程序员可能需要从数据采集缓冲区读出数据并将它们显示到用户界面的曲线上,然后处理事件对用户界面进行更新,当用户在界面上进行操作时,线程将继续处理用户界面而不能返回到数据采集任务,这将导致数据采集缓冲区的溢出。而LabWindow/CVI程序中使用多线程技术时,程序员可以将数据采集操作放在一个线程中,而将用户界面处理放在另一个线程中,这样,在用户对界面进行操作时,操作系统将进行线程切换,为数据采集线程提供完成任务所需的时间。

 

在程序中使用多线程技术的第二个原因是程序中可能需要同时进行低速的输入输出操作。例如,使用仪器来测试电路板的程序将从多线程技术中获得显著的性能提升。在LabWindows/CVI程序中使用单线程技术时,程序员需要从串口发送数据,初始化电路板,程序需要等待电路板完成操作之后,在去初始化测试时,你可以使用另一个线程来初始化测试仪器,这样,在等待电路板初始化的同时等待仪器初始化,低速的输入/输出操作同时进行,减少了等待所需要的时间开销。

 

在程序中使用多线程技术的第三个原因是借助多处理器计算机来提高性能。计算机上的每个处理器可以都执行一个线程。这样,在单处理器计算机上,操作系统只是使多个线程看起来是同时执行的,而在多处理器计算机上,操作系统才是真正意义上同时执行多个线程的。例如,进行数据采集、将数据写入磁盘、分析数据并且在用户界面上显示分析数据,这样的程序很可能通过多线程技术和多处理器计算机运行得到性能提升。将数据写到磁盘上和分析用于显示的数据是可以同时执行的任务。

 

在程序中使用多线程技术的第四个原因是在多个环境中同时执行特定的任务。例如,程序员可以在应用程序中利用多线程技术在测试舱进行并行化测试。使用单线程技术,应用程序需要动态分配空间来保存每个舱中的测试结果。应用程序需要手动维护每个记录及其对应的测试舱的关系。而使用多线程技术,应用程序可以创建独立的线程来处理每个测试舱。然后,应用程序可以使用线程局部变量为每个线程创建测试结果记录。测试舱与结果记录间的关系是自动维护的,使应用程序代码得以简化。

 

 

2 LabWindows/CVI的多线程技术简介

Thread pools 帮助用户将函数调度到独立的线程中去执行,处理线程缓存来最小化与创建和销毁线程相关的开销

Thread-safe queues 对线程间的数据传递进行了抽象。一个线程可以在另一个线程向队列写入数据的同时,从队列中读取数据

Thread-safe variables 高效地将临界代码段和任意数据类型结合在一起,用户可以调用简单的函数来获取临界代码段,设定变量值,然后释放临界代码段

Thread locks 提供了一致的API并在必要时自动选择合适的机制来简化临界代码和互斥量的使用。例如,如果需要在进程间共享互斥锁,或者线程需要在等待锁的时候处理消息。CVI会自动使用互斥量。

Thread-local variables 为每个线程提供变量实例,操作系统对每个进程可用的线程局部变量的数量进行了限制。CVI在实现过程中对线程局部变量进行了加强,程序中所有的线程局部变量只使用一个进程变量

 

在LabWindows/CVI的辅助线程中运行代码

单线程程序中的线程被称为主线程。在用户告诉操作系统开始执行特定的程序时,操作系统将创建主线程。在多线程程序中,除了主线程外,程序还通知操作系统创建其他的线程。这些线程被称为辅助线程。主线程和辅助线程的主要区别在于它们开始执行的位置。操作系统从main或者WinMain函数开始执行主线程,而开发人员来指定辅助线程开始执行的位置。

在典型的LabWindows/CVI多线程程序中,开发者使用主线程来创建、显示和运行用户界面,而使用辅助线程来进行其他时间要求严格的操作,如数据采集等。LabWindows/CVI提供了两种在辅助线程中运行代码的高级机制,这两种机制是线程池和异步定时器。线程池适合与执行若干次的或者一个循环内执行的任务。而异步定时器适合于定期进行的任务。

 

使用线程池

为了使用CVI的线程池在辅助线程中执行代码,需要调用Utility Library中的CmtScheduleThreadPoolFunction函数。将需要在辅助线程中运行的函数名称传递进来。线程池将这个函数调度到某个线程中去执行,根据配置情况和当前的状态,线程池可能会创建新的线程来执行这个函数,也可能会使用已存在的空闲进程执行函数或者会等待一个或者会等待一个活跃的线程变为空闲然后使用该线程执行预定的函数。传递给CmtScheduleThreadPoolFunction的函数被称为线程函数。线程池中的线程函数可以选择任意的名称,但是必须遵循以下原型

int CVICALLBACK ThreadFunction(void *functionData);

下面的代码显示了如何使用CmtScheduleThreadPoolFunction函数在辅组进程中执行一个数据采集的线程

int  CVICALLBACK DataAcqThreadFunction(void *functionData);

int main (int argc,char *argv[])
{

  int panelHandle;

  int functionId;

  if(InitCVIRTE (0,argv,0)==0)
     
    return -1;

  if((panelHandle=LoadPanel(0,"DAQDisplay.uir",PANEL))<0)

    return -1;

  DisplayPanel(panelHandle);

           CmtScheduleThreadPoolFunction(DEFAULT_THREAD_POOL_HANDLE,DataAcqThreadFunction,NULL,&functionId);


  RunUserInterface();

  DiscardPanel(panelHandle);


  CmtWaitForThreadPoolFunctionCompletion(DEFAULT_THREAD_POOL_HANDLE,functionId,0);

  return 0;

}



int CVICALLBACK DataAcqThreadFunction(void *functionData)

{

    while(!quit)

    {

        Acquire(....);
        Analyze(....);

    }

    return 0;
}













 

在前面的代码中,主线程调用了CmtScheduleThreadPoolFunction函数,使线程池创建了一个新的线程来运行DataAcqThreadFunction线程函数。主线程从CmtScheduleThreadPoolFunction函数返回,而无须等待DataAcqThreadFunction函数完成。在辅助线程中的DataAcqThreadFunction函数与主线程中的调用是同时执行的。

CmtScheduleThreadPoolFunction函数的第一个参数表示用于进行函数调度的线程池。CVI的Utility Library中包含了内建的默认线程池。传递常数DEFAULT_THREAD_POOL_HANDLE表示用户希望使用默认的线程池。但是用户不能对默认线程池的行为进行自定义。用户可以调用CmtNewThreadPool函数来创建自定义的线程池。CmtNewThreadPool函数返回一个线程池句柄,这个句柄将作为第一个参数传递给CmtScheduleThreadPoolFunction函数。程序员需要调用CmtDiscardThreadPool函数来释放由CmtNewThreadPool函数创建的线程池资源。

CmtScheduleThreadPoolFunction函数中最后一个参数返回一个标识符,用于在后面的函数调用中引用被调度的函数,调用CmtWaitForThreadPoolFunctionCompletion函数使得主线程等待线程池函数结束后再退出。如果主线程在辅助线程完成之前退出,那么可能会造成辅助线程不能正确地清理分配到资源。这些辅助线程使用的库也不会被正确的释放掉。

 

线程安全队列

使用CVI  Utility Library 的线程安全队列,可以在线程间安全地传递数据。当需要用一个线程来采集数据而用另一个线程来处理数据时,这种技术非常有用。线程安全队列在其内部处理所有的数据锁定。通常来说,应用程序中的辅组线程获取数据,而主线程在数据可用时读取数据然后分析并/或显示数据。下面的代码显示了数据如何使用线程安全队列将数据传递到另一个线程,在数据可用时,主线程利用回调函数来读取数据。

 

int queue;

int panelHandle;

int main(int argc, char *argv[])
{

     if(InitCVIRTE(0,argv,0)==0)

     {
        
        if(InitCVIRTE(0,argv,0)==0)
          
         return -1;

        if((panelHandle=LoadPanel(0,"DAQDisplay.uir",PANEL))<0)
        
         return  -1;

        CmtNewTSQ(1000,sizeof(double),OPT_TSQ_DYNAMIC_SIZE,&queue);

        CmtInstallTSQCallback (queue,EVENT_TSQ_ITEMS_IN_QUEUE,500,QueueReadCallback,0,CmtGetCurrentThreadID(),NULL);
            
        CmtScheduleThreadPoolFunction(DEFAULT
    
     }

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

·

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值