TIP: Click on subject to list as thread! ANSI
echo: os2prog
to: Timo Sirainen
from: Jonathan de Boyne Pollard
date: 1995-12-29 14:13:08
subject: Timeslices

TS>
  > Can I give timeslices to OS/2 in OS/2 prog? Or how should I
  > do this kind of program:
  >
  > repeat
  >   if keypressed then
  >     begin
  >       c:=readkey;
  >       {...}
  >     end else
  >     begin
  >       give_timeslice; { Any way to do this in OS/2 prog? }
  >     end;
  > until quit;
TS>

  We were talking about the design of the BOXER text editor here in the
  FIDONET OS2PROG echo back in October 1994, and Phil Crown asked
  roughly the same question.  Here is the response that I posted then :

                  ------------------------------------

Conference: Os2prog
  To: Phil Crown               Date: 10/22/1994
From: YOU                      Time:  9:38 am

  The reasons why polling the keyboard has such demerits are the latency
  of the DosSleep() call, and the fact that even when there is no actual
  work to do, the thread is using CPU time.

  Ignoring the clock and mouse for now, consider the loop with a
  blocking KbdCharIn().

  | for (;;) {
  |     blocking_read_keyboard () ;
  |     process_character () ;
  | }

  It spends most of its time blocked in the the upper half of the KBD$
  device driver. When a keystroke arrives, the lower half of the KBD$
  device driver unblocks the thread, making it ready to run.  When the
  thread is next dispatched, it returns from the upper half of the KBD$
  device driver and from the KbdCharIn() call.

  In other words, receipt of a character makes the thread ready to run
  almost immediately, and thus the character is processed as soon as
  possible (subject to thread priority) after it arrives in the keyboard
  device driver.  When there is no work to do, the thread is blocked,
  using no CPU time.

  Now consider a non-blocking loop interspersed with DosSleep() calls.

  | for (;;) {
  |     if ( poll_keyboard() ) {  // KbdCharIn(... IO_NOWAIT ...)
  |        process_character() ;
  |
  |     } else if ( poll_mouse() ) {
  |        process_mouse() ;
  |
  |     } else {
  |        DosSleep ( 1 ) ;     // Yield this time slice
  |        update_clock() ;
  |     }
  | }

  In theory the thread spends most of its time blocked in the internals
  of DosSleep().  When a keystroke arrives, the lower half of the KBD$
  device driver enqueues the keystroke, but since no thread is blocked
  in the upper half, it doesn't do anything more.

  So the thread doesn't actually see anything until DosSleep() returns
  and it calls KbdCharIn() once more.  If the character arrives in the
  keyboard device driver just as DosSleep() is being entered, it isn't
  processed until DosSleep() is over.  What's worse is that DosSleep()
  only guarantees a minimum sleep time.  So it could be quite a while
  before KbdCharIn() is even called to retrieve the character.

  In practice, the overhead of calling KbdCharIn() is quite significant.
  So the thread spends (relatively) more time in KbdCharIn().  But of
  course of that time spent, it only tests the queue once, so this
  doesn't imply that there's a larger window of opportunity for a
  character to be immediately recognised.

  Since polling the mouse has significant overhead too, one iteration
  around the loop ends up using quite a lot of processor time. When
  there is no actual work to do, this is a Mortal Sin for a program in a
  multitasking environment.

  Also in practice, the "dead time" when a keystroke remains
  unrecognised in the keyboard driver is further added to by the calls
  to do mouse and clock processing.

  One final thing to note is that the scheduler lowers the priority of
  CPU bound processes, and raises the priority of I/O bound processes
  (i.e. processes that spend most of their time blocked).  The blocking
  version of the loop is totally I/O bound, so when a character arrives,
  the thread will have a relatively high priority, and will be more
  likely to be the next thread dispatched, giving good response to user
  input.

  On the other hand, the non-blocking version of the loop uses CPU time,
  and will be penalised for it by the scheduler by having its priority
  proportionately lowered.  When the character arrives, it will be less
  likely to be the next thread dispatched, making the application less
  responsive to user input.

  > JdeBP <

___ Maximus/2 2.01wb

                  ------------------------------------

Conference: Os2prog
  To: Phil Crown            Date: 10/22/1994
From: YOU                   Time:  9:51 am

PC>
  > while( 1 ) {
  >   rc = KbdCharIn( &ki, 1, 0 );
  >   DosSleep( 5 );  // give up time slices this drops the CPU usage from
  >                   // %100 to %5 in this example
      do_mouse_and_clock_stuff() ;
  > }
PC>

  Since you asked how else a program would be able to know when a key on
  the keyboard is hit, here's my multithreaded improvement on the above
  single-threaded design.

  Main thread :

         Initialise event_queue and start secondary threads ;
         for (;;) {
             event = event_queue.blocking_read () ;
             switch (event) {
                    case MOUSE : do_mouse(event) ; break ;
                    case KEYBOARD: do_keyboard(event) ; break ;
             }
         }

       So when there is no work to do this thread spends its time
       blocked reading the event queue.

  Secondary thread 1 :

         for (;;) {
             mouse_event = blocking_read_of_mouse () ;
             event_queue.post(mouse_event) ;
         }

       So when there is no work to do this thread spends its time
       blocked in the upper half of the mouse device driver.

  Secondary thread 2 :

         for (;;) {
             keyboard_event = blocking_read_of_keyboard () ;
             event_queue.post(keyboard_event) ;
         }

       So when there is no work to do this thread spends its time
       blocked in the upper half of the keyboard device driver.

  Secondary thread 3 :

         for (;;) {
             DosSleep(1000) ;
             update_clock_display () ;
         }

       This thread spends all of its idle time blocked within DosSleep() .

  Total CPU usage when there is no actual work to do ?  Well, since all
  threads are blocked, it must be zero.

  ( Incidentally, Peter Fitzsimmons will tell you that you can also page
    tune the program by putting the inner loops of the threads as close
    together as possible, reducing the working set of an idle process.
    He'll also tell you that the clock thread is a stupid idea when it's
    just as easy for the user to start up the supplied clock on the
    desktop. )

  The only (slight) difficulty for the programmer is the event queue.
  This must support blocking reads, and all calls must be thread-safe.

  Although there is a message queue IPC facility provided by OS/2, it's
  possibly more expensive than rolling your own, since it has to use
  shared memory to ensure communication between processes.  For multiple
  threads within a single process, a global data structure can be used,
  and the only problem is synchronisation.  Fortunately, this happens to
  be the standard producer/consumer problem from most textbooks, and is
  easily solved using semaphores.

  Do you think that David Hamel is listening ?  (-:

  > JdeBP <

___ Maximus/2 2.01wb

                  ------------------------------------

  > JdeBP <
___
 X MegaMail 2.10 #0:
--- Maximus/2 3.00
* Origin: DoNoR/2,Woking UK (0483-725167) (2:440/4)
* Origin: DoNoR/2,Woking UK (0483-725167) (2:440/4)
* Origin: DoNoR/2,Woking UK (44-1483-725167) (2:440/4)
SEEN-BY: 270/101 620/243 711/401 409 410 413 430 808 809 934 955 712/407 515
SEEN-BY: 712/517 628 713/888 800/1 7877/2809
@PATH: 440/4 141/209 270/101 712/515 711/808 809 934

SOURCE: echomail via fidonet.ozzmosis.com

Email questions or comments to sysop@ipingthereforeiam.com
All parts of this website painstakingly hand-crafted in the U.S.A.!
IPTIA BBS/MUD/Terminal/Game Server List, © 2025 IPTIA Consulting™.