THREADS
Updated: Dec 29, 2007

Introduction
~~~~~~~~~~~~
Multi-Threading (MT) support provides a means to "lock" individual data
objects with the LockObj, UnlockObj and WaitObj statements in threads.inc.
A "data object" is any dimensioned item -- variable, UDT or native or
custom object.

Statements
~~~~~~~~~~
BEGIN THREAD +
  'code run completely before another thread can enter
EXIT THREAD  'an optional exit from the thread code block
  'optional additional code
END THREAD

  When multiple threads enter a THREAD code block, only one thread is
  processed at a time -- the first to arrive.  Others are queued to wait until
  its predecessor thread has completed the code block.

  A THREAD code block may be used to prevent unwanted re-entrance or multiple
  threads running the same code in the same time interval and as a tool to
  achieve "thread-safe" processing.

  A THREAD code block is like a tunnel that allows another thread to enter
  only when the present thread in the tunnel leaves.  Therefore, any
  thread-specific data used or processed in the code block cannot be corrupted
  by actions of another thread. 

  This kind of mechanism is used to prevent multiple processes or threads
  writing to the same file or database record.

  BEGIN/END THREAD is a key part of HotBasic's Multi-Threading (MT), Multi-CPU
  support.  This is the essence of concepts such as "protected resources",
  "sychonization", "critical sections" and the like, that previously required
  awkward sequences of API calls.  BEGIN THREAD/END THREAD code blocks do it
  all; simple, clean, fool-proof.  

STACK +

  STACK(index,value) writes value to the index stack position.  Please see
  code example below.

  Please see the STACK(index) Numeric Function below to read stack locations.

STACKALLOC +

STACKFREE +

  STACKALLOC(n) and STACKFREE(n) allocate and free n 4-byte stack locations
  for "thread-safe" read/write access with the STACK() function and statement.
  With STACKALLOC(n), the STACK keyword may be used to read/write an array
  of n LONG values.

  $APPTYPE CONSOLE
  defint i

  stackalloc(10) 'allocate 10-position stack buffer
  for i=0 to 9
  stack(i,imul(i,100))  'write i*100 in allocated stack
  print stack(i)        'read value from stack position i
  next i
  stackfree(10)  'n must match prior stackalloc(n)

  PAUSE

WAITTHREAD(ID)

  WAITTHREAD(ID) causes code to wait until thread ID completes, where the
  ID value is obtained from the CREATETHREAD Numeric Function.  For each
  thread ID launched, only one WAITTHREAD statement is needed, since upon
  completion, the thread will have terminated.

  Typically, a WAITTHREAD(ID) statement is placed immediately before a
  code section that requires the results of thread ID.  If WAITTHREAD
  statements are placed as far as possible from the thread's launch, there
  may be no wait at all, since, with good planning, the thread in question
  will have had time to complete.

  In short, with good design, the WAITTHREAD statement merely assures that
  a particular thread has completed before code using the thread's results.

  For a non-blocking query on thread status, please see the WAITTHREAD
  Numeric Function below.


Numeric Functions
~~~~~~~~~~~~~~~~~
CPUCOUNT  Number of processors on machine; r = CPUCOUNT

CREATETHREAD(lpFunc, lpParam)

  Syntax: ThreadID = CREATETHREAD lpFunc, lpParam
  where lpFunc is CODEPTR(Proc_Name) and lpParam is the address of a
  variable or UDT with the procedure's parameters.

  The prototype for Proc_Name is:

  DECLARE SUB Proc_Name STD (lpDat as LONG)

  On entry, Proc_Name's argument (lpDat above) receives the lpParam value
  from CREATETHREAD.  lpParam can be zero, if Proc_Name requires no parameters.
  Proc_Name's code can access any global variables or arrays in your program.

  For any set of SUBs (threads) that run concurrently, your names for
  "Proc_Name" and "lpDat" would probably be different for each SUB, as shown
  in the DualCore demo download. 

  Similar to single thread programs, MT requires that thread code does not
  interfere with subsequent code.  Mostly this reduces to care in writing
  variables by concurrently running threads (e.g., i = 1), where the value
  will be used later.  Whether single or multiple threads are involved, you
  avoid inserting statements like i = 0, where subsequent code will need
  i = 1.

  Fortunately, the OS requires that thread code be packaged in a procedure as
  described above.  Each such procedure (thread) can DIM the scratch variables
  needed; these will all be local and there will be no conflict among threads
  running simultaneously in their time slices and perhaps on different CPUs 
  as well.

  Much literature stresses "how difficult" MT can be.  Basically, you just
  want to avoid doing nutty things in multi-CPU apps, mostly the same crazy
  things you avoid in a single thread app.  In short, if you have supervised
  two or more children at play and they live through it, if you have organized
  a work crew with everybody busy most of the time and not interfering with
  each other, then you can do MT also; similar organization principles apply.

  On a single CPU machine, new threads will compete for CPU time slices with
  the main thread of your program.  MT on single CPU machines lets each 
  thread maintain control, so to speak, until its task is completed.

  On multi-CPU machines (e.g., Dual-Core), the OS will distribute thread
  time slices among the processors, which can result in a dramatic increase
  in computation speed.   CREATETHREAD can launch any number of threads.
  The CPUCOUNT Numeric Function can be used to design maximal usage of all
  available processors by parsing task components among them using MT.

  Please see the WAITTHREAD(ThreadID) Statement and Numeric Function. 
  A key component of MT computing is BEGIN THREAD/END THREAD code blocks.

STACK +

  The STACK(index) function reads a value from the application stack where
  index is an expression for the 4-byte stack position to read.

  j=STACK(0) reads value at the current stack pointer.

  In a STD SUB/FUNCTION (any procedure for which arguments have been pushed
  on to the stack), STACK(1) reads the first argument, STACK(2) reads
  the second argument, etc.
  
  The STACK function is relevant to multi-threading by reading procedure
  arguments without using a named global variable.  E.g.,

  SUB CORE1
  defint i    'i is local to the CORE1 code block
  i=STACK(1)  'get first argument 
  'code
  END SUB

WAITTHREAD

  Gets thread completion status; r = WAITTHREAD(ThreadID)
  where ThreadID is the result of the CREATETHREAD function.
  The boolean result r is true if the thread is still running.

  The WAITTHREAD function provides a non-blocking means to determine if a
  thread has terminated.

Thread-Safe Source Code
~~~~~~~~~~~~~~~~~~~~~~~
Since multiple threads have read/write access to the same application ram,
we may define thread-safe:  thread code which does not write values which
might be also written by another thread running in the same time interval.

Two value categories are relevant:  user-defined and HotBasic internal
variables used by keywords.  The dualcore.bas demo is an example of
thread-safe source code regarding user-defined symbols.

Concerning HotBasic keywords, in general, any set of HotBasic thread routines
are thread-safe if no routine uses HotBasic keywords used in another thread
in the set.  Of greater interest, a growing list of keywords are thread-safe
-- can be used in multiple threads:

[Note:  Thread-safe HotBasic source is "hot-off-the-press".  Items not listed
below may be thread-safe, but not yet listed here.]

1. Math operators (all)

   ^ * / \ SHL SHR MOD INV + - [relational] NOT AND OR XOR

2. Array read/write (all variable types) including ARRAYREF() and ARRAYREF$()
   statements and functions. 

3. Numeric functions:

   @ - ABS ACOS ARRAYREF ASC ASIN ATAN ATN BOOL BOOLEAN BYREF BYTESWAP
   CALLBACK CEIL CINT CLNG CODEPTR COMMANDCOUNT COS CREATETHREAD CSRLIN
   DIREXISTS EAX EXP FALSE FILEEXISTS FIX FLOOR FRAC GETLASTERROR HCOS HEX2DW
   HIWORD HSIN HTAN IADD IDIV IIF IMOD IMUL INP INPW INSTR INT ISCONSOLE ISHL
   ISHR ISUB LBOUND LEN LN LNTWO LOG LOG2E LOG2TEN LOGTWO LOWORD MEMCMP NEG
   NOT OBJPTR ONE PI POS RESOURCE RESOURCECOUNT RETFUNC RGB RND ROUND SCREEN
   SGN SHELL SHELL1 SIN SIZEOF SQR TALLY TAN TIMER TRUE UBOUND VARIANTREF
   VARPTR VARTYPE WAITTHREAD ZERO

4. String functions:

   [] ARRAYREF$ BYREF$ COMMA COMMANDLINE$ CRLF IIF$ INKEY$ NULL QUOTE SPACE
   TAB VARIANTREF$ VARPTR$

   [Note: a next step in thread-safe development is a larger set of string
   functions.]

5. Native Objects:

   FPU RECT SCREEN VARIANT VERSION

   [Note: a full enumeration of the many native objects and all their members
   has not yet been done.  Feel free to experiment!]  

Statements which are *not* (yet) thread-safe:

   COPY CREATE FUNCCALL MEMCPY MEMSET MOVE PLAYWAVE RANDOMIZE REDIMEX RENAME   
   RESTORE SELECT CASE (use ELSEIF instead) SOUND THIS WINDOW


+ Penthouse (registered) version

Copyright 2007 James J Keene PhD
Original Publication: Sep 20, 2007
