概述
多線程允許一個設(shè)計良好的程序中的每個任務(wù)可以彼此獨立運行,并且?guī)缀跏峭瑫r運行。在多個 CUP(雙核或四核)上運行多個線程可以改善性能。多線程應(yīng)用程序也可以在單 CPU 系統(tǒng)上工作,但是無法實現(xiàn)提速。
在過去幾年中,我見證了 COBOL 程序的演變過程:最初是在舊的 IBM 主機上的一個單線程中運行,按照現(xiàn)在的標(biāo)準(zhǔn),其速度非常慢,而目前,COBOL 程序可以在更快、更小的機器上的多個線程中運行。COBOL 的創(chuàng)始人,同時也是本文作者的論文導(dǎo)師,海軍少將 Navy Rear Admiral Grace Hopper 應(yīng)該會非常高興看到 IBM COBOL for AIX。
在多線程上運行 COBOL 程序的基本要點就是必須使用 THREAD 編譯器選項對所有 COBOL 程序進行編譯。同一個程序可以有不同的線程,例如,一個線程用于執(zhí)行程序的某個任務(wù),另一個線程用于執(zhí)行同一程序的第二個任務(wù),等等。
需要注意的是,某些 COBOL 應(yīng)用程序依賴于子系統(tǒng)或其他應(yīng)用程序,可能無法使用某些語言元素。這種依賴關(guān)系和語言元素限制約束了 COBOL 程序。通過繞過這些限制,您就可以主動獲得在多線程上運行程序的好處。
在本文中,我將介紹一些解決步驟,以避免被動處理 COBOL 多線程的限制:
- 小心那些不希望使用的語言元素
- 了解語言元素的范圍(scope)
- 為編譯做好準(zhǔn)備
- 編譯、鏈接并運行程序
第一步:小心那些不希望使用的語言元素
在多線程環(huán)境中編寫 COBOL 程序時,選擇合適的鏈接語句和語言元素,包括語句、專用寄存器和構(gòu)成 COBOL 程序邏輯的語句。小心那些無法用于多線程的語言元素。如果使用 THREAD 編譯器選項編譯包含不恰當(dāng)元素的程序,則會將這些元素標(biāo)記為錯誤。
下面的表 1 顯示了不受 THREAD 編譯器選項支持、應(yīng)當(dāng)避免的語言元素。在 Standard COBOL 85 中,這些語言元素被劃分到已作廢的類別中,并且不屬于 Standard COBOL 2002 的一部分。
表 1. 不受 THREAD 編譯器選項支持的語言元素
語言元素 | 類型 |
ALTER | 語句 |
DEBUG-ITEM | 專用寄存器 |
GO TO without procedure name | 語句 |
INITIAL phrase in PROGRAM-ID | 子句 |
RERUN | 子句 |
Segmentation module | 模塊 |
STOP literal | 語句 |
STOP RUN | 語句 |
USE FOR DEBUGGING | 聲明 |
當(dāng)找到這些不需要的語言元素后,可能需要用具有類似功能的元素替換它們,并且 THREAD 編譯器選項支持這些替換的元素。對于某些替換,需要修改程序邏輯或鏈接語句。
下面幾個例子演示了如何處理不受支持的語言元素:
使用 EVALUATE 語句替換 ALTER 語句。已被廢棄的 ALTER 語句提供了與 EVALUATE 相同的功能。然而,EVALUATE 提供了更多、更好的選項,鼓勵使用結(jié)構(gòu)化編程。ALTER 語句修改了 GO TO 語句中指定的轉(zhuǎn)移點 (transfer point)。EVALUATE 語句的功能遠(yuǎn)遠(yuǎn)不止這一點,它還可以充當(dāng)某些條件的一系列 IF 條件語句,并為每個條件指定一個不同的操作。您可以使用 EVALUATE 構(gòu)建一個 case 結(jié)構(gòu)。
如果發(fā)現(xiàn) GO TO 語句缺少對某個程序的引用,那么該程序?qū)o法正確進行編譯。相反,它會使用 PERFORM procedure-name-1 THRU procedure-name-2。如果試圖將 GO TO 語句與程序名一起使用,那么可以考慮將 EVALUATE 語句作為一個替換選項。
STOP RUN 語句關(guān)閉了運行單元內(nèi)的任意程序中定義的所有文件。在線程環(huán)境中,沒有 COBOL 函數(shù)可以有效地執(zhí)行 STOP RUN 功能。如果需要使用這種行為,可以考慮從 COBOL 程序調(diào)用 C exit 函數(shù),并在運行時終止退出后使用 _twzCOBOLTerm。
可以考慮將 GOBACK 或 EXIT PROGRAM 作為另一種終止程序運行的方法。GOBACK 語句指定了所調(diào)用的程序或方法的邏輯結(jié)束部分。這將使您返回到程序的調(diào)用方。當(dāng)從線程中的第一個程序(并不一定總是主程序)中使用 GOBACK 時,該程序?qū)⒔K止。像使用 GOBACK 那樣使用 EXIT PROGRAM,唯一例外的地方是它對主程序無效。
當(dāng)您未對 COBOL 環(huán)境進行預(yù)初始化就執(zhí)行存儲清除 (cleanup) 時,并且 COBOL 運行庫確定運行單元中不存在活躍的 COBOL 程序,那么將在線程的第一個程序中使用 GOBACK 執(zhí)行 COBOL 流程來終止運行單元(包括關(guān)閉所有打開的 COBOL 文件)。如果運行單元內(nèi)的所有 COBOL 程序都通過 GOBACK 或 EXIT PROGRAM 返回給其調(diào)用者,那么就可以確定這一點。
不要使用 CANCEL 語句替換 EXIT PROGRAM 語句。如果 CANCEL 語句指定了一個程序,并且該程序?qū)τ谌我饩程都是活躍的,那么執(zhí)行將失敗。指定的程序必須處于非活躍狀態(tài)。
使用 -g 編譯器選項替代 USE FOR DEBUGGING,提示編譯器為源代碼生成調(diào)試信息。
第二步:在兩個范圍(scope)內(nèi)使用語言元素
由于您的 COBOL 程序在進程中可以作為單獨的線程運行,因此可以在兩個不同的范圍內(nèi)解釋語言元素:運行單元范圍或程序調(diào)用實例范圍。這兩種范圍對于決定引用項目的位置以及項目的存儲周期的長短非常重要。
在一個多線程環(huán)境中,COBOL 運行單元是進程的一部分,包括實際已經(jīng)執(zhí)行 COBOL 程序的線程。當(dāng) COBOL 運行單元處于運行狀態(tài)時,語言元素將保持不變,并且可用于同一線程中的其他程序。這種情況將一直持續(xù)下去,直到任何線程的執(zhí)行堆棧中再也沒有活動的 COBOL 程序。例如,一個已調(diào)用的 COBOL 程序包含 GOBACK 語句(一個語言元素)并向某個 C 程序返回控制權(quán)。在運行單元的范圍內(nèi),COBOL 程序可以調(diào)用非 COBOL 程序,反之亦然。
在線程內(nèi),控制權(quán)在不同 COBOL 和非 COBOL 程序之間轉(zhuǎn)移。例如,一個 COBOL 程序可以調(diào)用另一個 COBOL 程序或 C 程序。每個被單獨調(diào)用的程序就是一個程序調(diào)用實例。這些特定程序的實例可以存在于給定進程的多個線程中。語言元素則僅存在于特定的程序調(diào)用中。
表 2 總結(jié)了可以應(yīng)用于線程的各種 COBOL 語言元素的范圍。它們包括專用寄存器、語句和 DATA DIVISION 部分。
表 2. COBOL 語言元素的范圍
語言元素 | 引用自 | 生命周期 |
ADDRESS-OF 專用寄存器 | 與關(guān)聯(lián)記錄相同 | 程序調(diào)用實例 |
LENGTH OF 專用寄存器 | 與關(guān)聯(lián)的標(biāo)識符相同 | 與關(guān)聯(lián)的標(biāo)識符相同 |
LINAGE-COUNTER 專用寄存器 | 運行單元 | 運行單元 |
LINKAGE-SECTION 數(shù)據(jù) | 運行單元 | 基于底層數(shù)據(jù)的范圍 |
LOCAL-STORAGE 數(shù)據(jù) | 線程 | 程序調(diào)用實例 |
RETURN-CODE | 運行單元 | 程序調(diào)用實例 |
SORT-CONTROL、SORT-CORE-SIZE、SORT-RETURN、TALLY 專用寄存器 | 運行單元 | 程序調(diào)用實例 |
WHEN-COMPILED 專用寄存器 | 運行單元 | 運行單元 |
WORKING-STORAGE 數(shù)據(jù) | 運行單元 | 運行單元 |
專用寄存器
專用寄存器是一些保留字,用于指定編譯器生成的存儲區(qū)域。它們的主要用途是存儲通過特定 COBOL 特性產(chǎn)生的信息。每一個這種存儲區(qū)域都有一個固定的名稱,并且不能在程序內(nèi)部定義它。
確保為專用寄存器提供了足夠的存儲空間。在運行程序時,經(jīng)常清理將要分配給多個線程的存儲空間。
對于使用 THREAD 選項編譯的程序(以及在 PROGRAM-ID 段落中包含 RECURSIVE 屬性的程序),以下專用寄存器的存儲將根據(jù)每個調(diào)用進行分配:
- ADDRESS-OF
- RETURN-CODE
- SORT-CONTROL
- SORT-CORE-SIZE
- SORT-FILE-SIZE
- SORT-MESSAGE
- SORT-MODE-SIZE
- SORT-RETURN
- TALLY
除了 ADDRESS-OF 和 LINAGE-COUNTER 專用寄存器外,其他專用寄存器都可以從運行單元中引用。它們也可以作為運行單元跨越程序的生命周期,并針對每個程序或方法條目重設(shè)初始值。對于某個程序被取消后的第一個調(diào)用,或?qū)τ谀硞方法調(diào)用,編譯器會初始化這些專用寄存器的字段,將它們作為初始值。ADDRESS-OF 專用寄存器中的值集合將僅在特定程序或方法調(diào)用期間存在。
將為每個包含 LINAGE 子句的 FD 條目生成一個 LINAGE-COUNTER 專用寄存器。當(dāng)生成多個專用寄存器時,必須用相關(guān)聯(lián)的文件名指定對 LINAGE-COUNTER 的每個引用。
LINAGE-COUNTER 中的值在任何給定時間都表示設(shè)備在當(dāng)前頁面中的行號?梢栽诔绦騽澐终Z句中引用 LINAGE-COUNTER;但不能用這些語句修改它。
存儲數(shù)據(jù)
在 DATA DIVISION 中,F(xiàn)ILE SECTION 中的所有文件都靜態(tài)分配了存儲空間,并且該空間將一直保留到運行單元結(jié)束。數(shù)據(jù)項的分配方式和初始化方式是各不相同的,這取決于這些數(shù)據(jù)項是在 WORKING-STORAGE SECTION 中還是在 LOCAL-STORAGE SECTION 中。
當(dāng)調(diào)用某個程序后,就會分配與該程序相關(guān)聯(lián)的 WORKING-STORAGE。含有 VALUE 子句的任意數(shù)據(jù)項將在此時被初始化為一個適當(dāng)?shù)闹。在運行單元期間,WORKING-STORAGE 項將一直保持最后一次使用時的狀態(tài)。
每次調(diào)用程序時,LOCAL-STORAGE SECTION 中定義的所有數(shù)據(jù)都將分配存儲空間,并在程序結(jié)束后取消該分配。如果為一個 LOCAL-STORAGE 項指定了一個 VALUE 子句,那么該項在每次調(diào)用時都將被初始化為此值。如果沒有指定 VALUE 子句,則表示沒有定義該項的初始值。LINKAGE SECTION 描述了來自另一個程序的數(shù)據(jù)。
第三步:為編譯做好準(zhǔn)備
要在多個線程中運行 COBOL 程序,必須在應(yīng)用程序中使用 THREAD 編譯器選項對所有 COBOL 程序進行編譯。在同一個應(yīng)用程序中,不能將使用 THREAD 編譯器選項編譯的程序與使用默認(rèn)的 NOTHREAD 編譯器選項編譯的程序混合在一起。
要準(zhǔn)備好編譯帶有多個線程的 COBOL 程序,您需要:
- 清理存儲
- 避免 CICS 環(huán)境
- 采用遞歸調(diào)用
- 使鏈接數(shù)據(jù)可用
清理存儲
如果您的程序初始化了多個 COBOL 線程,不要認(rèn)為 COBOL 程序(例如,您的 C 程序調(diào)用 COBOL 程序執(zhí)行數(shù)據(jù)輸入和輸出)會清理它們的環(huán)境,比如釋放不再使用的存儲空間。
要讓您的應(yīng)用程序控制 COBOL 清理操作,需要預(yù)先初始化 COBOL 環(huán)境?梢酝ㄟ^使用程序的 COBOL 預(yù)先初始化界面實現(xiàn)這一點。在關(guān)閉文件后,您可以為將要分配給多個線程的存儲騰出空間。
避免使用 CICS 環(huán)境
確保您將要在其中編譯程序的環(huán)境不是 CICS 環(huán)境,因為無法在 CICS 環(huán)境中運行多線程應(yīng)用程序。在 CICS 環(huán)境中,您可以在單線程中運行經(jīng)過 THREAD 選項編譯的 COBOL 程序,并且它是不包含多線程或 PL/I 任務(wù)的應(yīng)用程序的一部分。
采用遞歸調(diào)用
要執(zhí)行遞歸調(diào)用,必須在以遞歸方式調(diào)用的程序的 PROGRAM-ID 段落中編寫 RECURSIVE 子句(或?qū)傩裕,或指?THREAD 編譯器選項。由于必須在多線程應(yīng)用程序中將程序編寫為遞歸方式,因此無法編寫嵌套程序。您必須只對編譯單元的最外層程序指定 RECURSIVE 子句。
使鏈接數(shù)據(jù)可用
如果程序包含 RECURSIVE 屬性,或使用 THREAD 編譯器選項編譯,那么后續(xù)的程序調(diào)用將無法訪問 LINKAGE SECTION 中定義的數(shù)據(jù)。要檢索 LINKAGE SECTION 中的記錄,則需要向程序傳遞一個參數(shù),并在該程序的 USING 部分的相應(yīng)位置上指定該記錄。
第四步:編譯、鏈接和運行程序
如果對多線程執(zhí)行還編寫了 C 程序和支持語言環(huán)境的匯編器程序,那么可以在同一個運行單元中將您的多線程 COBOL 程序與這些程序組合在一起。
多線程示例是展示如何使用命令編譯、鏈接和執(zhí)行程序的最佳方法之一。下面的表 3 列出了三個程序例子:一個 C 語言編譯的主程序和兩個 COBOL 程序。
表 3. 將要編譯和運行的程序
程序名 | 描述 |
thrcob.c | 一個 C 程序,創(chuàng)建了兩個 COBOL 線程,等待線程完成并退出 |
subd.cbl | 一個 COBOL 程序,由 thrcob.c 創(chuàng)建的線程運行 |
subd.cbl | 另一個 COBOL 程序,由 thrcob.c 創(chuàng)建的線程運行 |
確保 C 程序具有合理的設(shè)計,否則可能會創(chuàng)建過多的線程(超過兩個),這將占用其他任務(wù)的空間。確保 COBOL 程序不會包含嵌套程序。
在 AIX shell 提示中,它使用三個命令編譯程序,一個命令生成一個可執(zhí)行主程序并運行該程序。
在編譯 COBOL 程序時,可以對需要 THREAD 編譯器的非 CICS 應(yīng)用程序使用 cob2_r 命令。使用 cob2_j 命令可以編譯面向?qū)ο螅∣O)的客戶端和類。該命令包含 THREAD 選項,將它用作默認(rèn)的調(diào)用選項。不要使用 cob2 命令。在生成和運行可執(zhí)行程序時,始終檢查是否存在編譯錯誤。
創(chuàng)建編譯后的程序
下面的清單 1 顯示了需要在 AIX shell 提示中輸入的三行命令,可用它們編譯一個 C 主程序和兩個 COBOL 程序,并假設(shè)源代碼沒有錯誤。
清單 1. 樣例編譯命令
xlc_r -c thecob.ccob2_r -c -g -qTHREAD subd.cblcob2_r -c -g -qTHREAD sube.cbl |
顯然,-c 選項表示編譯程序,但這里沒有將它們鏈接起來。用于 COBOL 程序的命令中的 -q 選項將 THREAD 編譯器選項傳遞給編譯器。-g 選項提示編譯器為源代碼生成調(diào)試信息。
運行可執(zhí)行模塊
完成程序編譯后,下面的清單 2 顯示了兩行命令。第一個命令通過將程序鏈接在一起生成了一個可執(zhí)行模塊。第二個命令將運行該模塊。
清單 2. 樣例編譯命令
cob2_r -o thrcob thecob.o subd.o sube.othecob |
-o 選項用于鏈接程序,它將 thrcob 指定為可執(zhí)行模塊,并通過將 thrcob.o、subd.o 和 sube.o 鏈接在一起生成該模塊。如果沒有使用 -o 選項,那么可執(zhí)行模塊的名稱默認(rèn)為 thrcob.out。
結(jié)束語
要處理 COBOL 多線程的局限性,則需要提前進行計劃,去掉不需要的語言元素,修改程序邏輯,以包括受 THREAD 編譯器選項支持的語言元素。開發(fā)人員應(yīng)當(dāng)在處理語言元素、進行編譯準(zhǔn)備,以及編譯、鏈接和執(zhí)行程序時與編程團隊進行溝通。
關(guān)于作者
Judith M. Myerson 是一位系統(tǒng)工程師兼架構(gòu)師。她感興趣的領(lǐng)域包括中間件技術(shù)、企業(yè)級系統(tǒng)、數(shù)據(jù)庫技術(shù)、應(yīng)用程序開發(fā)、網(wǎng)絡(luò)管理、安全性、 RFID 及時和項目管理。她是 RFID in the Supply Chain 的作者,也是 Enterprise Systems Integration, Second Edition Handbook 的編輯。
http://www.ibm.com/developerworks/cn/aix/library/au-COBOLmultithread/index.html