OS/2 Threads Cookbook

From OS2World.Com Wiki
Revision as of 15:04, 23 June 2018 by Martini (talk | contribs)
Jump to navigation Jump to search

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.

References

           The following  references may  be useful  in  expanding  the
           reader's understanding  of OS/2  multi-threading  techniques
           and possibilities  as  applicable  to  Presentation  Manager
           programming:


           Utilizing OS/2 Multithread Techniques in Presentation
           _____________________________________________________
           Manager Applications, Charles Petzold
           ____________________
           Microsoft Systems Journal Vol. 3 No. 2


           Planning and Writing a Multithreaded OS/2 Program with
           ______________________________________________________
           Microsoft C, Richard Hale Shaw
           ___________
           Microsoft Systems Journal Vol. 4 No. 2


           OS/2 PM Programming: A Performance Guide, P.G. Toghill
           ________________________________________
           IBM Personal Systems Developer, Winter 1991


           A Multithread CPU Monitor, Marc Cohen
           _________________________
           OS/2 Notebook, The Best of the IBM Personal Systems
           Developer, Microsoft Press


           Programming for Multithreaded Drawing, Charles Petzold
           _____________________________________
           PC Magazine, Vol. 9 Nos. 10-12


           Programming the OS/2 Presentation Manager, Charles Petzold
           _________________________________________
           Microsoft Press


           Inside OS/2, Gordon Letwin
           ___________
           Microsoft Press


           Microsoft OS/2 Programmer's Reference Vol. 1
           ____________________________________________
           Microsoft Press


           Programming Guide
           _________________
           IBM OS/2 Programming Tools and Information, Version 1.2


           The Design of OS/2, H.M. Deitel and M.S. Kogan
           __________________
           Addison-Wesley


           IBM C Set/2 User's Guide
           ________________________
           IBM Publication number S10G-4444-0