MVS(z/OS)は、マルチ・タスクのオペレーティング・システムです。一般のアプリケーションでも、複数のタスクに処理を振り分けることでより実行効率のよいプログラム構造を持つこともできます。同時に複数のイベント(処理要求)が発生したり、非同期に発生するイベントを処理するプログラムではタスクを分割するプログラム・デザインは珍しいものではありません。しかし、バッチ処理でのマルチ・タスク構造のプログラムはまれで、通常はオンライン処理プログラムなどサーバー的な機能を持つプログラムにおいて主に採用されます。
どういう時にマルチタスクを使うか
実現したい処理がオンラインだから、サーバーのサービスだからということで何でもかんでもマルチ・タスクにするわけではありません。I/O待ち(通信処理のI/Oも含む)の間に他の処理ができるからということでタスクを分けることは決して間違いではありませんが、トランザクション量が増えても集中しても誤りなく正確にマルチ・タスクのプログラムを制御することは決して簡単ではありません。まずは、本当に単一のタスクで制御できないのかを十分に検討し、その上で必要ならマルチ・タスクでのデザインを考える方がいいでしょう。
大まかなプログラムの設計として、機能か量かでタスクを分ける考え方があります。機能(ファンクション)で分けるというのは、オンライン処理などでは端末(クライアント)の接続、切断、送信、受信、ファイルやデータベースのアクセスや更新などの一連の処理をいくつかのパートに分けて、それぞれの処理を担うプログラムをタスクとして独立させる方法です。このようなデザインは一見わかりやすく各タスクに与えられた機能そのものを実現するプログラムも作りやすいのですが、全体を誤りなく正確に動かすためのタスク間の同期制御やデータを受け渡す制御処理が複雑になります。この部分が甘くなったり、抜けてしまうと信頼性を大きく落とすことになりかねません。
筆者の経験では、機能でタスクを分けるぐらいなら単一タスクのままWAITやEVENTSでマルチ・ウェイトを行い、イベント(非同期待ち合わせのI/Oなど)の完了した順に必要な処理を行い、待ち合わせの必要な処理は要求だけ投げてその後は他のイベントの処理へ回る、という構造(イベント駆動型)の方がすっきりすると考えています。これは筆者自身の考え方であって、実際に機能でタスクを分ける考え方を採用している例も多いです。筆者もプログラムを覚えたての頃は、機能(ファンクション)でタスクを分けることは自然なことと考えていました。随分と前のことですが、富士通のOS開発部門の方と話す機会があったときに「VTAMはシングル・タスクでやってるよ」ということを聞いた事があります。あれだけの大量のデータをあんなに速い速度で処理するシステムがシングル・タスクのプログラム構造で作れるのか、と感心してからいろいろ私なりに考えてみたものです。実際その後に、在籍していたベンダーでTN3270サーバー的なソフトウェア製品をエンハンスする仕事をした際に、それまで機能で分けていた複数タスクを単一タスクに設計変更して作り直した結果、送受信データが抜け落ちるようなバグは無くなり処理パフォーマンスも向上したという経験があります。
しかしながら、「一連の処理を単一のタスクで処理をする」という設計を行っても、1つのタスクでは大量のトランザクションが集中すれば処理が追いつかなくことも考えられます。サービスを提供するタスクが1つなので、サービスを受けるための待ち行列は長くなってしまいます。このような場合は、「一連の処理」を完結できる単一のタスクをトランザクション集中量などに応じて増やすことでマルチ・タスク化する方法があります。この方法なら、1つのトランザクションの処理は完結するまで同じタスクが処理しますが、そのタスクを10個用意すれば同時に10のトランザクションを並行して処理できます。今日では、プロセッサーに4つあるいは8つといった複数のCPUが実装されていることが多いので、OSによるCPUディスパッチの切り替えによる多重処理に加えCPU数に応じた同時実行も可能になります。
タスクの生成と実行(ATTACH)および消去(DETACH)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
【メインプログラム】 ----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- : MVI EOTECB,0 CLEAR SYNCHRONOUS ECB ATTACH EP=SUBTASK1, EXECUTE MOD=SUBTASK1 UNDER + PARAM=PARM1, NEW TASK WITH PARM=PARM1+ ETXR=EOTEXIT サブタスクへ渡すパラメーターはPARAMキーワードで指定する。 CALLマクロと同じで、アドレスパラメーターリストが生成される。 特定の値をパラメーターとして渡すような場合は、GR1に直接その 値を設定して、PARAMパラメーターを省略すればよい。 ST R1,TCBADDR SAVE ATTACHED TASK TCB ADDRESS ATTACH後、GR1には生成されたサブタスクのTCBアドレスが返る。 WAIT ECB=EOTECB WAIT UNTIL ATTACHED TASK DONE サブタスクの完了を待ち合わせるためのWAITマクロの発行。 ECBにPOSTするのは、ETXRパラメーターで指定したサブタスク 終了出口ルーチンになる。 もちろん非同期処理を行うためにタスクを分けているのだから、 WAITする前に他の処理を行ってかまわない。そのようにして 処理を並行させなければタスクを分ける意味がない。 : : EOTECB DC F'0' ATTACHED TASK COMPLETION ECB PARM1 DC CL50'THIS IS SUBTASK PARAMETER STRING' : : サブタスク終了出口ルーチン EOTEXIT DS 0H USING *,RF DEFINE TEMP BASE STM RE,RC,12(RD) SAVE CALLER REGISTERS L RC,=A(MAINENTR) ESTABLISH OUR BASE ADDRESS DROP RF FORGET TEMP BASE SPACE , ST R1,TCBADDR SAVE TCB ADDRESS FOR DETACH L R0,TCBCMP-TCB(,R1) LOAD TCB COMPLETION CODE POST EOTECB,(0) POST IT TO MAIN TASK 終了したタスクの完了コードはTCBCMPフィールドを調べればわかる。 このサンプルでは完了コードをPOSTマクロのPOSTコードとして 使用している。 DETACH TCBADDR DETACH ENDED TASK 終了したサブタスクのTCBをパージするためにDETACHマクロを 発行する。DETACHではTCBアドレスそのものをパラメーターとして 指定できない。TCBアドレスが格納されたポインターフィールドの アドレスを渡すことになる。 このサンプルではサブタスク終了出口ルーチンでDETACHを 発行しているが、メインルーチンで発行してもかまわない。 DETACHを発行するまではTCBは存在しているので、メインルーチンでも 参照可能である。 SPACE , LM RE,RC,12(RD) LOAD CALLER REGISTERS BR RE RETURN TO CALLER(DISPATCHER) SPACE , TCBADDR DC A(0) TCB ADDRESS FIELD : : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
【ATTACHされるサブタスクプログラム】 ----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- SUBTASK1 CSECT , DEFINE CONTROL SECTION USING *,RC DEFINE BASE REGISTER STM 14,12,12(13) SAVE CALLER REGISTERS LA 12,0(,15) GR12 -> OUR 1ST BASE ADDRESS LR 15,13 SAVE CALLER SAVEAREA CNOP 0,4 INSURE FULL WORD BOUNDARY BAL 13,*+4+72 AROUND OUR SAVEAREA DC 18F'-1' OUR GPR SAVEAREA ST 15,4(,13) SAVE CALLER SAVEAREA POINTER ST 13,8(,15) SET BACK CHAIN FOR LINK TRACE : : 必要な処理を実装し、ごく普通に作ればよい。 : GR1にはATTACHマクロのPARAMキーワードで指定された : パラメーターのアドレスが入る。 : パラメーターはアドレスリストの形式になっている。 : このサンプルの場合、以下のように渡される。 : GR1 → +---------------------------+ : +0 I メインプログラムの I : I PARM1フィールドのアドレス I : +---------------------------+ : : プログラムが最後の命令を実行して(BR 14等)OSに : 制御を戻すと、メインプログラム側のタスク終了出口ルーチン : が実行される。 : |
新たなタスクを生成し、実行が終わったタスクを消去するAPIの使用サンプルです。タスクを生成して実行するにはATTACHマクロを使います。ATTACHも、基本的にはLINKマクロによるプログラムの呼び出しと同様の手順です。ただし、ATTACHしたプログラム(呼び出し側プログラム)とATTACHされたプログラム(呼び出されたプログラム)は、異なるタスクによって同時に実行されることになる点がLINKと大きく異なります。なお、呼び出したプログラムの処理が短い場合は、ATTACH発行元に制御が戻って来た時点で呼び出したプログラムが終了してしまっていることもあり、どうなるかは処理内容とタイミングによります。ATTACH完了時に呼び出したプログラムが終わっているかまだ実行中かは不定であると考え、どちらであってもいいようにプログラムする必要があります。
ATTACHマクロのキーワードEP(あるいはEPLOC)は、呼び出すプログラムのロードモジュール・メンバー名で、PARAMは呼び出すプログラムへ渡すパラメーターの指定です。さらに、呼び出したプログラムが終了した際の通知方法を指定します。通知方法には、非同期出口ルーチンの起動とECBへのPOSTの2種類があります。どちらを選択するかはプログラム設計次第です。このサンプルでは、非同期出口ルーチンの起動を指定しています。ATTACHされたタスクが終了したタイミングで、OSはETXRキーワードで指定されたプログラムを呼び出して実行します。ATTACHを発行したプログラムの実行を一時停止して割り込んで実行するので「非同期出口ルーチン」と呼ばれます。その他にも細かなパラメーターがありますが、一般のアプリケーション・プログラムであれば省略値でも十分です。
ATTACHから制御が戻ると、GR1には生成されたサブタスクのTCBアドレス(OSがタスクを制御するためのコントロール・ブロック)が返ります。誤ったパラメーター・リストを指定するなどを除けば、ATTACH処理自体が失敗することはほとんどありません。EPまたはEPLOCで指定したプログラム名が誤っている場合も、ATTACHが失敗するのではなく生成されたサブタスクがS806等でABENDします。サブタスク起動時のABENDをトラップしてリカバリー処理を行う場合は、ESTAIキーワードを使用してESTAE同様の回復ルーチンを用意することもできます。しかし、S806ABENDをトラップするだけならわざわざESTAIを使わなくとも、ATTACH前にLOADやCSVQUERYマクロを利用することでモジュールが存在するかどうかのエラーは事前にチェックできます。サブタスク起動後であれば、サブタスク側でESTAEマクロを使ってリカバリー環境を構築できます。
ATTACHされたタスクが終了したら、DETACHマクロによってタスクを消去します。タスクの消去とは、TCBの消去でもあります。タスクの終了が非同期出口ルーチンまたはECBにPOSTされても、TCBはまだ残っておりATTACHしたプログラム側ではそのTCBを参照してタスクの終了状態を調べることができます。必要な情報を収集したり、リソースをクリーンアップした後、DETACHマクロによって終了タスクのTCBを消去することを行います。
ATTACHされたタスク(子タスク)のプログラムとATTACHしたタスク(親タスク)のプログラムは非同期に動きます。異なるタスクのプログラム間でデータの受け渡したり、処理の整合性を正しく取るためにはタイミング合わせや排他制御などのコントロールが必要になります。これらは、WAIT/POST、ENQ/DEQあるいは逐次化命令(CS/CDS)などを利用することで可能です。