>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
