OS/2 Threads Cookbook

By Stephen Best

Binary Samples at COOKBO.ZIP

Version 1.2 Stephen Best P.O. Box 3097 Manuka A.C.T.  2603 Australia Phone:   61-6-281-2147 FidoNet:   3:620/243.4 CompuServe: 100033,340 Copyright (c) 1991, 1992 Stephen Best

This document is an attempt to collect together and share a number of  my observations  and ideas  about programming for OS/2 Presentation  Manager using  multiple threads that have evolved over  time and  been gleaned (gratefully) from other explorers in  this area. A thorough understanding of the use of threads is essential for construction of all but the most trivial Presentation Manager programs  and it is hoped that the ideas  contained herein  with be  of aid  to programmers beginning to  tap into  the exciting  possibilities that the use of multiple threads introduce.

If you would like  the  full  C  source  for  the  examples discussed herein,  please contact  me via FidoNet/CompuServe or  at   the  address   given  with   your   Mastercard/Visa particulars. The cost is  $A45 (approx.  $US34)  with  free transfer via  CompuServe. An additional $A10 will be charged for postal delivery if  required  (3.5  inch  media  only). Payment entitles the licensee  to use  the source  from the examples in any programs of their own.

Also, if you have any comments at all regarding the material contained herein, including errors and omissions, I would be more than happy to hear of them.

Stephen Best

28 February, 1992

Introduction
OS/2 as   a  single   user  system  has  the  potential  to            substantially change  the user's  perception  as  to  how  a            personal  computer  should  work. Programs using  multiple threads can not only  increase execution  performance (both            perceived and  actual) but also change the emphasis in user- application interaction to one  where  the  user  has  more control and flexibility and  where the  application  itself takes on a passive  role. The program  should  always  be            receptive to  interaction with the user even if this is just the capability for that  user to  change his/her mind after initiating a lengthy activity.

Users that repeatedly tell  you that  "they don't  need  to            multitask" will have great difficulty in reverting to single threaded software after having  had the  luxury of  using a            well designed  and  responsive  multi-threaded  application. Thus anyone wishing to compete in the market may have a hard time selling their product  in an increasingly aware public arena. It is also  hoped that  all programmers will want to            wring the  maximum result  from  an  environment  for  their efforts, and I think multiple threads have the potential for good returns in this area.

It is also true,  though not  universally appreciated, that programming to the  multi-threaded  model  has  significant impact on the overall program design, and it is important to           have this  in mind  up front to avoid major restructuring of            the program at a later stage.

This document is aimed at the OS/2 programmer wishing to tap into the  power  that  programming  with  multiple  threads provides. As such, I will attempt to cover all the essential issues related to threads, a guide to where and when I think threads are applicable and some substantial coding examples that I think demonstrate  this. These examples are in C and are for OS/2 2.x,  though  conversion  to  other  languages and/or OS/2 1.x  should  not  be  too  difficult  once  the concepts are understood.

All code has been tested with IBM OS/2 2.0 pre-release level 6.177 on an IBM  PS/2 Model  80. The IBM C Set/2 compiler, linker and 6.177 toolkit headers were used.

It is my belief  that practically  ALL  programs  for  OS/2 Presentation Manager   will  benefit  from  using  multiple threads in their design, and indeed have a responsibility to           do so  given  the  message  switching  architecture  of  PM. Comments (especially  those  from  sources  with  a  vested            interest in  promoting second  rate software  products) that there is only  a  minimal  requirement  for  multi-threaded program design should be  considered in  the light  of  the immediate and obvious benefits  that their  proper use  can achieve.

Polemics over, let's learn about OS/2 threads.

What is a thread?
The thread is the basic level of execution under OS/2 and is           roughly equivalent  to the  task of other systems. A program (or process) has a  single thread  at the  beginning of its execution and can optionally  split the  activity  of  that program over a number  of threads. Each thread of execution will be time-sliced on  the processor (CPU) of the computer together with other threads  of that application, and those of other   applications  active  concurrently. A priority mechanism exists to ensure that the thread with the highest priority is always active,  with control  passing to  other threads of a lower  priority when the higher 'blocks' or is            waiting on an event. On top of this OS/2 has a sophisticated scheduler to dynamically alter  thread priority  to achieve responsive overall performance or  multitasking within  the system.

Note that splitting a single processor intensive task over a           number of threads does not in itself achieve anything as the processor itself is a finite resource which cannot be driven beyond its capacity. Indeed the housekeeping in alternately dispatching threads may slow  down execution  in this case. (It may be worthwhile  though to keep in mind that a future            version  of   OS/2  may   well  support   multiple  physical            processors, and  the requirement  for dividing compute bound            tasks will change in this case).

The criteria   for  dividing  a  process  into  threads  as            discussed herein  is aimed  at isolating  the activity  of a            program by  either priority, functional units or access to a            resource.

Consider an application which  presents  the  user  with  a            number of  child windows  or 'views', of which only one (the            active window)  can receive the keyboard focus at a time. It           would in  this case  make sense  to  give  the  active/focus window a higher priority  than the  others,  if  concurrent activity in   other  windows   is  likely   to  impede  the responsiveness of the one with which the user is interacting at that time. This can be achieved quite easily by assigning each window its own 'worker' thread and setting the priority of the active/focus window  thread higher  than that of its siblings. In this case the thread for the active window will receive the processor resource  that  it  requires  without�

interference from the other windows, aiding in the perceived responsiveness of the application.

Actual overall efficiency can  be achieved  by  overlapping processor intensive tasks with  those for  input/output eg. disk I/O. OS/2, as a true pre-emptive multitasking system, can balance the priorities  between processes  to  maximize throughput but it is  the application's  responsibility  to            separate within  itself lengthy  I/O  tasks  from  processor intensive ones, and especially  those likely  to  interfere with servicing of the  system message  queue (more  on this            important area  later). For example, if  a user initiates a            lengthy file  open/save operation  or printing  activity  it            should be  possible to  interrupt this  activity if the user changes his/her mind, or still interact with other facets of           the application  in parallel  with the I/O activity. Failure to split this activity off from the primary thread can even inhibit the user's ability  to switch  to another unrelated application on  the  desktop. In this  case,  it  may  be            advantageous to  spawn a  thread specifically  for servicing the disk or printer  asynchronously. The main thread could then off-load such tasks  and 'queue'  them to a background thread, and get on  business of  interacting with the user. Note that in this case it makes no sense to have a number of           threads for  a  single  resource  (like  a  printer)  as  no            efficiency is gained.

It may be helpful  to think of a one for one correspondence between threads and 'resources' (be they windows or the disk           or a  printer), with  a 'master' thread interacting with the user (and hence the  system  message  queue). It is  this concept of resource based  threads that  will be  expounded upon in the following.

Message queues
Presentation Manager (among other GUI systems) has a message switching architecture to facilitate the routing of messages of different types among  the 'windows'  that make  up  the presentation layer for OS/2. An application  can  receive messages from the system eg. when a user attempts to re-size a window, or can send messages to itself or other windows in           the system. Messages can either be  SENT (explicitly  with            WinSendMsg or  implicitly with  a large  number of other API            calls eg.  WinSetWindowText) or  POSTed  (with  WinPostMsg). Sent messages (and those  API calls  that  result  in  sent            messages) will  be turned  into direct  calls to  the window procedure for the window  specified  in  the  send. Posted messages on   the  other   hand  will   be  queued  in  the application's message queue for deferred execution.

The application message queue is created by the application itself with WinCreateMsgQueue and it is the act of doing so               that  distinguishes   that  application  as  a  Presentation Manager one (as opposed  to a  character  mode  application            executing in  its own session). An application may create as           many message  queues  as  desired  provided  that  only  one message queue exists for  each thread. Message queues other than the   primary  one   are  optional  in  multi-threaded applications and the following  examples  will  attempt  to            demonstrate where  multiple application message queues might be applicable.

Messages queued on a  message queue  (by the  system or the            application itself) are un-queued with (generally) WinGetMsg in a message loop  and then  dispatched to  the appropriate window procedure with WinDispatchMsg. This WinDispatchMsg can be thought of as turning the POSTed message into a SEND for immediate execution. In both cases,  the window handle given specifies the appropriate  window procedure  for that message ... the association  between  window  handle  and procedure (for other than pre-registered classes) is made by           the application with the combination of WinRegisterClass and WinCreate(Std)Window.

The system message queue (of which there is only one for the           whole Presentation  Manager session)  is provided  to  queue those 'messages' that will  be subsequently  distributed to            the appropriate  application message  queue(s) at  such time that the context of  the message  can  be  determined. The primary consideration here is user input (both keyboard and            mouse  actions)   that  may   occur  asynchronously  to  the application message flow. The 'problem' for PM programs is            that the  application processing  of any  message can itself change the destination  for  keyboard  and  mouse  messages pending in the system  message queue  (eg. explicitly  with            calls WinSetFocus or WinSetCapture) and thus it is only when PM itself regains control  from prior  messages that  it is            possible to  determine the  appropriate application queue in            which to  place the  keyboard or mouse message. In addition, the program is responsible  for processing messages dealing with loss of focus  and activation before other windows can be activated. The implication for a  PM program is that it            should always be available for processing user input events, and process all incoming messages quickly.

The methodologies discussed in  this document  are aimed at            off-loading the  bulk of  the processing requirement for the application from the 'input'  message thread to other 'non- input' threads, making the  application always receptive to            user input,  and thus  increasing the  responsiveness of the application and the system as a whole. It may be helpful to           consider that  serialization of  keyboard and mouse messages in the system queue  is not  so  much  a  'problem'  to  be            overcome with  adding threads,  but that  the  main  'input' thread of the application  is just  a vehicle for receiving input from the system  and like all shared resources, to be            treated accordingly.

Performance and restrictions
Sent messages can be  processed faster than posted messages because they never appear  in  the  message  queue  of  the application and thus avoid the message loop altogether. The throughput of inter thread posts will be slower still. This is not to say that posts should be avoided, but that it may be desirable to use  sends rather  than posts when an clear choice exists between the two. Sends also have the guarantee that any dynamic  memory  area  addressed  by  the  message parameter(s) will remain current  for the life of the send, which is a benefit  if  more  data  than  the  eight  bytes permitted with the two 32 bit message parameters themselves is required. As a bonus, the return code from the receiving window procedure method is available upon completion of the send. Sends (because they  are translated into calls to the            window procedure)  will cause  the window procedure(s) to be            called recursively,  and thus may place excessive demands on            the program stack with high levels of recursion.

Posts on the other  hand,  because  of  their  asynchronous nature will be serialized in the message queue and processed when the application itself enters message loop processing. This means that  any  dynamic  data  addressed  by  message parameters when the post was issued may no longer be valid. This consideration requires a number of differing techniques to transfer more data than the message parameters themselves permit. Another important point to note about posts is that the message may not  actually be  posted should the message queue be full at the time of the post. The return code from WinPostMsg should thus be checked to see if the post was in            fact accepted  and implementing  a  delayed  retry  or  some pacing algorithm to ensure the message is not lost. Despite the above, posts will play a big part in the interaction of            and communication  between threads  and thus  the techniques for achieving   efficient  and  reliable  use  of  them  is            presented herein.

Another difference between sends and posts is the context in           which it is valid to issue them. Posts can be issued without restriction between threads and  will appear in the message queue of the thread with which the window addressed (by the            window  handle   specified)  was  created. A variation  on            WinPostMsg  is  WinPostQueueMsg  where  the  handle  of  the message queue itself is  specified instead  of  the  window handle. This permits an  application to  queue messages  to            another thread  (assuming the  receiving thread  has created            its own  message queue)  when no actual window procedure may exist for that thread. This variation will be explored in            one of the following examples.

Sends on the other  hand can only be issued between threads each having a message  queue,  and  for  reasons  following should be avoided for  anything  other  than  intra  thread communications. Firstly, sends to  a window  created  on  a            different thread  than that  from which  the send  is issued will still execute in  the context  of that window's thread and thus may incur a performance penalty due to the overhead involved in the required  thread switch. In addition, inter thread sends (and API  calls that  result in sends to other            threads) may  result in  a  deadlock  situation  should  the receiving thread be waiting (using say a semaphore) on some event from the calling  thread at  the  time  the  send  is            issued. (Note that WinMsgMuxSemWait  exists specifically to            avoid this  deadlock situation.)  The temptation  may be  to            think that  creating windows  each on  separate threads will permit extensive processing without  interference with  the overall message flow, but  it must  be remembered  that all threads that create a (non object) window are subject to the same input restrictions discussed  above. It is because of            these reasons  that I propose creation of all windows on the initial thread and exclusive  use of posts for inter thread communications in this document.

The above brings up the important concept of distribution of           responsibilities within the application. The model I use and propound herein is that  the main  (initial) thread be used almost exclusively for window  'management'. Thus ALL (non            object) windows  will be created (or 'owned') by this thread and any  activity  likely  to  involve  more  than  minimal processing off-loaded to non-window  'service' threads. The main thread (simply because  of the fact that this is where            the windows  were created)  will be  the sole 'input' thread subject to the keyboard/mouse message restrictions discussed above. All other threads  can  thus  undertake  substantial processing tasks   (or   waits)   without   impacting   the application's ability   to  appear   responsive   to   user interaction. Using   this   demarcation   of   processing responsibility, it is unlikely  that the  problem of  using inter thread sends will arise.

Managing threads
Threads (over and above  the initial one allocated when the            program  begins   execution)  are  created  explicitly  with DosCreateThread. Each  thread  will  have  its  own  stack (allocated and committed dynamically with 2.x) but share all code and data areas  of the parent process. Optionally a 32 bit parameter can be  passed to the thread at this time and this is normally used to address thread initialization data (or 'thread parameters'). A thread so created will exist for the life of  the  program  execution  (process)  unless  it            terminates  itself  by  'returning'  or  making  a  call  to            _endthread or DosExit (with EXIT_THREAD).

Due to any overhead  in creating/destroying  a thread it is            normal to  have the  thread life tied to the 'owning' window or dialog box or failing that, the entire process. There are no rules as to  how many  threads should  be created in the 'average' program as this  will be governed by the activity and resource requirements of  each. One way of deciding the number (and more importantly, function) of threads to create is to consider how  many of the elements of the program you would like to run in parallel. Thus a program which creates a number   of  windows  (all  of  which  require  extensive            graphics) plus provides for background printing may create a            thread for  each window,  with  another  for  servicing  the printer queue. Or maybe all the windows could share a single drawing thread if the  processing requirements are smaller. The final consideration of thread numbers and function will depend on both the degree of interactivity and visible feel the programmer wishes to  create with  the program  and how logically functions are isolated  internally in the program itself. (Realistically, the same end result may be achieved            by creating  only a  single 'service'  thread in addition to            the main  thread and  alternately  allocating  time  to  the            respective resources,  but maintaining  the desired  balance            may require  duplicating the  function of the OS/2 scheduler            itself, and hence be self defeating.)

A number  of  other  API  calls  are  related  to  threads. DosWaitThread (new with  2.x)  allows  the  thread  'owner' (actually any thread) to wait until the specified thread is            terminated and  thus can  be used  when the  owner itself is            being destroyed  for clean-up  operations. DosSuspendThread and DosResumeThread allow another thread to temporarily halt execution of the specified thread, and resume operation at a           later time. Due to the fact  that it  will probably not be            possible to  predict the  exact stage  of operation  of  the specified thread, these calls  may not  prove  to  be  that useful, and indeed a  similar effect  can  be  achieved  by            resetting that thread's priority. DosSetPriority can be used to modify a thread's  priority, or to place the thread in a            different dispatching  class. DosKillThread (also new  with            2.x) can  be used  to terminate secondary threads but at the risk of leaving allocated  resources used  by that  thread. DosEnterCritSec and   DosExitCritSec   can   be   used   to            temporarily disallow  execution of  all other threads in the process when serialized access  to a  resource of some type must be guaranteed,  and  using  mutex  semaphores  is  not appropriate. Finally, DosSleep can  be used  by a thread to            surrender the  remainder of its dispatching time slice or to            delay execution for a specified amount of time.

In addition to the  above, OS/2  has a  rich set  of inter- process communication facilities, such  as  semaphores  and pipes which may be used for thread control and transferring data between threads.

DosCreateThread vs. _beginthread
No paper on OS/2  threads  programming  would  be  complete without a discussion on  the differences between the use of            the API  function  DosCreateThread  and  the  replacement  C            compiler run-time extension _beginthread.

The problem with using  DosCreateThread in  a C  program is            that a  number of  C run-time  library and  inline functions assume a single instance  of internal  static variables and the behaviour of the  program may  be undefined  when  this common data is accessed by two or more threads concurrently. Such functions include malloc/free,  strtok and  rand. The standard malloc/free   functions,  for   example,    assume unrestricted access   to  the   heap   management   control information and corruption may  occur  if  access  to  this information is preempted  by  a  second  thread  requesting access to the same data. The strtok and rand functions both save their current state  between calls which may result in            indeterministic behaviour  due  to  dynamics  in  access  of            threads to the previous state.

The solution adopted by  a number of vendors of C compilers has been to prevent  these undesirable  effects  by  either serializing access to such  data that  must be  shared,  or            providing an individual instance of the data for each thread created. This is achieved  firstly by  performing some run- time initialization  of  localized  thread  variables  with _beginthread prior to invoking the DosCreateThread function. Secondly, a number of  run-time functions  are modified  to            either access these local variables or request serialization (with DosRequestMutexSem or DosEnterCritSec)  when the data must be shared. To the  programmer,  such  management  is            transparent provided  that the _beginthread function is used exclusively and the program  is linked with the appropriate multi-threading run-time library.

An alternative solution to the above approach is to restrict a program's use of  functions to  those  documented  to  be            reentrant. True reentrant routines  will use  a stack-based local copy of any  data (where required) and thus avoid any contention from other threads as each has its own individual stack. The IBM C Set/2 Subsystem run-time library (with the            heap  management   functions  replaced   with  use  of  OS/2            suballocation routines)  may well  support this alternative. Such may be desired  to minimize  the run-time  overhead in            providing contention support when none is desired.

Both examples below use _beginthread for creation of threads and are compiled with the multi-threading switch and linked with the supporting run-time library.

Window data
Each window procedure associated  with a  window class will have some data to  be retained over the life of the window, or between processing of messages. This 'static' data can be           initialized when the window procedure receives its WM_CREATE or WM_INITDLG message and  updated depending  on subsequent message flow. It is common practise to place such 'static' data in a dynamically allocated area of memory and have this addressed by a  window  'pointer'. Thus an  area  of  the appropriate size will be  allocated (with  malloc) when the window is created and  the address  of this area saved in a            window 'word' with WinSetWindowPtr. The address of this area will be retrieved with  WinQueryWindowPtr immediately prior to processing of all other messages for the window, and the memory area   disposed  of   (with  free)   in   WM_DESTROY processing. Thus if multiple  'instances' of the window are created, each window can be assured of integrity of its own data. This can have  an added benefit in reducing the total EXE file size, and more importantly promotes what I believe to be a good 'object oriented' programming style. Though not directly related to using  threads,  the  concept  of  data encapsulation will  be  rigidly  exploited  in  the  coding examples contained herein.

Example 1
The first example below is the complete window procedure for a file search dialog. This dialog provides the user with a           means to  search a  number of disks for a specified file, or            ones matching the given 'mask' criteria. The user enters the desired file name (with or without free characters), selects a number of disks  and presses the 'start' button. Once the search is initiated, the 'start' button changes its function to 'stop' to enable the user to interrupt the active search. As files are found that match the search criteria, they will be added to a  list box  which can be scrolled and an entry selected even though the  search is  still active, enabling the user to exit with the selected file without waiting for the search to complete. The 'stop' button reverts  to its 'start' function when the  search is  complete. Whilst this search is in progress,  the user can move the dialog window or interact with other applications on the desktop.

The virtue of using  a separate  thread for  this  type  of            dialog is  that the  I/O intensive  logic for  scanning  the directory list(s) for the specified files can be segregated from that of interacting  with the  user. The end result is           that maximum  flexibility of interaction is achieved without impacting the speed of the actual search.

This dialog window procedure  creates the  search thread in            the WM_INITDLG  processing  and  terminates  the  thread  in            WM_DESTROY, thus  the thread  exists for  the  life  of  the dialog session. The search thread issues a mux wait on two event semaphores: a 'trigger' to initiate a new search and a           'terminate' event  to signal  thread termination. Once the search is active, it  can be  interrupted  by  setting  the fInterrupt flag TRUE, and this flag is checked periodically in the search process.

As files are found  that match  the specified criteria, the search thread   posts  a  UM_SEARCHUPDATE  message  to  the 'owning' thread to signal  that the  found entry  should be            added to  the list  box. In this case,  we cannot  use  the message parameters on the post to fully contain the data to            be transferred  as the  file name length clearly exceeds the eight bytes available. What has been done in this example is           to use  a simplified  form of  circular buffer, with an 'in' and 'out' count. Thus entries can  be added  to the buffer when the 'in' count  does not exceed the 'out' count by the total number of entries  in the  buffer, otherwise we would overlay data that had  not  been  accepted  by  the  owning thread. As the buffer  and counters  are accessible by both threads, all that is required is to signal the owning thread that new data has  been added  to the  list and  should  be            processed. This is done here by equating UM_SEARCHUPDATE to            WM_SEM2 and  using message  parameter 1  as a progress flag, with TRUE indicating completion of the search. The WM_SEM1-4 messages are special in that the messages are not stacked in           the message queue, but accumulated into one message with the message parameter 1 seen  by the  recipient being the OR'ed            result from  all the  messages  parameters  posted. WM_SEM2 (rather than WM_SEM1) was  selected as the priority of this message is lower than  that of keyboard/mouse messages thus avoiding any impact on user interaction whilst transferring data. (If you move the mouse pointer around rapidly you will           notice that the search will slow down.)

A few other observations  on this  example. Because of the nature of the WM_SEMx messages, there is no risk of flooding the application message queue  (and hence losing a post) in            that there can be only one message of this type in the queue at any time. Also, it is  likely that  a number  of  found entries can be transferred  for each  post the  main thread sees, hence improving the efficiency of the transfer. If the circular buffer is full  (indicated by  the  value  of  the            difference  in   the  counters)  the  search  thread  issues DosSleep to surrender the remainder of its dispatching time slice and thus allowing  the main  thread  to  process  the queued entries and free up the slots required.

Another important   element  is   that  the  dialog  window procedure has   been  structured  to  not  have  to  depend synchronously on the action  of the search thread, allowing the search to be interrupted and end without the main thread logic having to issue  a wait. If it is possible to avoid such waits, an extra  level of semaphore handshaking can be            omitted.

Example 2
The second example is a window procedure (together with its            'service'  thread)   for  utilizing   'shadow'  bitmaps   to            facilitate fast  paints and  to off-load  the  bulk  of  the processing requirement to a  'non input'  thread. A shadow bitmap (as used in  this example)  is the  context for  the drawing operations which can  proceed offline from the main window procedure and be  quickly transferred  to the window context with   GpiBitBlt  in   the  WM_PAINT  method. This implementation is ideal when an application can present the completed drawing, rather than show the drawing activity in            progress. Also, if the destination window is to be restored (eg. after being covered  by another)  a subsequent call to            the processor intensive graphics functions is avoided.

This example differs from  the first  in that  the  service thread allocates its own  message queue, and communications between threads   is  achieved   with  posts  (rather  than            semaphores). Thus, a  request  for  some  activity  can  be            'queued' to  the service  thread (with  WinPostQueueMsg)  by            specifying the handle of the message queue itself. Note that WinPostMsg could not be  used in  this case  as the service thread has not actually  created any  windows and  hence no            window handle  exists to  enable PM to determine which queue is applicable. The service thread has its own message loop to un-queue the posted requests and route to the appropriate logic based on message ID, and in this sense is no different from a  normal  window  procedure. When the  activity  is            complete, the  service thread  posts a completion message to            the 'owning'  thread to  trigger the appropriate action (eg.            paint). Lastly, the service thread is terminated by posting WM_QUIT to its message  queue  which  causes  the  loop  to            terminate.

The service thread in  this example  exists for the life of            its 'owning'  window, created in WM_CREATE and terminated in            WM_DESTROY. As the main  procedure  must  insure  that  the service thread's message queue is valid, a semaphore is set by the service thread when the queue handle is available to            its owner.

If multiple instances of this window are required, each will have its own service  thread and  this enables  a  priority mechanism to exist to ensure that the active window will be             drawn before  other, non-active windows. This is achieved in           this example  buy raising  or lowering  the  service  thread priority (in  WM_ACTIVATE)  so  that  the  active  window's            priority is  always higher  that its  siblings. The priority mechanism is absolute in  that the  service thread  for the active window must 'block'  (in WinGetMsg) before the other windows will receive any  processor resource. Note that as           implemented in  this example this set priority will still be            lower than  that of  the main  'input' thread  to reduce any interference with desktop operations.

When using this message  queue technique, it is possible to            optionally check  for pending  messages posted  in the queue with a call to WinQueryQueueStatus. In this example, as all output posts from the  service thread  are the  same,  some processing may be saved if processing of the current message is aborted in favour  of pending messages of the same type. This should only be attempted when it can be quaranteed that the sequence of incoming messages is not disturbed.

This example has been  structured so  that the  main window thread never has to  explicitly wait  for completion  of  a            posted task  (other than for thread termination and recovery            from  failed   posts). If  serialization   is   necessary, semaphores 'posted' by the  service thread  can be  used to            delay  execution  until  desired. Alternatively, the  main thread can wait for  a posted  completion message  by using WinGetMsg and  specifying  the  message  identity. In the example given

WinGetMsg (pw->hab, &qmsg, (HWND) hwnd,                       UM_WINDOWUPDATE, UM_WINDOWUPDATE);

would delay the main thread until the requested service was complete. Note that either of the above (using semaphores or           waiting for  completion messages) issued from the main input thread will have the  effect of  stopping flow  in the main message queue of the  program, and  delay incoming keyboard and mouse messages system-wide  (as discussed  above). The goal should thus be  to structure  the program so that such serialized dependencies are minimized (or ideally avoided).

Other possibilities
The above   two  examples   represent  a   sample  of   the possibilities of managing program  activity  with  multiple threads. A number of  other methodologies  exist which  may prove applicable to different program requirements.

A variation on the  shadow bitmap  example above is to give drawing control of  the  window  presentation  space  to  a            service  thread. This has  the  similar  benefit  in  that processor intensive graphics functions can be off-line from the main input thread  with the  bonus that the application user can see the  drawing in progress, rather than wait for the shadow bitmap to  be completed. To do this the program would (probably in WM_CREATE) associate a presentation space to the window context  with WinOpenWindowDC and GpiCreatePS and pass this presentation  space  handle  to  the  drawing thread. The drawing thread would thus receive requests from the main thread and  invoke the graphics functions required to draw directly upon  the window  presentation space. Some provision may need to  be made for retaining the results of            the drawing  activity should  a full  or partial re-paint be            required due to window sizing or restoral.

An extension of using  threads with their own message queue is to create object  windows (windows created with a parent            of HWND_OBJECT). Activity in such 'windows'  is  initiated with WinPostMsg as the object window handle is specified to            identify the  appropriate message queue and window procedure for that window. In all other respects, this is identical to           the message  queue example  above. The use of object windows may be applicable when  a thread exists to support a number of resources and no overlap in processing is required.

Conclusion
It is hoped that  by now  the  reader  has  understood  the fundamentals of  why multiple threads are applicable to OS/2 Presentation Manager  programs  for  improving  the  overall responsiveness of  the desktop dictated by the message queue architecture, and  the implications  for presentation  of  a flexible  user-application   interface. The  existence  of threads in  OS/2 provides  the application  designer with  a rich set  of techniques  to distribute  function within  the program itself  and co-ordinate  activity. The goal of  the application designer should be to identify opportunities for parallel operation,  and  to  build  the  program  with  the appropriate threads  to achieve  this, whilst  allowing  the user to interrupt or abort any lengthy activity in progress.

Multiple threads,  I  feel,  offer  the  means  to  totally transform a  user's expectation  of  how  personal  computer software should  work and  hopefully this document will help bring about  this new  age of  more responsive  and flexible software.