[comp.os.os2] Problem with multiple threads ...

rommel@lan.informatik.tu-muenchen.dbp.de (Kai-Uwe Rommel) (01/05/90)

Some time ago I posted my version of MicroEMACS 3.10 to this news group.
Of course it's not the best way to do the input from keyboard and mouse
with busy waiting. Now I tried to use two separate threads which wait
for keyboard and mouse input and write the characters to a global buffer
from which the main thread now reads his input.

For testing, I extracted all code related to this problem to a stand
alone program. The source is appended to this article. I do not have
much experience with multiple threads. Can someone please tell me if
something is wrong ?

The test program works but I have the following problems:

1. In checkmouse() you will find a call to MouReadEventQue() with
   MOUSE_NOWAIT! When I use MOUSE_WAIT instead, I expect the thread to
   sleep inside the call when no mouse input is available. But actually
   THE OTHER (keyboard) thread is blocked and does not accept any input or
   the main thread is blocked and cannot write to the screen. Why ?
   As a workaround I use MOUSE_NOWAIT but only 10 times per second (the
   DosSleep(100L) call !).

2. The KbdGetStatus() call (commented out) blocks the mouse thread when
   the keyboard thread is waiting for input. Why ? I need the shift state
   to create the mouse event characters correctly.

Except these two problems the serialization with the two semaphores
works well. But when I include the code into MicroEMACS other problems
arise which do no occur with the stand alone model (yes, the correct
compiling and linking options and libraries were used):

3. The whole editor blocks until I insert a DosSleepCall(10L) into the
   keyboard thread loop too. When I trace the program with the debugger,
   everything works well even without the DosSleep(10L).

4. When the mouse thread got an event he is blocked before he could
   insert the event into the global buffer. Again, when I trace the threads
   with the debugger, everything works well.

I already tried DosSetPrty(PRTYS_THREAD, PRTYC_IDLETIME, 0, threadid)
calls to reduce the priority of the waiting threads - no change.

What am I doing wrong ?

Chris Adie suggested another solution using a semaphore for the mouse
thread and another for the keyboard thread and a DosMuxSemWait() in the
main thread. But I want the kbd and mouse threads to run independently
and writing their input to the global buffer because this buffer then
serves as a typeahead buffer.

Kai Uwe Rommel
Munich
rommel@lan.informatik.tu-muenchen.dbp.de

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

#include <stdio.h>
#include <process.h>
#include <stdlib.h>


#define INCL_BASE
#define INCL_NOPM
#include <os2.h>
#include <conio.h>

#define KEYBUFSIZE 64
#define THREADSTACK 4096

static int mouseflag = 0;
static HMOU hmouse;
static USHORT mousemask;

static int buttonmask[3] = {MOUSE_BN1_DOWN, MOUSE_BN3_DOWN, MOUSE_BN2_DOWN};

static USHORT in_buf[KEYBUFSIZE], in_next, in_last;
static void *kbd_stack, *mouse_stack;

static ULONG semAccess = 0L, semEmpty = 0L;

void in_init(void)       /* initialize the input buffer */
{
  DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT);

  DosSemSet(&semEmpty);
  in_next = in_last = 0;

  DosSemClear(&semAccess);
}

int in_check(void)      /* is the input buffer non-empty? */
{
  int empty;

  DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT);

  empty = (in_next == in_last);

  DosSemClear(&semAccess);

  return !empty;
}

void in_put(int event)
{
  DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT);

  in_buf[in_last++] = event;
  in_last &= (KEYBUFSIZE - 1);
  DosSemClear(&semEmpty);

  DosSemClear(&semAccess);
}

int in_get(void)    /* get an event from the input buffer */
{
  int event;     /* event to return */

  DosSemWait(&semEmpty, SEM_INDEFINITE_WAIT);

  DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT);

  event = in_buf[in_next++];
  in_next &= (KEYBUFSIZE - 1);

  if (in_next == in_last)
    DosSemSet(&semEmpty);

  DosSemClear(&semAccess);

  return(event);
}

void checkmouse(void *arg)
{
  MOUEVENTINFO mouInfo;
  KBDINFO kbdInfo;
  USHORT wait, old, new, cnt, chr;

  while (1)
  {
    wait = MOU_NOWAIT;
    MouReadEventQue(&mouInfo, &wait, hmouse);

    if ( mouInfo.time != 0L )
    {
      kbdInfo.cb = sizeof(kbdInfo);
      /* KbdGetStatus(&kbdInfo, 0); */

      chr = 'a';
      if ( kbdInfo.fsState & (LEFTSHIFT | RIGHTSHIFT) )
        chr = 'A';
      if ( kbdInfo.fsState & CONTROL )
        chr = 'A' - '@';

      for ( cnt = 0; cnt < 3; cnt++ )
      {
        old = mousemask & buttonmask[cnt];
        new = mouInfo.fs & buttonmask[cnt];

        if ( old != new )
        {
          in_put(0);
          in_put(0x8000);
          in_put(mouInfo.col);
          in_put(mouInfo.row);
          in_put(new ? chr + 2 * cnt : chr + 2 * cnt + 1);
        }
      }

      mousemask = mouInfo.fs;
    }

    DosSleep(100L);
  }
}

void checkkbd(void *arg)
{
  KBDKEYINFO keyInfo;
  USHORT nextc;

  while (1)
  {
    KbdCharIn(&keyInfo, IO_WAIT, 0);

    if (keyInfo.chChar == 0 || keyInfo.chChar == 0xE0)
    {
      nextc = keyInfo.chScan;
      in_put(0);
      in_put(nextc >> 8);         /* prefix byte */
      in_put(nextc & 255);        /* event code byte */
    }
    else
      in_put(keyInfo.chChar);

    /* DosSleep(10L); */
  }
}

int get(void)
{
  NOPTRRECT rect;
  USHORT res;

  if ( in_check() )
    return in_get();

  if ( mouseflag )
    MouDrawPtr(hmouse);

  res = in_get();

  if ( mouseflag )
  {
    rect.row = rect.col = 0;
    rect.cRow = 24;
    rect.cCol = 79;
    MouRemovePtr(&rect, hmouse);
  }

  return res;
}

void main(void)
{
  USHORT mask, key;

  in_init();

  if ( (kbd_stack = malloc(THREADSTACK)) == NULL ||
       (mouse_stack = malloc(THREADSTACK)) == NULL )
  {
    puts("Cannot allocate thread stacks.");
    exit(-1);
  }

  MouOpen(NULL, &hmouse);
  mask = MOUSE_BN1_DOWN | MOUSE_BN2_DOWN;
  MouSetEventMask(&mask, hmouse);
  mask = 0;
  MouSetDevStatus(&mask, hmouse);

  _beginthread(checkmouse, mouse_stack, THREADSTACK, NULL);
  _beginthread(checkkbd, kbd_stack, THREADSTACK, NULL);

  do
  {
    key = get();
    printf("Empty=%08lx Access=%08lx in_next=%d in_last=%d %04x\n",
           semEmpty, semAccess, in_next, in_last, key);
  }
  while (key != 27);

  exit(0);
}

kulokari@cc.helsinki.fi (01/07/90)

In article <1028@tuminfo1.lan.informatik.tu-muenchen.dbp.de>, rommel@lan.informatik.tu-muenchen.dbp.de (Kai-Uwe Rommel) writes:
> Some time ago I posted my version of MicroEMACS 3.10 to this news group.
> Of course it's not the best way to do the input from keyboard and mouse
> with busy waiting. Now I tried to use two separate threads which wait
> for keyboard and mouse input and write the characters to a global buffer
> from which the main thread now reads his input.
> 
> For testing, I extracted all code related to this problem to a stand
> alone program. The source is appended to this article. I do not have
> much experience with multiple threads. Can someone please tell me if
> something is wrong ?

A corrected version of your program is appended. My patches can
be identified by the defined variable HRK (my initials).

Some notes:
                                
1) When I first tried to compile your program, it did not
compile. You use some symbols (like MOU_WAIT) which are not
defined in the standard header files of the IBM toolkit v. 1.1.
Which toolkit do you use?

2) When the program did compile (after I had checked from manuals
what those undefined symbols probably did mean), it did not link.
_beginthread was not found in the standard C library. Closer
inspection revealed, that you have been using the multithreaded
version of the MSC 5.1 library. This is usually (and in this
case) quite unnecessary, and to be avoided of at any cost.

3) Then I found that you have installed your C header files in a
nonstandard fashion. The header files of the multithreaded
library are supposed to be in the MT subdirectory of the default
INCLUDE directory, and the #include statement for
process.h is usually

#include <mt\process.h>

4) A make file with the C flags and libraries documented would
also have been helpful.

FLAME ON:

When you post your code here for others to scrutiny, please
make sure that all deviations from standard assumptions are
documented.

Thank you.

FLAME OFF.

5) I replaced the call to _beginthread() with DosCreateThread()
and declared far all that should be far. (The thread functions at
least.) The corrected code is not dependent of memory model (I
used small), and it compiles, links and runs in standard
environment.

6) Your code was mostly okay, although I did quite a lot of
cleanup here and there. The basic fault, and probably the cause
for all your sorrows, was quite simple: Mou and Kbd subsystems
are not re-entrant. Only one thread/process at a time can have a
call pending. If other processes need to do something to keyboard
or mouse (note: keyboard != Kbd, mouse != Mou), they have to
use device control calls. I have substituted those where needed.

7) If you need to de-activate the keyboard/mouse handler
temporarily, as you will if you are doing uEmacs, then you have
additional trouble as documented in my note posted here a couple
of days back.

The non-re-entrancy of the Mou and Kbd subsystems is not well
documented, if at all. It can be read from between the lines,
however, when you remember, that all the processes in a screen
group have ONE keyboard and ONE mouse, and only ONE process at a
time has the input focus (not necessarily the same process for
keyboard and mouse). That process can use the Mou a/or Kbd calls.
Virtual keyboards may complicate the picture, but that is a
subject with which I am not familiar enough yet.

---

This is the day and time of the week when all the industrious Finns
take a break and go to sauna. So will I. Hope the rest of the world
will find something to amuse itself with in the meantime.

Hannu Kulokari
CC, U of Helsinki

kulokari@cc.helsinki.fi (Internet)
kulokari@finuh (Bitnet)

------------------------------------------------------------------------
#define HRK 1

#include <stdio.h>
#if HRK==0
#include <process.h>
#endif
#include <stdlib.h>


#define INCL_BASE
#define INCL_NOPM
#include <os2.h>
#include <conio.h>

#define KEYBUFSIZE 64
#define THREADSTACK 4096

#if HRK

/* these constants are NOT defined in the IBM toolkit! */

#define MOU_WAIT 1
#define MOU_NOWAIT 0
#define MOUSE_BN1_DOWN 4
#define MOUSE_BN2_DOWN 16
#define MOUSE_BN3_DOWN 64

#define LEFTSHIFT 2
#define RIGHTSHIFT 1
#define CONTROL 4

#endif

static int mouseflag = 0;
static HMOU hmouse;
static USHORT mousemask;

static int buttonmask[3] = {MOUSE_BN1_DOWN, MOUSE_BN3_DOWN, MOUSE_BN2_DOWN};

static USHORT in_buf[KEYBUFSIZE], in_next, in_last;
#if HRK
/* 
   These pointers must have type, if we are to increment them in
   the call of DosCreateThread 
*/   

static char *kbd_stack, *mouse_stack;
#else
static void *kbd_stack, *mouse_stack;
#endif

static ULONG semAccess = 0L, semEmpty = 0L;

void in_init(void)       /* initialize the input buffer */
{
#if HRK==0
  /* not needed here */
  DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT);
  DosSemSet(&semEmpty);
#endif  

  in_next = in_last = 0;

  DosSemClear(&semAccess);
}

#if HRK==0    /* see get() */
int in_check(void)      /* is the input buffer non-empty? */
{
  int empty;

  DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT);

  empty = (in_next == in_last);

  DosSemClear(&semAccess);

  return !empty;
}
#endif

void in_put(int event)
{
#if HRK==0    
  DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT);
#endif  

  in_buf[in_last++] = event;
  in_last &= (KEYBUFSIZE - 1);
#if HRK==0  
  DosSemClear(&semEmpty);
  DosSemClear(&semAccess);
#endif  
}

#if HRK==0     /* see get() */   

int in_get(void)    /* get an event from the input buffer */
{
  int event;     /* event to return */

  DosSemWait(&semEmpty, SEM_INDEFINITE_WAIT);

  DosSemRequest(&semAccess, SEM_INDEFINITE_WAIT);

  event = in_buf[in_next++];
  in_next &= (KEYBUFSIZE - 1);

  if (in_next == in_last)
    DosSemSet(&semEmpty);

  DosSemClear(&semAccess);

  return(event);
}
#endif


#if HRK
/* must be explicitly declared far when using small model */
void far checkmouse()
#else
void checkmouse(void *arg)
#endif
{
  MOUEVENTINFO mouInfo;
#if HRK
  struct {
    USHORT fsState;
    BYTE fNLS;
  } kbdInfo;
#else  
  KBDINFO kbdInfo;
#endif  
  USHORT wait, old, new, cnt, chr;
    

  while (1)
  {
#if HRK
    wait = MOU_WAIT;
#else
    wait = MOU_NOWAIT;
#endif    
    MouReadEventQue(&mouInfo, &wait, hmouse);

    if ( mouInfo.time != 0L )
    {
#if HRK
      /* Get keyboard shift status. 
         must use device control call, because Kbd subsystem
         is not re-entrant. Only one thread can have a call
         pending. */  
      DosDevIOCtl(&kbdInfo,0L,0x0073,0x0004,0);
#else        
      kbdInfo.cb = sizeof(kbdInfo);
      /* KbdGetStatus(&kbdInfo, 0); */
#endif      

      chr = 'a';
      if ( kbdInfo.fsState & (LEFTSHIFT | RIGHTSHIFT) )
        chr = 'A';
      if ( kbdInfo.fsState & CONTROL )
        chr = 'A' - '@';

#if HRK
      /* You want this to be saved in unbroken sequence */  

      DosSemRequest(&semAccess,-1L);
#endif      

      for ( cnt = 0; cnt < 3; cnt++ )
      {
        old = mousemask & buttonmask[cnt];
        new = mouInfo.fs & buttonmask[cnt];

        if ( old != new )
        {
          in_put(0);
          in_put(0x8000);
          in_put(mouInfo.col);
          in_put(mouInfo.row);
          in_put(new ? chr + 2 * cnt : chr + 2 * cnt + 1);
        }
      }
#if HRK      
      DosSemClear(&semAccess);
      if (in_next != in_last)DosSemClear(&semEmpty);
#endif      

      mousemask = mouInfo.fs;
    }

#if HRK==0
    DosSleep(100L);
#endif    
  }
}

#if HRK
/* must be explicitly declared far when using small model */
void far checkkbd()
#else
void checkkbd(void *arg)
#endif
{
  KBDKEYINFO keyInfo;
  USHORT nextc;

  while (1)
  {
    KbdCharIn(&keyInfo, IO_WAIT, 0);

#if HRK
    DosSemRequest(&semAccess,-1L);
#endif    

    if (keyInfo.chChar == 0 || keyInfo.chChar == 0xE0)
    {
      nextc = keyInfo.chScan;
      in_put(0);
      in_put(nextc >> 8);         /* prefix byte */
      in_put(nextc & 255);        /* event code byte */
    }
    else
      in_put(keyInfo.chChar);
#if HRK      
    DosSemClear(&semAccess);
    DosSemClear(&semEmpty);
#endif    

    /* DosSleep(10L); */
  }
}


int get(void)
{
  USHORT res;
  NOPTRRECT rect;
#if HRK
  if (in_next == in_last) {
    /* queue empty. */
    /* Show mouse while waiting. Must use device control call,
       because Mou subsystem is not re-entrant (only one thread
       can have a call pending).
       MouseDrawPtr() would hang. */

    if (mouseflag) DosDevIOCtl(0L,0L,0x0057,0x0007,hmouse);
    DosSemSetWait(&semEmpty,-1L);
  }  
  res = in_buf[in_next++];
  in_next &= (KEYBUFSIZE - 1);
#else  

  if ( in_check() )
    return in_get();

  if ( mouseflag )
    MouDrawPtr(hmouse);

  res = in_get();
#endif

  if ( mouseflag )
  {
    rect.row = rect.col = 0;
    rect.cRow = 24;
    rect.cCol = 79;
#if HRK
    /* must use device control call */    
    DosDevIOCtl(0L,&rect,0x0058,0x0007,hmouse);
#else    
    MouRemovePtr(&rect, hmouse);
#endif    
  }
  return res;
}

#if HRK
TID kbdid, mouid; 
#endif

void main(void)
{
  USHORT mask, key;

  in_init();

  if ( (kbd_stack = malloc(THREADSTACK)) == NULL ||
       (mouse_stack = malloc(THREADSTACK)) == NULL )
  {
    puts("Cannot allocate thread stacks.");
    exit(-1);
  }

#if HRK
  mouseflag = 0 == MouOpen(NULL,&hmouse);
#else  
  MouOpen(NULL, &hmouse);
#endif
#if HRK
  if (mouseflag) {
#endif    
  mask = MOUSE_BN1_DOWN | MOUSE_BN2_DOWN;
  MouSetEventMask(&mask, hmouse);
  mask = 0;
  MouSetDevStatus(&mask, hmouse);

#if HRK
  MouFlushQue(hmouse);  
  }
  /* _beginthread is not in the *standard* MSC 5.1 library! */  

  if (mouseflag)
    DosCreateThread(checkmouse,&mouid,mouse_stack+THREADSTACK);
  DosCreateThread(checkkbd,&kbdid,kbd_stack+THREADSTACK);
#else  
  _beginthread(checkmouse, mouse_stack, THREADSTACK, NULL);
  _beginthread(checkkbd, kbd_stack, THREADSTACK, NULL);
#endif  

  do
  {
    key = get();
    printf("Empty=%08lx Access=%08lx in_next=%d in_last=%d %04x\n",
           semEmpty, semAccess, in_next, in_last, key);
  }
  while (key != 27);
  exit(0);
}

jack@csccat.UUCP (Jack Hudler) (01/09/90)

In article <1702.25a6586a@cc.helsinki.fi> kulokari@cc.helsinki.fi writes:
>2) When the program did compile (after I had checked from manuals
>what those undefined symbols probably did mean), it did not link.
>_beginthread was not found in the standard C library. Closer
>inspection revealed, that you have been using the multithreaded
>version of the MSC 5.1 library. This is usually (and in this
>case) quite unnecessary, and to be avoided of at any cost.
>                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
This statement is quite inaccurate... you must use the
multithread library whenever using stdio in any threads,
howelse is 'errno' going to know which thread to return
the proper error number too, and other like functions.
This was clearly stated in the OS/2 Design Workshops I have
attended in Redmond.
-- 
Jack 		Computer Support Corportion		Dallas,Texas 
Hudler		UUCP: {texsun,texbell,attctc}!csccat!jack

leefi@microsoft.UUCP (Lee Fisher) (01/10/90)

[regarding the discussion of _beginthread() being unnecessary]

Yes, if you use the MS C 5.1 runtime functions inside a newly created
thread, you should be using _beginthread() to create it. If you're
using "pure" OS/2 APIs inside this thread (and no C RTL functions), you 
can use DosCreateThread(). The _beginthread() function sets up things in
the C runtime so it can run properly.

For more information on _beginthread() (and multi-threaded programming
in general) with the MS C 5.1 compiler, see the file MTDYNA.DOC, included
in the C 5.1 disks. Also, I don't have the issue number handy, but about
six months ago there was an article in Microsoft Systems Journal that
discussed pros and cons of using these two functions.
--
lee fisher, leefi@microsoft.uu.net, leefi%microsoft@uw-beaver.mil
{uw-beaver,decvax,decwrl,fluke,intelca,sco,sun,uunet}!microsoft!leefi 
disclaimer: my opinions are not necessarily those of my employer.

kulokari@cc.helsinki.fi (01/10/90)

In article <10198@microsoft.UUCP>, leefi@microsoft.UUCP (Lee Fisher) writes:
> [regarding the discussion of _beginthread() being unnecessary]
> 
> Yes, if you use the MS C 5.1 runtime functions inside a newly created
> thread, you should be using _beginthread() to create it. If you're
> using "pure" OS/2 APIs inside this thread (and no C RTL functions), you 
> can use DosCreateThread(). The _beginthread() function sets up things in
> the C runtime so it can run properly.

Also, on page 26 of the MSC 5.1 Update of the MSC User's guide there is a list
of functions in the *standard* run-time library, which *are* re-entrant and
thus *can* be used even in multi-threaded programs. With proper care, you can
do just about anything with these functions. I stand by my earlier statement
that using the special multi-threaded library should be avoided at any cost.
The cost is not so high if you limit your use of multiple threads to special
cases, and use OS/2 API functions when necessary instead of the C runtime
library (for I/O at least). The concept of threads is non-portable anyway.

Hannu Kulokari
CC, U of Helsinki

kulokari@cc.helsinki.fi (Internet)
kulokari@finuh (Bitnet)