ABAP多线程程序优化

转载出处:https://blog.csdn.net/wangjolly/article/details/8949754

实际项目实施过程中,我们会遇到程序性能优化的问题,这里介绍一种方法:通过RFC接口进行远程函数的异步调用实现程序的并行处理。

同步/异步调用函数语法:

同步调用:CALL FUNCTION '函数名' ;

同步调用的实质:程序进行单线程执行。

异步调用:CALL FUNCTION '函数名' STARTING NEWTASK <taskname> "任务名称

                  DESTINATION IN GROUP <RFC Serve Group>

                  PERFORMING <subroutine> ON END OF TASK。"子程序

异步调用的实质:程序进行多线程执行。

 

一些关于函数异步调用实现程序并行处理的文章,没有介绍如下问题

⒈ 为了避免相同程序重复运行产生的后台任务相互冲突,需要保证在相同时间段同一程序只被一个用户占用;

⒉ 异步调用获取的最终结果数据与同步调用获取的结果存在差异;

⒊ 固定RFC Server Group如system = 'parallel_generators',无法保证程序在不同服务器中通用性。

 

问题1分析:从MD01中运行MRP我们可以知道,系统为了避免相同程序并发执行,导致后台任务冲突, MD01在并行模式下是不允许被两个用户同时执行的。如下图

解决方法:通过在程序中利用锁对象来达到程序相同时间段只被同一用户占用的目的。

 

问题2分析:在LOOP循环中采用异步调用函数的模式,通过SY-SUBRC = 0来判断任务启动成功,当SY-SUBRC <> 0时,则获取先前启动的进程返回的值,但是这样就遇到一个问题:如第N次循环正好分配给程序的进程被占用完,这样本次无法启动一个任务进程,导致本次的原始数据通过函数无法获取目标,从而最终结果出现数据不完整和数值不断变化的现象。

解决方法:牺牲部分性能保证数据的完整。通过RZ12获取服务器的Max. requests in queue 的值,LOOP循环的时候统计启动的启动的进程数是否 = Max. requests inqueue,如果等于则获取先前启动的进程返回的值,然后再重新启动进程,重复此操作。系统分配给每个程序的最大进程数> Max. requests in queue,但是把启动的进程数限制在Max.requests in queue的水平可以保证获取结果的完整性。如下图所示

 

问题3分析:一般系统直接指定<RFC Serve Group> =' parallel_generators ',如上图的“服务器组”对应的内容,为了保持一般性通过如下逻辑段获取

    "获取 RFC Serve Group name----------->开始
    "一般系统默认g_classname = 'parallel_generators',但为了通用性按照如下方法获取
    CALL 'C_SAPGPARAM'                                    "#EC CI_CCALL
      ID 'NAME'  FIELD 'rdisp/myname'
      ID 'VALUE' FIELD gv_applserver.

    SELECT SINGLE classname INTO gv_classname   "Server Group Name
           FROM rzllitab
           WHERE applserver = gv_applserver
           AND grouptype = 'S'.   "S:服务器组,空:登陆组

    "获取 RFC Serve Group name----------->结束

 

下面通过例子来说明函数异步调用,这里使用函数'MD_STOCK_REQUIREMENTS_LIST_API'获取MD04中的物料+工厂 MRP数据明细。首先我们使用同步调用的方法,然后再使用异步调用的方法,比较二者在同等条件下的执行效率。

同步调用:

************************************************************************
* 程 序 名:ZTEST_TB
* 程序描述:同步调用函数获取MRP明细列表
* 事务代码:
************************************************************************
* 修改日志
************************************************************************
* 日期     版本 修改人       描述
* -------- ---- ------------ -------------------------------------------
* 20230908 1.0  Amell        创建程序
*
************************************************************************
REPORT ztest.

************************************************************************
* Tables Definitions
************************************************************************
TABLES: marc.
************************************************************************
* Data Definitions                定义数据
************************************************************************
TYPES: BEGIN OF  ty_marc,
         werks TYPE marc-werks,
         matnr TYPE mara-matnr,
       END OF ty_marc.

DATA: BEGIN OF ty_data.
    INCLUDE STRUCTURE mdez.
DATA: werks TYPE marc-werks,
      matnr LIKE mara-matnr.
DATA: END OF ty_data.

DATA: gt_marc TYPE TABLE OF ty_marc,
      gt_data LIKE TABLE OF ty_data,
      gt_mdez TYPE TABLE OF mdez.

DATA: gs_layout   TYPE lvc_s_layo, "布局
      gt_fieldcat TYPE lvc_t_fcat. "字段

************************************************************************
* Includes Module                 包含模块
************************************************************************

************************************************************************
* Selection Screen                选择屏幕
************************************************************************
PARAMETERS: p_werks LIKE marc-werks DEFAULT '2000' OBLIGATORY.
SELECT-OPTIONS: s_matnr FOR marc-matnr.

************************************************************************
* Initialization                  初始化事件
************************************************************************
INITIALIZATION.

************************************************************************
* At Selection Screen             PAI事件
************************************************************************
AT SELECTION-SCREEN.

************************************************************************
* At Selection Screen Output      PBO事件
************************************************************************
AT SELECTION-SCREEN OUTPUT.

************************************************************************
* Report Format                   报表格式
************************************************************************
TOP-OF-PAGE.

END-OF-PAGE.

************************************************************************
* Main Process                    主要逻辑
************************************************************************
START-OF-SELECTION.

  "获取数据
  PERFORM frm_get_data.

  "显示数据
  PERFORM frm_display_data.

END-OF-SELECTION.


*&---------------------------------------------------------------------*
*& Form FRM_GET_DATA
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_get_data .

  DATA: ls_marc TYPE ty_marc,
        ls_data LIKE ty_data,
        ls_mdez TYPE mdez.

  SELECT marc~werks
         marc~matnr
         UP TO 2000 ROWS
         INTO CORRESPONDING FIELDS OF TABLE gt_marc
         FROM marc
         INNER JOIN mara ON marc~matnr EQ mara~matnr
         WHERE marc~matnr IN s_matnr
         AND marc~werks EQ p_werks
         AND marc~lvorm = ''
         AND mara~mtart = 'HALB'
         AND mara~matnr LIKE 'CT%'
         AND mara~lvorm = ''.

  SORT: gt_marc BY matnr.

    LOOP AT gt_marc INTO ls_marc.

      CLEAR gt_mdez.

      CALL FUNCTION 'MD_STOCK_REQUIREMENTS_LIST_API'
        EXPORTING
          matnr                    = ls_marc-matnr
          werks                    = ls_marc-werks
        TABLES
          mdezx                    = gt_mdez
        EXCEPTIONS
          material_plant_not_found = 1
          plant_not_found          = 2
          OTHERS                   = 3.

      LOOP AT gt_mdez INTO ls_mdez.
        MOVE-CORRESPONDING ls_mdez TO ls_data.
        ls_data-werks = ls_marc-werks.
        ls_data-matnr = ls_marc-matnr.
        APPEND ls_data TO gt_data.
        CLEAR ls_data.
      ENDLOOP.

    ENDLOOP.

ENDFORM.

*&---------------------------------------------------------------------*
*& Form FRM_DISPLAY_DATA
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_display_data .

  "栏位最适宽度
  gs_layout-cwidth_opt = 'X'.

  "ALV条纹
  gs_layout-zebra = 'X'.

  "构建ALV的栏位
  PERFORM frm_create_field.

  CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY_LVC'
    EXPORTING
      i_callback_program = sy-repid       "当前程序名
      is_layout_lvc      = gs_layout      "Layout
      it_fieldcat_lvc    = gt_fieldcat    "Fieldcat
      i_save             = 'A'
    TABLES
      t_outtab           = gt_data
    EXCEPTIONS
      program_error      = 1
      OTHERS             = 2.
ENDFORM.
*&---------------------------------------------------------------------*
*& Form FRM_CREATE_FIELD
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_create_field .

  DATA: ls_fieldcat TYPE lvc_s_fcat,
        lv_count    TYPE i.

  DEFINE fieldcat.
    ADD 1 TO lv_count.

    "栏位显示顺序
    ls_fieldcat-col_pos    = lv_count.
    "内表栏位
    ls_fieldcat-fieldname  = &1.
    "参考栏位
    ls_fieldcat-ref_field = &2.
    "参考表
    ls_fieldcat-ref_table = &3.
    "单位
    ls_fieldcat-qfieldname = &4.
    "栏位标题(长文或中等文或短文)
    ls_fieldcat-scrtext_s = ls_fieldcat-scrtext_m =
    ls_fieldcat-scrtext_l = &5.
    "显示长文或中等文或短文
    ls_fieldcat-colddictxt = &6.

    APPEND ls_fieldcat TO gt_fieldcat.
    CLEAR ls_fieldcat.
  END-OF-DEFINITION.

  fieldcat 'WERKS' 'WERKS' 'MARC' '' '' ''.
  fieldcat 'MATNR' 'MATNR' 'MARC' '' '' ''.
  fieldcat 'DAT00' ''      ''     '' '日期' 'L'.
  fieldcat 'DELB0' ''      ''     '' 'MRP元素' 'L'.
  fieldcat 'MNG01' ''      ''     '' '数量' 'L'.


ENDFORM.
View Code

同步调用运行时间:

异步调用:

************************************************************************
* 程 序 名:ZTEST_YB
* 程序描述:异步调用函数获取MRP明细列表
* 事务代码:
************************************************************************
* 修改日志
************************************************************************
* 日期     版本 修改人       描述
* -------- ---- ------------ -------------------------------------------
* 20230908 1.0  Amell        创建程序
*
************************************************************************
REPORT ztest.

************************************************************************
* Tables Definitions
************************************************************************
TABLES: marc.
************************************************************************
* Data Definitions                定义数据
************************************************************************
TYPES: BEGIN OF  ty_marc,
         werks TYPE marc-werks,
         matnr TYPE mara-matnr,
       END OF ty_marc,
       BEGIN OF ty_task,
         werks        TYPE marc-werks,
         matnr        TYPE mara-matnr,
         taskname(10) TYPE c,
       END OF ty_task.

DATA: BEGIN OF ty_data.
    INCLUDE STRUCTURE mdez.
DATA: werks TYPE marc-werks,
      matnr LIKE mara-matnr.
DATA: END OF ty_data.

DATA: gt_marc TYPE TABLE OF ty_marc,
      gt_task TYPE TABLE OF ty_task,
      gt_data LIKE TABLE OF ty_data,
      gt_mdez TYPE TABLE OF mdez.

DATA:gv_taskname(10) TYPE c,                   "Task name(同时运行的任务名称必须保持唯一)
     gv_classname    TYPE rzlli_apcl,          "Server Group Name
     gv_applserver   TYPE rzllitab-applserver. "RFC Serve Group

DATA:gv_snd_jobs TYPE i,
     gv_rcv_jobs TYPE i.

DATA: gs_layout   TYPE lvc_s_layo, "布局
      gt_fieldcat TYPE lvc_t_fcat. "字段

************************************************************************
* Includes Module                 包含模块
************************************************************************

************************************************************************
* Selection Screen                选择屏幕
************************************************************************
PARAMETERS: p_werks LIKE marc-werks DEFAULT '2000' OBLIGATORY.
SELECT-OPTIONS: s_matnr FOR marc-matnr.
PARAMETERS: p_pid TYPE n LENGTH 5. "并发进程数(根据RZ12中的最大请求队列数设置)

************************************************************************
* Initialization                  初始化事件
************************************************************************
INITIALIZATION.

************************************************************************
* At Selection Screen             PAI事件
************************************************************************
AT SELECTION-SCREEN.
  "程序锁
  PERFORM frm_lock_program.

************************************************************************
* At Selection Screen Output      PBO事件
************************************************************************
AT SELECTION-SCREEN OUTPUT.

************************************************************************
* Report Format                   报表格式
************************************************************************
TOP-OF-PAGE.

END-OF-PAGE.

************************************************************************
* Main Process                    主要逻辑
************************************************************************
START-OF-SELECTION.

  "获取数据
  PERFORM frm_get_data.

  "解锁程序
  PERFORM frm_unlock_program.

  "显示数据
  PERFORM frm_display_data.

END-OF-SELECTION.

*&---------------------------------------------------------------------*
*& Form FRM_LOCK_PROGRAM
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_lock_program .

  CALL FUNCTION 'ENQUEUE_ES_PROG'
    EXPORTING
      name = sy-repid.
  IF sy-subrc <> 0.
    MESSAGE '程序已被锁定,请稍后执行' TYPE 'S' DISPLAY LIKE 'E'.
  ENDIF.

ENDFORM.

*&---------------------------------------------------------------------*
*& Form FRM_UNLOCK_PROGRAM
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_unlock_program .
  CALL FUNCTION 'DEQUEUE_ES_PROG'
    EXPORTING
      name = sy-repid.
ENDFORM.

*&---------------------------------------------------------------------*
*& Form FRM_GET_DATA
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_get_data .

  DATA: ls_marc TYPE ty_marc,
        ls_task TYPE ty_task,
        ls_data LIKE ty_data,
        ls_mdez TYPE mdez.

  DATA: lv_open_task_num TYPE i.   "启动任务数量

  SELECT marc~werks
         marc~matnr
         UP TO 2000 ROWS
         INTO CORRESPONDING FIELDS OF TABLE gt_marc
         FROM marc
         INNER JOIN mara ON marc~matnr EQ mara~matnr
         WHERE marc~matnr IN s_matnr
         AND marc~werks EQ p_werks
         AND marc~lvorm = ''
         AND mara~mtart = 'HALB'
         AND mara~matnr LIKE 'CT%'
         AND mara~lvorm = ''.

  SORT: gt_marc BY matnr.



  "获取 RFC Serve Group name----------->开始
  "一般系统默认g_classname = 'parallel_generators',但为了通用性按照如下方法获取
  CALL 'C_SAPGPARAM'                                      "#EC CI_CCALL
    ID 'NAME'  FIELD 'rdisp/myname'
    ID 'VALUE' FIELD gv_applserver.

  SELECT SINGLE classname INTO gv_classname   "Server Group Name
         FROM rzllitab
         WHERE applserver = gv_applserver
         AND grouptype = 'S'.   "S:服务器组,空:登陆组

  "获取 RFC Serve Group name----------->结束

  LOOP AT gt_marc INTO ls_marc.
    "生成任务名称 = 'Task' + sy-tabix----------->开始
    WRITE sy-tabix TO gv_taskname.
    CONDENSE gv_taskname.
    CONCATENATE 'Task' gv_taskname INTO gv_taskname.
    "生成任务名称 = 'Task' + sy-tabix----------->结束

    ls_task-taskname = gv_taskname.
    ls_task-werks = ls_marc-werks.
    ls_task-matnr = ls_marc-matnr.
    APPEND ls_task TO gt_task.

    "异步调用函数----------->开始
    CALL FUNCTION 'MD_STOCK_REQUIREMENTS_LIST_API' STARTING NEW TASK gv_taskname
      DESTINATION IN GROUP gv_classname
      PERFORMING frm_subroutine_done ON END OF TASK "子程序
      EXPORTING
        matnr                 = ls_marc-matnr
        werks                 = ls_marc-werks
      EXCEPTIONS
        communication_failure = 1
        system_failure        = 2
        resource_failure      = 3.
    IF sy-subrc = 0.
      gv_snd_jobs = gv_snd_jobs + 1.
    ENDIF.
    "异步调用函数----------->结束

    "记录启动的进程数量
    lv_open_task_num = lv_open_task_num + 1.

    IF lv_open_task_num = p_pid.
      "获取并发进程返回的结果
      WAIT UNTIL gv_rcv_jobs >= gv_snd_jobs.

      CLEAR:lv_open_task_num,gv_rcv_jobs,gv_snd_jobs.
      FREE: gt_task.
    ENDIF.

  ENDLOOP.

ENDFORM.

*&---------------------------------------------------------------------*
*& Form FRM_SUBROUTINE_DONE
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_subroutine_done USING iv_taskname.
  DATA: ls_task TYPE ty_task,
        ls_data LIKE ty_data,
        ls_mdez TYPE mdez.

  gv_rcv_jobs = gv_rcv_jobs + 1.

  CLEAR gt_mdez.
  RECEIVE RESULTS FROM FUNCTION 'MD_STOCK_REQUIREMENTS_LIST_API'
    TABLES
      mdezx                    = gt_mdez
    EXCEPTIONS
      material_plant_not_found = 1
      plant_not_found          = 2
      OTHERS                   = 3.

  LOOP AT gt_mdez INTO ls_mdez.
    MOVE-CORRESPONDING ls_mdez TO ls_data.
    READ TABLE gt_task INTO ls_task WITH KEY taskname = iv_taskname.
    IF sy-subrc = 0.
      ls_data-werks = ls_task-werks.
      ls_data-matnr = ls_task-matnr.
    ELSE.
      DATA(lv_error) = 1.
    ENDIF.

    APPEND ls_data TO gt_data.
    CLEAR ls_data.
  ENDLOOP.

ENDFORM.

*&---------------------------------------------------------------------*
*& Form FRM_DISPLAY_DATA
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_display_data .

  "栏位最适宽度
  gs_layout-cwidth_opt = 'X'.

  "ALV条纹
  gs_layout-zebra = 'X'.

  "构建ALV的栏位
  PERFORM frm_create_field.

  CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY_LVC'
    EXPORTING
      i_callback_program = sy-repid       "当前程序名
      is_layout_lvc      = gs_layout      "Layout
      it_fieldcat_lvc    = gt_fieldcat    "Fieldcat
      i_save             = 'A'
    TABLES
      t_outtab           = gt_data
    EXCEPTIONS
      program_error      = 1
      OTHERS             = 2.
ENDFORM.
*&---------------------------------------------------------------------*
*& Form FRM_CREATE_FIELD
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_create_field .

  DATA: ls_fieldcat TYPE lvc_s_fcat,
        lv_count    TYPE i.

  DEFINE fieldcat.
    ADD 1 TO lv_count.

    "栏位显示顺序
    ls_fieldcat-col_pos    = lv_count.
    "内表栏位
    ls_fieldcat-fieldname  = &1.
    "参考栏位
    ls_fieldcat-ref_field = &2.
    "参考表
    ls_fieldcat-ref_table = &3.
    "单位
    ls_fieldcat-qfieldname = &4.
    "栏位标题(长文或中等文或短文)
    ls_fieldcat-scrtext_s = ls_fieldcat-scrtext_m =
    ls_fieldcat-scrtext_l = &5.
    "显示长文或中等文或短文
    ls_fieldcat-colddictxt = &6.

    APPEND ls_fieldcat TO gt_fieldcat.
    CLEAR ls_fieldcat.
  END-OF-DEFINITION.

  fieldcat 'WERKS' 'WERKS' 'MARC' '' '' ''.
  fieldcat 'MATNR' 'MATNR' 'MARC' '' '' ''.
  fieldcat 'DAT00' ''      ''     '' '日期' 'L'.
  fieldcat 'DELB0' ''      ''     '' 'MRP元素' 'L'.
  fieldcat 'MNG01' ''      ''     '' '数量' 'L'.


ENDFORM.
View Code

异步调用运行时间:

SE30中截取的两张运行时间图,

同步用时83s = 5sABAP程序使用时间 + 78s数据访问消耗时间;

异步用时31s = 30sABAP 程序使用时间 + <1s的数据库访问消耗时间。

本服务器Max.requests in queue = 5. 运行时间83 / 31 = 2.7,即提升了2倍多的执行效率。

posted @ 2023-09-09 10:38  鲸与海  阅读(541)  评论(0编辑  收藏  举报