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