>I would use WAIT/POST on a "wait-for-work" ECB for each subtask, although
>SUSPEND/RESUME is also a valid approach (if slightly trickier to code).

>I would also have another *single* TCB that does all of the output
>processing for the file and the subtasks just add to the tail of the
>"output pending" queue for the dataset and POST the queue ECB. This output
>TCB will just pop output request elements from the queue head and write the
>records to the dataset. The use of a single TCB for the output removes the
>requirement to provide additional serialization for the I/O.

>The "PLO" instruction with either "CSDST" or "CSTST" functions is fantastic
>for queue management.

>Usage of CPOOL obviously comes into play when doing this sort of thing as
>does designing some sort of generic structure (DSECT) for your TCBs and you
>probably need the ability for the mother to monitor the status of each
>subtask handler task.

I agree completely with Rob's reply (quoted above) to the original post by
Joe Owens. I have a few comments:

You will have to take Rob's advice concerning a single task whose job is to
write to the output file. So the need for establishing a queue of records
becomes a requirement. I would approach this task like this:

Establish a "global" area obtained and initialized by the main task and
addressable by each of the subtasks. During maintask initialization obtain
storage for the OUTPUT_WORK_QUEUE.

Code a DSECT for queued records which contains a NEXT pointer and a PREV
pointer and contains the queued data itself. Put an OPCODE in this DSECT so
the maintask can pass instructions to the subtask.

In the global area code a field called FIRST_FREE_RECORD and one called
FIRST_USED_RECORD. These two areas contain a fullword counter and a fullword
address. You'll manipulate these areas using your queueing mechanism only.

After obtaining the work queue area, initialize it by establishing a forward
and back chain for each record in the queue and store the address of the
first one in the global FIRST_FREE_RECORD address. Hence you are starting
with a queue totally containing only records free and available for use.

This is the easy part.

Now open Principles of Operation and start reading about "Perform Locked
Operation". When your eyes have cleared after glazing over you will consider
other possibilities for queue manipulation. There are others, because we've
been using queues ever since VTAM required the CS and CDS instructions years
ago.

You can follow the examples in the POpS for maintaining  a free queue but
don't fall into the trap of trying to do it with a Compare Single and Swap
(CS). Take the POpS advice and use Compare Double and Swap (CDS). The flaw
of using CS will occur and you will end up changing to CDS in time. This I
promise.

If you use CDS you will find that the subtask has to remove all the queued
elements from the chain and then must loop through them establishing a back
chain which is the order that you have to process each element. If you
manage PLO you can remove only one element at a time and that can be the
earliest entry on the queue. If you have a macro that you've written (or
that can be found by google) life will be much easier for you. If you have
to write such a macro, treat it as a separate task and code and debug the
macro before trying to use it in your final program.

In either case (CDS or PLO) the subtasks will each obtain a record from the
FREE_QUEUE, move the data into it and queue it onto the USED_QUEUE. Then
POST the subtask whose job it is to write the output data set. This task
will come awake from its WAIT and will remove records from the USED queue
(either one at a time via PLO or all of them via CDS) and will write the
data to the output data set. As each record is written the subtask will put
the queue entry back onto the FREE queue to be used again. When the queue is
empty the subtask will issue another WAIT on an ECB in the global area which
every other subtasks POSTS when it has queued a record for writing. Note
that there's only one output subtask so its ECB can be global and not in its
subtask control block.

SUSPEND/RESUME requires you be in Supervisor state which implies that you
are running APF-Authorized. Unless you have some other reason to be in this
state, stick with WAIT/POST.

I suggest that each subtask have an initialization routine which it executes
once when originally attached. Have the maintask attach more subtasks than
you think you'll ever need and after each ATTACH wait for the subtask to
post an ECB which indicates that the initialization was complete (and
successful or not). Don't attach more subtasks during execution but start
with more than you think you'll want and then over time scale back to the
number which you find will support the message rate you want. Each subtask
will POST the completion of its initialization and will then issue a WAIT
for a message to be queued to it.

The maintask will POST the subtask when it's time to go to end of job and
the maintask will wait on the task completion ECB (established when the
subtask was attached). Don't forget to issue DETACH STAE=NO when each
subtask ends and have some sort of fail safe mechanism if the subtasks don't
go away when you tell them to. Perhaps a task is hung in MQGET and isn't
waiting on its ECB. Just issue a DETACH STAE=YES and see what happens.

I would suggest that each subtask have its own ESTAEX which would be driven
if the main task force detached it. I don't know how happy MQ will be to
have the task ripped out from under it but you'll find out soon enough. This
problem of being hung in MQGET will have to be dealt with because sooner or
later a message reply won't come back.

You will have to design a mechanism that allows the main task to know
whether a subtask is processing a message at any particular time or not.
This would be an indicator in the SUBTASK_CONTROL_BLOCK which Rob discussed
and which you will want to have. Since only the maintask and one subtask
will ever update the SCB you don't need much locking but I think you'll find
you need some.

Try, as a matter of course, to avoid any ENQ/DEQ logic. These are expensive
system functions. Very expensive, especially DEQ. And use CPOOL whenever
possible to avoid STORAGE OBTAIN any time except initialization. Don't worry
about path lengths during initialization. Do all the expensive work here so
that you don't have to do it during execution. CPOOL is very fast but read
carefully its register use because it is a little non-standard.

My final suggestion is to use Strobe to monitor execution of your program in
a production environment once you get it debugged and have it running for
real. Strobe will tell you where (if any) you are spending an inordinate
amount of time and will help you tune your program for maximum performance.

Oh, if you are moving messages around consider an MVC loop rather than a
MVCL. You might just make a note to yourself to replace the MVCL's at a
later date if Strobe tells you they are causing a problem.

Please stay in touch and let us know how you are proceeding.

Andy Coburn

Reply via email to