首页 >> 新闻
 

在Windows操作系统上
以异步模式调用同步的
Dialogic® Dialogic® 函数的
解决方案

 
  • 概述
  • 绪言
  • 问题
  • 解决方案
    线程池
    为什么使用线程池
    Windows*线程池服务
    Windows线程池服务特性
    处理同步函数
    向应用程序发送通知
    平台无关性
  • 应用实例
    应用场景
    应用程序初始化
    调用同步函数
    异步方式调用同步函数
    应用程序标准运行时事件循环
  • 结论

概 述

  本应用手册描述了在Windows操作系统上以异步方式调用Dialogic® Dialogic®同步函数的解决方案。首先讲解线程及线程池,然后介绍一个会议应用的解决方案。

绪 言

  本文档介绍的方案普遍适用于任何一种耗时的,在完成之前阻塞其调用线程的函数。该方案在多线程的应用环境里利用Dialogic的标准运行时库(SRL)进行事件管理。你可以使用任何事件管理机制来替代SRL,但是SRL提供了一个标准的方法来管理Dialogic?系统中的事件。

  想了解更多关于SRL的信息,请访问:
Voice Software Reference: Standard Runtime Library for Windows at
http://www.Dialogic.com/

  请注意在试用本应用手册里介绍的方案之前,建议读者对同步函数的多线程安全性进行测试。本文所述方案仅适用于线程安全的同步函数。

问 题

  绝大多数Dialogic Dialogic函数允许调用者选择一种函数调用方式,分别是EV_SYNC(同步方式)和EV_ASYNC(异步方式)。但是有些函数只提供一种同步接口。同步函数在执行过程中会阻塞调用它的线程或整个单线程的应用程序,直到函数返回。这种局限性成为了应用开发者非常关心的问题,特别是在高密度的系统中,某些同步函数非常耗时。这种耗时的函数限制了应用程序继续处理其它的逻辑,而只能等待同步函数返回。

图 1. 同步和异步操作模式

解决方案

  使用多线程可以异步的执行同步函数。创建一个新的线程或使用一个已经创建的线程作为工作线程,用来调用耗时的同步函数并等待其执行和返回,而应用线程可以继续处理后面的逻辑。当同步函数返回时,工作线程发布一个SRL事件(稍后说明)给应用线程,根据同步函数的返回值传递相应的返回码。

  一种方法是预先创建一个工作线程池等待为主应用程序服务。这样可以避免创建和结束动态线程的负荷。此时,线程池里的某个线程可能专门用于为池里的其它可用线程分配工作。

  下面的图表阐述了这种解决方案的原理和好处。

  线程池

  本节介绍如何利用线程实现本篇应用手册中建议的解决方案。

  为什么使用线程池

  如前所述,使用额外的线程可以让主应用线程不必去等待同步函数的执行。也就是说,应用程序可以继续处理它的逻辑,而不会被执行中的同步函数阻塞。

  但是如果额外的线程是在要调用同步函数的时候创建的,应用程序就要花费操作系统所需要的时间去创建这个线程。更好的解决方法是在应用程序初始化时预先创建一组工作线程,当应用程序需要时可以利用它们调用任何的同步函数。这个池里的工作线程需要由"某个人"来管理,为它们分派工作并保持负载平衡。因此在一个有"n"个工作线程的池里,你可以指定第一个线程作为主线程或者控制线程,其它?quot;n-1"个线程作为用来调用同步函数的工作线程。

  Windows*线程池服务

  Windows*本身就提供了由操作系统来进行管理的线程池技术。它根据应用程序的需要动态的创建和删除线程。可创建的线程数仅受到系统内存的限制。

  应用程序通过Windows的函数QueueUserWorkItem来使用线程池库。当应用程序第一次调用这个函数时,Windows会创建线程池。池里至少会有一个线程被用来监测其它的线程,并按照应用程序的需要为它们分配工作任务。

  预了解更多详情,请访问http://msdn.microsoft.com/library ,查找QueueUserWorkItem。

  Windows线程池服务特性

  Microsoft Visual Studio* 6.0版本提供的"winbase.h"里没有包含函数QueueUserWorkItem的定义。开发者需要按如下所示手工定义这个函数,或者在包含Visual Studio头文件之前包含Windows SDK版本的"winbase.h"。

  处理同步函数

  任何同步函数要通过异步方式调用要开发一个开起来类似的函数, 而且和同步函数有相同的参数。

  同步函数:
  long foo(Type1 arg1, Type2 arg2 …, Typen argn)

  异步方式的同步函数:
  long ASYNC_foo(Type1 arg1, Type2 arg2 …, Typen argn);

  按如下所示定义一个结构用来封装函数的参数并传递给线程池。如果任何一个形式参数是指针类型的变量,要确保同步函数在工作线程中执行时该指针是有效的。
  typedef struct tagFOO {
   Type1 arg1;
   Type2 arg2;
   .
   .
   .
   Typen argn;
  } DFOO, *PFOO;

  应用程序按如下所示调用Dialogic函数:
  // asynchronous invocation
  if (invokeAsynchronously)
  {
    rc = ASYNC_foo(arg1, arg2, … , argn);
  }
  // synchronous invocation
  else
  {
    rc = foo(arg1, arg2, … , argn);
  }

    };
    PFOO pvContext = new DFOO (vContext);
    QueueUserWorkItem(THREAD_ASYNC_foo, pvContext,
       WT_EXECUTEINLONGTHREAD);
    return 0;
  }


  THREAD_ASYNC_foo是回调函数,会被Windows线程池服务选择的工作线程调用。

  回调函数的实现方法:

  #define ASYNC_EVENT_FOO 0x10000
  void THREAD_ASYNC_foo(void* pvContext)
  {
    long rc = 0;
    PFOO pContext = (PFOO)pvContext;
    rc = foo(pContext->arg1, pContext->arg2, …, pContext->argn);
    // the synchronous function has returned;
    // post an event to the appln
    sr_putevt(devh, ASYNC_EVENT_FOO, sizeof(rc), &rc, 0);
    delete pContext;
  }


  ASYNC_EVENT_FOO是同步函数foo完成时发布给应用程序的SRL用户事件。实际上foo确实是以同步方式调用的,但是现在仅有线程池中被选中的线程用来等待该函数完成,而应用程序则可以不受限制的继续处理它的逻辑。

  形式参数被封装在一个结构里,其占用内存是在程序片断里动态分配和释放的。在高密度系统中,可以使用缓冲池来预先分配内存从而避免动态分配和释放内存的负荷。

  向应用程序发送通知

  同步函数完成时,工作线程通过SRL提供的事件发布技术通知应用程序。其它的事件管理方法也可以用来达到这一目的。

  SRL主要的功能是提供事件管理的通用接口和所有Dialogic设备的通用功能。SRL负责集中的分发所有Dialogic设备上产生的事件。通过SRL,能够以一种标准的方式处理事件。

  同步函数完成后,THREAD_ASYNC_foo回调函数使用SRL函数sr_putevt向应用程序发布一个预定义的事件。

  // return code
  long rc;
  rc = foo(…);
  sr_putevt(devh, ASYNC_EVENT_FOO, sizeof(rc), &rc, 0);

  应用程序接着使用某个SRL等待函数来等待SRL事件,例如如下所示的sr_waitevt,该函数在指定的时间段内等待任何事件。

  // loop infinitely
  while (1)
  {

  函数ProcessEvents为应用程序管理事件。所有在应用程序的SRL事件循环中接收到的事件都是在这个函数里处理的。该函数仅对接收到的事件进行分析和执行事件相关的处理。

  平台无关性

  本应用手册介绍的解决方案是在Windows上实现的。你可以开发一个平台无关的线程池库,这样应用程序就不用关心底层是什么操作系统了。如果是Windows,这个库可以利用Windows自带的线程池服务;如果是其它的操作系统,可能要自行实现线程池技术。

应用实例

  本应用手册介绍的解决方案是以一个会议应用为实例的。这个应用程序有两个参数。
  模式: 0 = 同步; 1 = 异步
  会议方数

  应用场景

  应用程序创建一个用户提供方数的会议。第一方播放一个文件,其余各方录制该文件。应用程序运行在异步模式演示了异步调用方式。函数dcb_estconf和dcb_addtoconf例证了这种方案。这两个函数是Dialogic R4 API的一部分,它们只能提供同步的调用模式。但是这个应用程序演示了一个开发者如何用异步方式来调用这些函数。

  应用程序初始化

  int main(int argc, char* argv[])
  {
    int iIndex = 0;
    short iIterator = 0; // to access global arrays
    // collect command line parameters
    .
    .
    .
    if (async)
    {
      // initiale thread pool library
      .
      .
      .
    }

  }

  调用同步函数

  每个同步函数foo的异步形势可能是ASYNC_foo。两种函数应该有相同形势的参数。

  if (async)
  {

  }

  异步方式调用时,返回码仅表示这项工作已被成功的传递给工作线程。实际的错误返回码将在同步函数完成时由工作线程发送到应用程序的事件循环里。

  异步方式调用同步函数

  为了完成让工作线程调用同步函数的任务,异步形势的函数必须把函数参数封装在一个结构里,为这个结构分配内存,然后使用Windows API QueueUserWorkItem把一项用户工作发送给线程池库。

  typedef struct tagDCB_ESTCONF {
    int devh;
    MS_CDT* cdt;
    int numpty
    int confattr;
    int* confid;
  } DCB_ESTCONF, *PDCB_ESTCONF;
  long WINAPI ASYNC_dcb_estconf(int devh, MS_CDT* cdt, int numpty,
    int confattr, int* confid)
  {
    DCB_ESTCONF vContext = {
    devh,
    cdt,
    numpty,
    confattr,
    confid

    QueueUserWorkItem(THREAD_ASYNC_dcb_estconf, pvContext,
                   WT_EXECUTEINLONGTHREAD);
    return 0;
  }
  void THREAD_ASYNC_dcb_estconf(void* pvContext)
  {
    long rc = 0; PDCB_ESTCONF pContext = (PDCB_ESTCONF)pvContext;
    rc = dcb_estconf(pContext->devh, pContext->cdt, pContext->numpty,     
                  pContext->confattr, pContext->confid);
    sr_putevt(pContext->devh, DCB_EVENT_ESTCONF, sizeof(rc), &rc, 0); delete pContext;
  }


  应用程序标准运行时事件循环


  下面这段代码提供了本应用手册所讨论的会议应用的事件管理实例。

  void ProcessEvents()
  {
    long ev;
    void* vp;

结 论

  在一个简单的32方会议应用实例的测试运行中,同步模式调用阻塞了应用程序达3545毫秒。与此相反,异步模式仅仅阻塞了应用程序421毫秒。换言之,本应用手册介绍的方案帮助应用程序以相当于同步方式12%的时间处理了同步函数。在大多数应用中,这段宝贵的时间可以用来处理其它的应用逻辑。该测试系统的规范如下:

  • 系统版本: Dialogic Dialogic SR 5.1.1 FP1 for Windows
  • 操作系统: Windows 2000 SP 3
  • 处理器速度: 500 MHz
  • 内存: 128 MB
  • Dialogic Dialogic板卡: DM/V1200A-4E1, DM/V1200-4E1

  与了解更多详情,请访问Dialogic网站http://www.Dialogic.com/.

 


[ 全文英文版 ]

 




融合通信专栏>>技术开发>>

 
 

相关频道:           文摘