OS/2 Threads Cookbook: Difference between revisions

From OS2World.Com Wiki
Jump to navigation Jump to search
 
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
By [[Stephen Best]]
''By [[Stephen Best]]''


Binary Samples at COOKBO.ZIP
Binary Samples at COOKBO.ZIP [Stephen Best]


Version 1.2
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.
:Stephen Best
:P.O. Box 3097
:Manuka A.C.T. 2603
:Australia
Copyright (c) 1991, 1992 Stephen Best


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.
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.
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.
Line 25: Line 20:


28 February, 1992
28 February, 1992


==Introduction==
==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.


            OS/2  as  a  single  user  system  has  the  potential  to
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.
            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
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.
            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.


            This document is aimed at the OS/2 programmer wishing to tap
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.
            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
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.
            6.177 on  an IBM  PS/2 Model  80. The  IBM C Set/2 compiler,
            linker and 6.177 toolkit headers were used.


 
Polemics over, let's learn about OS/2 threads.
            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?==
==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.


            The thread is the basic level of execution under OS/2 and is
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).
            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
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.
            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
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.
            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
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.
            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.


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==
==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.


            Presentation Manager (among other GUI systems) has a message
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.
            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
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.
            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
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.
            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
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.
            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==
==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.


            Sent messages  can be processed faster than posted messages
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.
            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
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.
            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
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.
            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  createdA  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
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
            each having  a message  queue,  and  for  reasons  following
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.
            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==
==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).


            Threads (over  and above  the initial one allocated when the
Due to any overhead  in creating/destroying a thread it is
            program  begins  execution)  are  created  explicitly  with
normal to  have the  thread life tied to the 'owning' window
            DosCreateThread.  Each  thread  will  have  its  own  stack
or dialog box or failing that, the entire process. There are
            (allocated and committed dynamically with 2.x) but share all
no rules as to  how many  threads should be created in the
            code and  data areas  of the parent process. Optionally a 32
'average' program as this  will be governed by the activity
            bit parameter  can be  passed to the thread at this time and
and resource  requirements of  each. One way of deciding the
            this is  normally used to address thread initialization data
number (and more importantly, function) of threads to create
            (or 'thread parameters'). A thread so created will exist for
is to  consider how  many of the elements of the program you
            the life  of  the  program  execution  (process)  unless  it
would like  to run in parallel. Thus a program which creates
            terminates  itself  by  'returning'  or  making  a  call  to
a  number of  windows  (all  of  which  require  extensive
            _endthread or DosExit (with EXIT_THREAD).
graphics) plus provides for background printing may create a
 
thread for  each window,  with another  for  servicing  the
            Due to any overhead  in creating/destroying a thread it is
printer queue. Or maybe all the windows could share a single
            normal to  have the  thread life tied to the 'owning' window
drawing thread  if the  processing requirements are smaller.
            or dialog box or failing that, the entire process. There are
The final  consideration of thread numbers and function will
            no rules as to  how many  threads should be created in the
depend on both the degree of interactivity and visible feel
            'average' program as this  will be governed by the activity
the programmer  wishes to  create with  the program and how
            and resource  requirements of  each. One way of deciding the
logically functions are isolated  internally in the program
            number (and more importantly, function) of threads to create
itself. (Realistically,  the same end result may be achieved
            is to  consider how  many of the elements of the program you
by creating  only a  single 'service'  thread in addition to
            would like  to run in parallel. Thus a program which creates
the main  thread and alternately allocating  time  to  the
            a  number   of  windows  (all  of  which  require  extensive
respective resources, but maintaining  the desired balance
            graphics) plus provides for background printing may create a
may require  duplicating the  function of the OS/2 scheduler
            thread for  each window,  with another  for  servicing  the
itself, and hence be self defeating.)
            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 rich set of inter-
A number  of  other  API  calls are  related  to  threads.
            process communication facilities, such as semaphores  and
DosWaitThread (new  with  2.x) allows  the  thread  'owner'
            pipes which may be used for thread control and transferring
(actually any  thread) to wait until the specified thread is
            data between threads.
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 threadthese 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==
==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.


            No paper  on OS/2  threads  programming  would  be  complete
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.
            without a  discussion on  the differences between the use of
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
            the API  function  DosCreateThread  and  the  replacement  C
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.
            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
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.
            has been to prevent these undesirable effects by either
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.
            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
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
            a program's  use of  functions to  those  documented  to be
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.
            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
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.
            and are compiled with the multi-threading switch and linked
            with the supporting run-time library.


==Window data==
==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.
            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==
==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 first example below is the complete window procedure for
The virtue of using a separate thread for this type of dialog is that the  I/O intensive logic for scanning  the directory list(sfor 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.
            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
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.
            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
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.)
            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 terminationOnce 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
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. Alsoit 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
            search thread  posts a UM_SEARCHUPDATE  message  to  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.
            'owning' thread  to signal  that the  found entry  should be
            added to  the list box. In  this casewe 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
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
            nature of the WM_SEMx messages, there is no risk of flooding
such waits, an extra level of semaphore handshaking can be omitted.
            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==
==Example 2==
            The second  example is a window procedure (together with its
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.
            '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
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.
            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
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.
            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
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.
            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
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.
            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).


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==
==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.


            The  above  two  examples  represent  a  sample  of  the
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.
            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.


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==
==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.


            It is  hoped that  by now  the  reader  has  understood  the
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.
            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==
==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:
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
; 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. 3 No. 2 : Planning and Writing a Multithreaded OS/2 Program with Microsoft C, Richard Hale Shaw
Line 727: Line 210:
; The Design of OS/2, H.M. Deitel and M.S. Kogan: Addison-Wesley
; 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
; IBM C Set/2 User's Guide : IBM Publication number S10G-4444-0
[[Category:Technical]]

Latest revision as of 23:39, 29 September 2022

By Stephen Best

Binary Samples at COOKBO.ZIP [Stephen Best]

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.

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