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

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.

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.