0% found this document useful (0 votes)
204 views

Understanding Harbour MultiThread

The document discusses Harbour's support for multithreading. Key points: - Harbour provides API functions for starting and managing threads, mutexes, signals, and moving workareas between threads. - It supports xBase++ threading concepts like SYNC methods and moving workareas. - For multithreading, settings like code page and language are stored on a thread-specific HVM stack rather than global variables. - Static variables and memory can be allocated on the HVM stack to make them local to each thread.

Uploaded by

juanato
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
204 views

Understanding Harbour MultiThread

The document discusses Harbour's support for multithreading. Key points: - Harbour provides API functions for starting and managing threads, mutexes, signals, and moving workareas between threads. - It supports xBase++ threading concepts like SYNC methods and moving workareas. - For multithreading, settings like code page and language are stored on a thread-specific HVM stack rather than global variables. - Static variables and memory can be allocated on the HVM stack to make them local to each thread.

Uploaded by

juanato
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 37

Understanding Harbour MultiThread

Harbour PRG level API:


 hb_threadStart( [<nThreadAttrs> ,] <@sStart()> | <bStart> | <cStart> [,
<params,...> ] ) -> <pThID>
 hb_threadSelf() -> <pThID> | NIL
 hb_threadId( [ <pThID> ] ) -> <nThNo>
 hb_threadJoin( <pThID> [, @<xRetCode> ] ) -> <lOK>
 hb_threadDetach( <pThID> ) -> <lOK>
* hb_threadQuitRequest( <pThID> ) -> <lOK>
 hb_threadTerminateAll() -> NIL
 hb_threadWaitForAll() -> NIL
 hb_threadWait( <pThID> | <apThID>, [ <nTimeOut> ] [, <lAll> ] ) => <nThInd> |
<nThCount> | 0
 hb_threadOnce( @<onceControl> [, <bAction> ] ) -> <lFirstCall>
 hb_mutexCreate() -> <pMtx>
 hb_mutexLock( <pMtx> [, <nTimeOut> ] ) -> <lLocked>
 hb_mutexUnlock( <pMtx> ) -> <lOK>
 hb_mutexNotify( <pMtx> [, <xVal>] ) -> NIL
 hb_mutexNotifyAll( <pMtx> [, <xVal>] ) -> NIL
 hb_mutexSubscribe( <pMtx>, [ <nTimeOut> ] [, @<xSubscribed> ] ) ->
<lSubscribed>
 hb_mutexSubscribeNow( <pMtx>, [ <nTimeOut> ] [, @<xSubscribed> ] ) ->
<lSubscribed>

 * - this function call can be ignored by the destination thread in some


cases. HVM does not guarantee that the QUIT signal will be always
delivered.

xBase++ compatible functions and classes:

  ThreadID() -> <nID>

  ThreadObject() -> <oThread>

  ThreadWait( <aThreads>, <nTimeOut> ) -> <xResult>

  ThreadWaitAll( <aThreads>, <nTimeOut> ) -> <lAllJoind>

  Thread() -> <oThread>      // create thread object

  Signal() -> <oSignal>      // create signal object

  Harbour supports also SYNC methods and SYNC class method implementing
  original xBase++ behavior.

  It also supports xBase++ concept of moving workareas between threads

  using functions like dbRelease() and dbRequest().

In tests/mt you will find few simple test programs for MT mode.

All is started with this post

2008-09-13 18:49 UTC+0200 Przemyslaw Czerpak (druzus/at/priv.onet.pl)


* harbour/include/hbpp.h
* harbour/include/hbvm.h
* harbour/include/hbcomp.h
* harbour/include/hbcompdf.h
* harbour/include/hbtrace.h
* harbour/include/hbapilng.h
* harbour/include/hbinit.h
* harbour/source/rtl/langapi.c
* harbour/source/pp/ppcore.c
* harbour/source/pp/hbpp.c
* harbour/source/vm/itemapi.c
* harbour/source/vm/hvm.c
* harbour/source/common/hbver.c
* harbour/source/common/hbtrace.c
* harbour/source/common/expropt2.c
* harbour/source/compiler/complex.c
* harbour/source/compiler/hbident.c
* harbour/source/compiler/hbfunchk.c
* changed some declarations from 'char *' to 'const char *' and
fixed casting for some more pedantic compilers

* harbour/source/pp/ppcore.c
! fixed one typo which could cause memory leak and even GPF

* harbour/common.mak
* harbour/source/vm/Makefile
* harbour/source/rtl/Makefile
- harbour/source/rtl/set.c
+ harbour/source/vm/set.c
* harbour/include/hbstack.h
* harbour/source/vm/estack.c
* moved from RTL to HVM
* eliminated hb_set global structure
* moved set structure to HVM stack
+ added internal function hb_setClone() which is used to create
copy of SET structure for child threads
* hidden HB_SET_STRUCT declaration - 3-rd part code must not access it
directly. Dedicated hb_set*() functions should be used instead.
+ added new function:
BOOL hb_setSetItem( HB_set_enum set_specifier, PHB_ITEM pItem )
which allows the changing of some SETs using 3-rd party code.
TODO: not all SETs can be changed yet - if anybody has a little time,
then please add code for the missing ones.

* harbour/include/set.ch
* harbour/include/hbset.h
+ added _SET_CODEPAGE which works like _SET_LANGUAGE giving common
interface

* harbour/include/hbsetup.h
+ added HB_CODEPAGE_DEFAULT which works like HB_LANG_DEFAULT

* harbour/source/vm/hvm.c
! fixed builds which uses non EN lang or code page modules
by forcing linking the chosen ones

* harbour/include/hbstack.h
* harbour/source/vm/estack.c
* harbour/include/hbapicdp.h
* harbour/source/rtl/cdpapi.c
- removed global code page variable: hb_cdp_page and moved
code page settings to HVM stack
+ added new function hb_cdpID() which returns current code page
character ID
+ added new functions hb_vmCDP() and hb_vmSetCDP() to get/set
active for given thread's code page structure

* harbour/include/hbstack.h
* harbour/source/vm/estack.c
* harbour/include/hbapilng.h
* harbour/source/rtl/langapi.c
+ moved lang setting to HVM stack
+ added new functions hb_vmLang() and hb_vmSetLang() to get/set
active for given thread's language module

* harbour/include/hbvmpub.h
* harbour/include/hbstack.h
* harbour/include/hbapi.h
* harbour/source/vm/estack.c
* harbour/source/vm/dynsym.c
* harbour/source/vm/itemapi.c
* harbour/source/vm/memvars.c
* changed memvar handles for HB_HANDLE to void * which is directly
cast to PHB_ITEM - new memvar references
* changed HB_DYNS declarations for MT mode. In MT mode, HB_DYNS does
not contain area and memvar handles which are moved to thread
local HVM stack
+ added array for thread local memvar and area handles to HVM stack
% eliminated global continues array with all memvars and detached locals
% changed HB_IT_MEMVAR to use pointers to HB_ITEM directly - it resolves
synchronization problems in MT mode and should also improve the speed
and reduce memory usage. It should be easily visible in applications
which
use a lot of detached locals.
- removed hb_memvarsInit() and hb_memvarsFree() - they are not necessary
now because we no longer use array with all allocated memvars,
and detached local and private stack initialization is made
automatically
+ added internal functions hb_dynsymGetMemvar()/hb_dynsymSetMemvar()
+ added hb_memvarGetValueBySym() for debugger
* moved PRIVATE variable stack to HVM stack
* eliminated all static variables in memvars module

* harbour/include/hbstack.h
* harbour/source/vm/estack.c
* harbour/source/rtl/fserror.c
* moved IO errors to HVM stack
+ added special IO error handling which works without HVM stack
It allows the use of hb_fs*() functions without allocated stack
by 3-rd party threads.

* harbour/source/rtl/filesys.c
* moved hb_fsCurDir() to HVM stack with special handling to work
with HVM stack like IO errors

* harbour/source/rdd/workarea.c
* allocated RDD node array in bigger pieces to reduce later RT
reallocations in MT mode. If user wants to add dynamically more
then 64 RDDs then he should synchronize this operation himself.

* harbour/source/rdd/wacore.c
* moved WA list, current WA, default RDD and neteer() flag to HVM stack

* harbour/include/hbdefs.h
- removed HB_HANDLE declaration

* harbour/include/hbapi.h
- removed HB_VALUE structure - it's not longer used due to different
memvar handling
* updated hb_struMemvar to new memvar handling
* replaced hb_vmIsLocalRef() and hb_memvarsIsMemvarRef() with
hb_vmIsStackRef() which respect multiple stack and new memvar
and static structures and location in GC mark pass.

* harbour/include/hbstack.h
* harbour/source/vm/estack.c
* harbour/source/vm/hvm.c
+ added support for thread specific data located on HVM stack
Now it's possible to allocate static variables which are
local to thread. Such variables are allocated on HVM stack
and automatically destroyed. To declare new TSD (thread specific data)
variable use:
HB_TSD_NEW( <name>, <size>, <init>, <destruct> )
<name> - name of variable which holds TSD handler
<size> - size of TSD to be allocated
<init> - init function, executed when new TSD is allocated by thread
(thread access given TSD 1-st time). This function receives
void * pointer to allocated area.
<destruct> - destructor function executed when HVM stack is destroyed
f.e.:
static HB_TSD_NEW( s_scrData, sizeof( HB_SCRDATA ),
NULL, hb_xSaveRestRelease );
To initialize dynamically allocated TSD variable use:
HB_TSD_INIT( <name>, <size>, <init>, <destruct> )
Pointer to TSD can be accessed using hb_stackGetTSD( &<name> )
where <name> is name of variable which holds TSD handler, f.e.:
PHB_SCRDATA pScrData = ( PHB_SCRDATA ) hb_stackGetTSD( &s_scrData );
See source/rtl/xsavescr.c as an example
It's also possible to test if data has been already allocated for
current thread by:
hb_stackTestTSD( &<name> ) => pData
it works like hb_stackGetTSD() but return NULL if current thread data
has not been allocated yet.

* harbour/include/hbstack.h
* harbour/source/vm/estack.c
* changed hb_stack location to thread local storage in MT mode
+ added functions and macros to access/assign new HVM stack members
+ changed garbage collection mark functions to work with multiple
stacks, and thread-specific local static and memvar variables

* harbour/source/rtl/xsavescr.c
* use TSD data for screen buffer to make __XSAVESCREEN()/__XRESTSCREEN()
thread independent

* harbour/source/rtl/idle.c
* use TSD data for idle task settings and codeblocks
- removed hb_idleShutDown() - it's no longer necessary

* harbour/source/rtl/setkey.c
* use TSD data for allocated keys to make SETKEY() thread independent

* harbour/source/rtl/math.c
* moved math error handler, math error block, math error mode and
math error structure to TSD

* harbour/source/rtl/errorapi.c
* moved error handler, error block, error launch counter and DOS error
value to TSD

* harbour/source/rtl/inkey.c
* moved inkey "before" and "after" blocks to TSD

* harbour/source/rdd/hsx/hsx.c
* moved HSX handles array to TSD

* harbour/include/hbapigt.h
* harbour/source/rtl/console.c
- removed hb_setkeyInit() and hb_setkeyExit() - they are not longer
necessary; allocated resources will be freed by TSD destructor
function

* harbour/include/hbapi.h
* harbour/source/rtl/console.c
* removed hb_conXSaveRestRelease() - it's not longer necessary;
allocated resources will be freed by TSD destructor function

* harbour/source/vm/macro.c
* moved s_macroFlags to TSD

* harbour/source/rtl/accept.c
* moved accept buffer to TSD
* harbour/include/hbcomp.h
* harbour/include/hbcompdf.h
* harbour/include/hbxvm.h
* harbour/source/compiler/hbmain.c
* harbour/source/compiler/hbfix.c
* harbour/source/compiler/hbpcode.c
* harbour/source/compiler/hbdead.c
* harbour/source/compiler/complex.c
* harbour/source/compiler/genc.c
* harbour/source/compiler/gencc.c
* harbour/source/compiler/hbopt.c
* harbour/source/compiler/hblbl.c
* harbour/source/compiler/hbstripl.c
* harbour/source/compiler/harbour.y
* harbour/source/compiler/harbour.yyc
* harbour/source/compiler/harbour.yyh
* harbour/source/vm/hvm.c
+ added new PCODE HB_P_THREADSTATICS
+ added support for static variables which are local to thread:
THREAD STATIC <varname [:= <exp>], ...>
They work like normal static variables but each thread operates
on its own copy.
* added protection against possible double call to hb_xfree()
It can happen due to wrong marking of expressions as used by bison
and executing destructors after we free some code when syntax error
appears.

* harbour/source/rtl/perfuncs.prg
* harbour/source/rtl/menuto.prg
* harbour/source/rtl/getlist.prg
* harbour/source/rtl/readvar.prg
* harbour/source/rtl/text.prg
* use THREAD STATIC variables to make above code MT safe

* harbour/include/hbgtcore.h
* harbour/source/rtl/hbgtcore.c
+ added hb_gt_BaseFree() which will release current GT pointer
locked by hb_gt_Base() function. This function will be used
to optionally automate GT access synchronization when threads
share the same GT.

* harbour/source/rtl/gtapi.c
* harbour/source/rtl/inkeyapi.c
* harbour/source/rtl/mouseapi.c
* harbour/contrib/hbct/ctwin.c
* free GT pointer by hb_gt_BaseFree()
TODO: CTWIN is not MT safe yet - it will be updated together
with core GT when we add multi window interface for
thread with own console window.

* harbour/bin/hb-func.sh
* harbour/config/linux/gcc.cf
+ added rt lib to Linux builds

* harbour/bin/postinst.sh
* create MT safe version of FM stat library: fmmt
* harbour/bin/pack_src.sh
+ added support for ZIP packing

* harbour/include/hbapi.h
* harbour/include/hbvm.h
* harbour/source/vm/hvm.c
+ added hb_vmThreadInit()/hb_vmThreadQuit() functions - they initialize
HVM for calling thread so it can execute .prg code and call HVM
functions. They can be used by 3-rd party code threads.
+ added hb_vmUnlock()/hb_vmLock() functions which informs that
thread will not operate on HVM structures for some time allowing
to execute single thread only processes like GC.
+ added hb_vmThreadQuitRequest() which sends stop request to given
thread
+ added hb_vmWaitForThreads() which stops main thread execution waiting
for other threads
+ added hb_vmSuspendThreads() and hb_vmResumeThreads() used by GC
to stop all HVM threads before mark/swap scan
+ added linked list of HVM stacks
+ added hb_vmTerminateThreads() used by main HVM thread in QUIT state
* moved EXIT procedures execution from QUIT request to HVM QUIT state
in MT mode. It may effects some non structural code which tries to
access private variables in EXIT functions but it's much cleaner
and understandable for user. Please remember that we guarantee
that code in BEGIN SEQUENCE is _always_ executed even after
HVM QUIT request just like destructs. Personally I think that we
should move EXIT procedures execution also in ST mode.
* changed startup and cleanup code for new internal structures
* changes startup and cleanup code for MT mode
% removed some redundant HB_ITEM type settings
! eliminated non MT safe code which was using reference counters
without protection

* harbour/common.mak
* harbour/source/vm/Makefile
+ harbour/include/hbthread.h
+ harbour/source/vm/thread.c
+ added C level functions to manage threads and synchronization objects.
See hbthread.h for detail description. They are based on PTHREAD API
and PTHREAD documentation can be used as reference. I intentionally
keep this list small for easier multiplatform porting.
Now they have been implemented for PTHREADS (POSIX threads supported by
many different OSes), MS-Win32/64 and OS2. The OS2 version is not tested
at all. I do not even know if it can be compiled so please make tests.
I used Internet resources and some part of xHarbour code as
documentation
for OS2 MT API. It should be quite easy to add other platforms if
necessary.
Harbour core code needs non recursive mutexes, conditional variables and
TLS for one pointer. If a platform does not support conditional
variables
(f.e. MS-Win or OS2) then they can be emulated using multistate
semaphores.
+ added .prg functions to manage threads and synchronization objects:
hb_threadStart( <@sStart()> | <bStart> [, <params,...> ] ) -> <pThID>
hb_threadJoin( <pThID> [, @<xRetCode> ] ) -> <lOK>
hb_threadDetach( <pThID> ) -> <lOK>
hb_threadQuitRequest( <pThID> ) -> <lOK>
hb_threadWaitForAll() -> NIL
hb_mutexCreate() -> <pMtx>
hb_mutexLock( <pMtx> [, <nTimeOut> ] ) -> <lLocked>
hb_mutexUnlock( <pMtx> ) -> <lOK>
hb_mutexNotify( <pMtx> [, <xVal>] ) -> NIL
hb_mutexNotifyAll( <pMtx> [, <xVal>] ) -> NIL
hb_mutexSubscribe( <pMtx>, [ <nTimeOut> ] [, @<xSubscribed> ] ) ->
<lSubscribed>
hb_mutexSubscribeNow( <pMtx>, [ <nTimeOut> ] [, @<xSubscribed> ] ) ->
<lSubscribed>
The function list should give results similar to xHarbour API but they
are not exactly
the same, and except for hb_mutex*() functions, should replicate
xHarbour behavior.

+ harbour/source/vm/vmmt
+ harbour/source/vm/vmmt/Makefile
+ added hbvmmt library to GNU make builds.
Non GNU make builds should be updated.

* harbour/contrib/hbct/pos1.c
* harbour/contrib/gtwvg/gtwvg.c
* harbour/contrib/rddads/ads1.c
* harbour/contrib/hbmisc/spd.c
* harbour/contrib/hbbmcdx/bmdbfcdx.c
* harbour/contrib/examples/rdddbt/dbfdbt1.c
* harbour/source/vm/runner.c
* harbour/source/vm/itemapi.c
* harbour/source/vm/hvm.c
* harbour/source/rtl/console.c
* harbour/source/rtl/strcase.c
* harbour/source/rtl/spfiles.c
* harbour/source/rtl/defpath.c
* harbour/source/rtl/hbgtcore.c
* harbour/source/rtl/dateshb.c
* harbour/source/rtl/mlcfunc.c
* harbour/source/rtl/fstemp.c
* harbour/source/rtl/is.c
* harbour/source/rtl/setcolor.c
* harbour/source/rtl/errorint.c
* harbour/source/rtl/transfrm.c
* harbour/source/rtl/dates.c
* harbour/source/rtl/filesys.c
* harbour/source/rtl/gtdos/gtdos.c
* harbour/source/rtl/gtwin/gtwin.c
* harbour/source/rtl/gtwvt/gtwvt.c
* harbour/source/rtl/gtxwc/gtxwc.c
* harbour/source/rtl/gttrm/gttrm.c
* harbour/source/rtl/gtpca/gtpca.c
* harbour/source/rtl/gtcgi/gtcgi.c
* harbour/source/rtl/gtcrs/gtcrs.c
* harbour/source/rtl/gtstd/gtstd.c
* harbour/source/rtl/gtsln/gtsln.c
* harbour/source/rtl/gtsln/gtsln.h
* harbour/source/rdd/dbf1.c
* harbour/source/rdd/sdf1.c
* harbour/source/rdd/delim1.c
* harbour/source/rdd/dbcmd.c
* harbour/source/rdd/hbdbsort.c
* harbour/source/rdd/workarea.c
* harbour/source/rdd/dbffpt/dbffpt1.c
* harbour/source/rdd/dbfcdx/dbfcdx1.c
* harbour/source/rdd/dbfntx/dbfntx1.c
* harbour/source/rdd/hsx/hsx.c
* harbour/source/rdd/hbsix/sxfname.c
* use API functions instead of direct accessing to hb_cdp_page or hb_set

* harbour/source/rtl/fstemp.c
* harbour/source/rtl/fssize.c
* harbour/source/rtl/hbffind.c
* harbour/source/rtl/filesys.c
* encapsulate potentially slow IO operation inside
hb_vmUnlock()/hb_vmLock() calls to allow other thread GC
activation

* harbour/contrib/hbnf/fttext.c
! fixed casting

* harbour/contrib/gtwvg/gtwvg.h
- removed #include <comctl32.h> - my MinGW and MinGW/CE instalations do
not have them. If it exists in some newer ones then it has to be
covered by #if version checking.

* harbour/source/vm/dynsym.c
- removed hb_dynsymLog() and hb_dynsymMemvarHandle()
* modified code to be MT safe and improved speed of some operations
* added MUEXT protection for global dynamic table access

* harbour/include/hbapi.h
* harbour/source/vm/garbage.c
* changed to work with MT HVM
* changed to work with new memvar structures and thread local static and
memvar variables
* added MUEXT protection for linked block lists
+ added parameter to hb_gcCollectAll() which will force GC activation
in MT mode by temporary suspending all executed threads.
+ added logical parameter to HB_GCALL() functions which is passed to
hb_gcCollectAll()

* harbour/source/vm/fm.c
* added MUEXT protection for FM statistic module
* added MT protection for reference counters. For platforms
which support atomic incrmenetation/decrementation (f.e.
Interlocked*() functions in MS-Win), such operations are
used. For others, it is MUTEX protection. It gives MT safe
readonly access for HVM complex variables without user
synchronization. The MUTEX protection can cause some speed
overhead so it's good to define MT safe version of
HB_ATOM_INC()/HB_ATOM_DEC() in hbthread.h if given platform
has them. Now they are defined only for Windows. For other
platforms, we can define them in the future in assembler for some
most popular CPUs.
* harbour/source/vm/classes.c
* changed class definition array. Now it keeps pointers to class
structures.
* In MT mode allocated at HVM startup big enough array for class
definitions to avoid later RT reallocations. It effectively eliminates
MUTEX synchronization for class structure access.
* protect by MUTEX code for new class creation

* harbour/source/debug/dbgentry.c
* eliminated hbvmopt.h and direct accessing to HVM structures

* harbour/source/rtl/gtclip.c
* protect with MUTEX access to internal clipboard data

* harbour/source/rdd/nulsys/nulsys.c
+ added hb_rddCloseAll()

+ harbour/tests/mt
+ harbour/tests/mt/mttest01.prg
+ harbour/tests/mt/mttest02.prg
+ harbour/tests/mt/mttest03.prg
+ harbour/tests/mt/mttest04.prg
+ harbour/tests/mt/mttest05.prg
+ harbour/tests/mt/mttest06.prg
+ harbour/tests/mt/mttest07.prg
+ added some demonstration/test small MT programs written
using Harbour language. Some of them can be also compiled
by xHarbour but xHarbour does not pass any of my tests in
real multi-CPU machine so do not expect they will work
correctly.

Harbour threads needs OS threads support. Each Harbour thread is directly


mapped to OS thread. It's not very efficient on some older system where
cost of thread creation and/or task switching is very expensive but it
should not be a big problem for modern OS-es which can support threads
in practice almost always in user space only.
I haven't touched Harbour function calling convention which comes from
Clipper. It means that we do not pass pointer to VM to each functions
like CLIP or xBase++. To resolve the problem I have to use thread local
storage (TLS) where such pointer is kept. If platform does not support
TLS then it can be emulated by us. Anyway the speed of accessing TLS
data and extracting HB_STACK poitner is critical for performance.
Some compilers, depending on used hardware and OS, give native support
for TLS (f.e. __thread keyword in GCC/BCC or __declspec( thread ) in MSVC).
This should give optimal performance. On others, Harbour uses TLS functions
like TlsGetValue() (MS-WIN) or pthread_getspecific() (PTHREAD) are used.
OS2 gives quite interesting TLS functionality which seems to be quite fast
though it will be interesting to know how it is iplemented internally for
real multi CPU machines (if it depends on CPU exception then the
performance will be bad). We need TLS only for one pointer to HB_STACK
structure.
I haven't added any tricks like HB_THREAD_STUB in xHarbour to reduce
the cost of TLS access. If it becomes necessary for some platform, then we
can add it.
Except TLS, Harbour threads need OS support for non recursive mutexes or
critical sections and conditional variables. If the platform does not
support
conditional variables (f.e. MS-Win or OS2) then they can be emulated using
multistate semaphores. I intentionally didn't create code which may need
recursive mutexes. The non recursive ones are often faster and some
platforms may not support recursive mutexes so they will have to be
emulated by us.
Harbour uses reference counters for complex variables. It means that even
readonly access to complex item causes internal write operations necessary
to increment/decrement its reference counter. To make such readonly access
MT safe we have to make incrementation and decrementation with result
checking atomic. By default it's done by mutex inside vm/fm.c but some
platforms have native support for atomic inc/dec operations, f.e.
Interlocked*() functions in MS-Win. If they are available then such
functions should be used to not reduce the performance by mutex call to
often used functions. For many CPUs it should be quite easy to
implement such atomic inc/dec functionality in assembler. F.e. for
GCC and x86 <at> 32 it may looks like:

static __inline__ void hb_atomic_inc32( volatile int * p )


{
__asm__ __volatile__(
"lock incl %0"
:"=m" (*p) :"m" (*p)
);
}

static __inline__ int hb_atomic_dec32( volatile int * p )


{
unsigned char c;
__asm__ __volatile__(
"lock decl %0"
"sete %1"
:"=m" (*p), "=qm" (c) :"m" (*p) : "memory"
);
return c == 0;
}

and then it's enough to define in hbthreads.h:


#define HB_ATOM_INC( p ) hb_atomic_inc32( ( volatile int * ) p )
#define HB_ATOM_DEC( p ) hb_atomic_dec32( ( volatile int * ) p )

Probably I'll make it for some most popular CPUs in the future.
In Harbour, each thread which wants to call HVM functions has to allocate
it's own HVM stack. It's done with hb_vmThreadInit(). The HVM stack is
freed
by calling hb_vmThreadQuit(). This functions can be called also by 3-rd
party threads if they want to call HVM functions or execute .prg code.
Calling HVM functions without allocated stack will cause GPF.
I moved most of static variables to HVM stack to make them thread
local. But some of them like FS errors have their own alternative
copy which is used when thread does not allocate HVM stack. It allows
to use hb_fs*() functions without HVM stack but programmer have to
know that error codes return by hb_fs*Error() functions can be
overwritten by other threads which also didn't allocated HVM stack.
To execute garbage collector scan and mark pass it's necessary to
stop other HVM threads. Otherwise the scan may give false results.
It's also possible to not stop threads but protect with mutex all
operations on GC items but it will probably cause performance reduction
and will force some other modifications. Maybe I'll implement it
in the future.
I didn't use any OS level thread KILL or CANCEL calls. All HVM threads
have to be cleanly removed without any resource leaks.
QUIT command terminate only calling thread. If main (startup) HVM
thread calls QUIT then it sends QUIT request to all existing threads.
Statements and destructors are executed ALWAYS in QUIT state.
New thread is created by:
hb_threadStart( <@sStart()> | <bStart> [, <params,...> ] ) -> <pThID>
The returned value is a pointer to internal thread structure which
can be used in JOIN or DETACH operations. Each thread should be Joined
or DETACHED to avoid resource leaks. If the programmer does not store
<pThID> or if all instances of <pThID> are destroyed then the thread is
automatically detached. I do not know of a clear method of thread detaching
in OS2. If some OS2 users know it then plase update vm/hbthread.c.
When thread terminates then all mutexes locked by this thread are
released.
Each thread uses its own memvars (PRIVATEs and PUBLICs) and work areas.
When new thread is created then it inherits from parent thread:
- code page
- language
- SETs
- default RDD
error block is initialized to default value by calling ERRORSYS()
and PUBLIC variable GetList := {} is created.
The following objects are initialized to default value:
- error block
- math error handler and math error block
- macro compiler features setting (hb_setMacro())
or move them to SETs.
We can think about inheriting them. It's also possible to add
inheriting of all visible memvars but I do not know it's a good
idea.

Compilation and linking:


For MT mode HVM library should be compiled with HB_MT_VM macro.
GNU make automatically creates hbvmmt library which should be
linked with Harbour MT programs instead of hbvm.
Non GNU make files should be updated.
If given compiler support TLS then you can try to set HB_USE_TLS
to force using native compiler TLS support. Now it's enabled by
default only for BCC. For Linux and GCC builds it may depend also
on used GLIBC version. In older system there is no TLS support
at all or TLS works only for shared binaries so I haven't enabled
it. If you are testing some other compiler then please add default
native TLS support for them in hbthread.h
Users using hb* scripts can simply use -mt switch when they want
to create MT program, f.e.:
hbmk -n -w3 -es2 -mt mttest01.prg

There are still some minor things which should be done but I'll
do them later. Current state seems to be fully functional.
The most important and still missing is our own file lock server
for RDD synchronization in POSIX systems. Kernel internally
recognizes POSIX locks by PID and file i-node - not by PID and file
handle. It means that the same file opened more then once by one
process shares locks. Because POSIX locks can be overwritten
then we do not have any synchronization between aliased workareas
or threads using the same table in *nixes. We have to make
synchronization ourselves. I'll create such lock server ASAP.

Please test and enjoy using Harbour threads.


Workarea
Workareas are fully thread separated. Each thread uses it's own workareas so
there is not need to synchronize access to WA.
The same applies to SETs (so _SET_DELETED, and similar switches are local to
thread and do not effect other threads) and some RDD attributes like default
RDD driver, current WA, neterr
flag, etc. Each thread has it's own table for ALIASes and aliases
are not shared between threads. Just like memvars.
Only RDD nodes and RDD settings (rddInfo()) are global to application.

> Are there public WA for all MT and private WA for one MT?

private for MT.

> Will it be possible to close all private WA of a single MT?

dbCloseAll() - it closes all workareas opened by thread which


executes it. When you terminate thread then all opened by this
thread WA are closed automatically.

> > Is it correct if I say that:


> I've read again the MT's ChangeLog entry and it seems that very few
> things are passed from the "parent" thread to the "child".
> Surely there are good reasons behind it, but I see difficult ( or I'm
> missing sth ) to add threads to existing apps.
> What I mean is that today it's normal to have:
> main()
> opendbfs()
> mainmenu() // which call
> menupoint1()
> menupoint2()
>
> if I have a long report or a ftp exchange I could move it to a
> separate thread so the user doesn't wait for it, but if the threaded
> function has to reopen all the dbfs it will require a major change of
> the apps.

No it's exactly the opposite situation. You can reopen all DBFs quite fast
in single function. Just pass them to child workarea in an array.
But if you do not want to make it then you will have to change your
application and add some mutexes to synchronize WA access between
threads. Further to not kill the performance you will have to group
your IO operations inside single locks. It will still not be efficient
enough when your application uses indexes because each thread
will discard index page tress for the other if they are operating
on different records so each skip will have to start with seek to
rebuild the path from index root node to current record key, etc.
You will also have to implement your own functions to lock workarea
which will keep current record and current order and after each WA
LOCK you will have to restore them and before each WA UNLOCK, you
will have to store current values which will be restored later.

Do you like it? You have to make it in xHarbour. And you cannot
easy reopen tables because WA aliases are global so each thread
will have to use new alias names. Full WA and alias separation
like in Harbour resolves this problems. Please also consider
that it's quite easy to implement automatic synchronization with
setting and saving RECNO and order number. Anyhow it will not
be useable for real life application. Just think what will
happen when two threads want to read 100 fields from different
records in the same WA on real multiprocessor machine:

=thread 1: locks WA makes GOTO 20, RDD reads record from file,
reads field from record buffer, unlocks WA
=thread 2: locks WA makes GOTO 30, RDD reads record from file,
reads field from record buffer, unlocks WA
=thread 1: locks WA makes GOTO 20, RDD reads record from file
again because thread 2 has just overloaded the
buffer with record 30 body, reads field from record
buffer, unlocks WA
=thread 1: locks WA makes GOTO 30, RDD reads record from file
again because thread 1 has just overloaded the
buffer with record 20 body, reads field from record
buffer, unlocks WA
[the same yet 98 times]

Now think about 10 threads and think what will happen. It


will be killer for any computer. To resolve the problem
you will have to create separate record buffer for each
thread in each shared WA. But you will have to make the
same for RECNO value, index tree, current index position
and nearly all other WA internal data so you will end up
with nearly full copy of WA. In practice only OS related
resources will not have to be replicated and even should
not be for optimal performance. For WA it means that
you can share file handles, file locks and file cache
buffers. We do not have file cache buffers yet so it's
not too much what we can share.
As you can see reopening WA is no more different
than creating a final shared workarea for new
thread, working well with acceptable performance.
I'll add WA cloning mechanism in the future but now just
simply reopen the tables.
You can crate a function which will store information about
all open in shared mode tables and indexes in an array, f.e.:
{ cRDD, cTable, { cIndexes, ... } }
for all used WA and then pass this array to child thread
so it can reopen them.
Alternatively, if you do not want to start new thread with such
code for each single query then do not terminate threads after
finishing a job but stop it on mutex using hb_mutexSubscribe()
waiting for new job which will be sent from server by
hb_mutexNotify(). The workarea will be open so you can
simply execute the requested job. If your server decides
that there are too many waiting threads (f.e. more then 5)
then simply send few terminate requests using this mutex
to reduce number of waiting threads. You have a classic
example how it can work in tests/mt/mttest07.prg
where as terminate request I used NIL value.

> With SQL data object is much easier you need only to pass
> one reference to the connection

Not that easy. Only if used protocol/API is stateless and


connection library MT safe. If API is not stateless then
each thread will need new (cloned by us end if necessary by
server) communication object which will store the states or
you will have to use mutex to synchronize access to such
object and it's internal connection. In practice it is
exactly the same problem as for workareas and exactly
the same things have to be resolved.

> but with many workareas that have many


> indexes things are not easy.
> Am I wrong? ( I hope so )

Read above. I'm not sure I was clear enough bu I hope


you will understand me.

* make_b32.mak
+ Now setting 'HB_BUILD_ST = no' will generate an MT build.
The default IS 'yes'

* bin/bld.bat
+ Added cw32mt.lib for BCC32 to make it work for MT Harbour.
; I hope this won't break ST Harbour builds, a quick test
proved not.

best regards,
Przemek

On Tue, 16 Sep 2008, Miguel Angel Marchuet wrote:

Hi Miguel,

> Examples of use:


>     at msdn, it is possible to find an example using threads, one works only
refreshing
>         controls of the window meanwhile another thread make a background
process. 1st
>        present FieldGet's at screen meanwhile 2nd works moving recno
etc...   
It's not clear for me the relation between threads.

If both threads cooperate then it should be shared area.


But in such a case it will only slow the final application
because both thread have to be synced and one can start when second has
finished.

If they do not make some other expensive jobs not related


to WA access then program will be slower due to synchronization and/or context
switching.

If they are making two different independent jobs then


each of them need cloned WA structure for efficient work.

>     Meanwhile 1st thread prints invoices at background, 2nd thread prints in
screen
>     every second how progress the process reading OrdKeyNo().
>     Meanwhile
1st process sell ticket cinema. 2nd thread automatically prints
>     a ticket
using one of your triggers.

This are independent jobs for which you need cloned workareas.
The shared one will not be enough.
We do not have native cloning support but you can simply
reopen table in second other threads to enAable such effect. When we have
cloned WA then instead of reopening you send WA from one thread to another.
The
rest of code will not be changed.
> I know what does it mean, but think that if you have a database in
> xml, ini or html file will be possible to share it.
The storage format is less important here. Each one can
be synced and only the cost will be different.
> Another case, if we want to use a function or object to manage one wa,
> and we call it from different threads, we needs that this wa will be
> common for all threads. Example we use tDataBase 
> object to manage application configuration and work sometimes as mutex too.

And current model is perfect here. Create thread which


will make all requested jobs from other threads on this WA. I do not want why
you want to keep this WA accessible by all other threads. It will force mutex
synchronization on the levels.
1-st: because WA accesses only one thread, as one thread access at a time is
supported by Harbour. Harbour automatically does this to eliminate possible
crashes or internal structure corruption
and 2-nd: which will be used by programmer to group logical WA operations in
one
atom operation which cannot be interrupted by other threads. We may decide to
not implement the 1-st one at all so user will have to make all
synchronizations manually but I do not know that is a good idea because any
user
mistake in synchronization can corrupt internal RDD structures. Anyhow even
without 1-st mutex synchronization, access to such WA will be linear done by
one
thread only and you will not be able to benefit from speed improvement using
threads on multi CPU machine.

Multi thread compatible with xbase++


Here is the complete implementation detail how various threads coordinate with
each other :
Controlling threads using signals
------------------------------
There is a possibility of coordinating threads which does not require a thread
to terminate before another thread resumes. However, this requires the usage
of an object of the Signal class. The Signal object must be visible in two
threads at the same time. With the aid of a Signal object, one thread can tell
one or more other threads to leave their wait state and to resume program
execution:

Thread A Thread B

running running // simultaneous execution


| | // of program code
| oSignal:wait() // thread B stops
|
| wait state // thread A executes code
oSignal:signal()
| running // thread B resumes

| |

Whenever a thread executes the :wait() method of a Signal object, it enters a


wait state and stops program execution. The thread leaves its wait state only
after another thread calls the :signal() method of the same Signal object. In
this way a communication between threads is realized. One thread tells other
threads to leave their wait state and to resume program execution.

Mutual exclusion of threads


--------------------------
As long as multiple threads execute different program code at the same time,
the coordination of threads is possible using wait states as achieved with
:synchronize() or ThreadWait(). However, wait states are not possible if the
same program code is running simultaneously in multiple threads. A common
example for this situation is adding/deleting elements to/from a globally
visible array:

PUBLIC aQueue := {}

FOR i:=1 TO 10000 // This loop cannot


Add( "Test" ) // be executed in
Del() // multiple threads
NEXT

**********************
FUNCTION Add( xValue )
RETURN AAdd( aQueue, xValue )

**************
FUNCTION Del()
LOCAL xValue

IF Len(aQueue) > 1
xValue := aQueue[1]
ADel ( aQueue, 1 )
ASize( aQueue, Len(aQueue)-1 )

ENDIF

RETURN xValue
In this example, the array aQueue is used to temporarily store arbitrary
values. The values are retrieved from the array according to the FIFO
principle (First In First Out). Function Add() adds an element to the end of
the array, while function Del() reads the first element and shrinks the
array by one element (note: this kind of data management is called a Queue).
When the functions Add() and Del() are executed in different threads, the
PUBLIC array aQueue is accessed simultaneously by multiple threads. This
leads to a critical situation in function Del() when the array has only one
element. In this case, a runtime error can occur:

Thread A Thread B

LOCAL xValue
IF Len(aQueue) > 1
Thread is Thread executes function completely
interrupted by the
operating system LOCAL xValue
IF Len(aQueue) > 1
xValue := aQueue[1]
ADel ( aQueue, 1 )
ASize( aQueue, Len(aQueue)-1 )
ENDIF
RETURN xValue

Thread resumes

xValue := aQueue[1]

Runtime error:

Meanwhile, the array is empty

The operating system can interrupt a thread at any time in order to give
another thread access to the CPU. If threads A and B execute function Del()
at the same time, it is possible that thread A is interrupted immediately
after the IF statement. Thread B may then run the function to completion
before thread A is scheduled again for program execution. In this case, a
runtime error can occur because the function Del() is not completely
executed in one thread before another thread executes the same function.

The example function Del() represents those situations in multi-threading


which require muliple operations to be executed in one particular thread
before another thread may execute the same operations. This can be resolved
when thread B is stopped while thread A executes the Del() function. Only
after thread A has run this function to completion may thread B begin with
executing the same function. Such a situation is called "mutual exclusion"
because one thread excludes all other threads from executing the same code
at the same time.

Mutual exclusion of threads is achieved in Xbase++ not on the


PROCEDURE/FUNCTION level but with the aid of SYNC methods. The SYNC
attribute for methods guarantees that the method code is executed by only
one thread at any time. However, this is restricted to one and the same
object. If one object is visible in multiple threads and the method is
called simultaneously with that object, the execution of the method is
serialized between the threads. In contrast, if two objects of the same
class are used in two threads and the same method is called with both
objects, the program code of the method runs parallel in both threads. As a
result, mutual exclusion is only possible if two threads attempt to execute
the same method with the same object. The object must be an instance of a
user-defined class that implements SYNC methods. A SYNC method is executed
entirely in one thread. All other threads are automatically stopped when
they attempt to execute the same method with the same object.

The example with the above mentioned PUBLIC array aQueue must be programmed
as a user-defined class in order to safely access the array from multiple
threads:

PUBLIC oQueue := Queue():new()

FOR i:=1 TO 10000 // This loop can run


oQueue:add( "Test" ) // simultaneously in
oQueue:del() // multiple threads
NEXT

***********
CLASS Queue // Class for managing
PROTECTED: // a queue
VAR aQueue

EXPORTED:
INLINE METHOD init
::aQueue := {} // Initialize array

RETURN self

SYNC METHOD add, del // Synchronized methods


ENDCLASS

METHOD Queue:add( xValue ) // Add an element


RETURN AAdd( ::aQueue, xValue )

METHOD Queue:del // Remove first element


LOCAL xValue // and shrink array

IF Len(::aQueue) > 1
xValue := aQueue[1]
ADel ( ::aQueue, 1 )
ASize( ::aQueue, Len(::aQueue)-1 )
ENDIF

RETURN xValue

In this example, the Queue class is used for managing a dynamic array that
may be accessed and changed from multiple threads simultaneously. The array
is referenced in the instance variable :aQueue and it is accessed within the
SYNC methods :add() and :del() . The Queue object which contains the array
is globally visible. The execution of the :del() method is automatically
serialized between multiple threads:

Thread A Thread B
| |
oQueue:del() |
| oQueue:del()
<...>
ADel( ::aQueue, 1 ) thread is stopped
<...>
RETURN xValue thread resumes
| <...>
| ADel( ::aQueue, 1 )
| <...>
| RETURN xValue

| |

When thread B wants to execute the :del() method while this method is
executed by thread A, thread B is stopped because it wants to execute the
method with the same Queue object. Therefore, SYNC methods are used whenever
multiple operations must be guaranteed to be executed in one thread before
another thread executes the same operations. A SYNC method can be executed
with the same object only in one thread at any time (Note: if a class method
is declared with the SYNC attribute, its execution is serialized for all
objects of the class).

Signal() - Class function of the Signal class.


------------------------------------------
Return

The function Signal() returns the class object of the Signal class.

Description

Signal objects are used to control program flow within one or more threads
from one other thread. To achieve this, it is necessary that one and the
same Signal object is visible in at least two threads. Therefore, Signal
objects can only be used in conjunction with Thread objects. In other words,
at least one additional Thread object must exist in an application for the
Signal class to become useful (note: the Main procedure is executed by an
implicit Thread object).
There are two methods in the Signal class: :signal() and :wait() . The
method :signal() triggers a signal, while the :wait() method causes a thread
to enter a wait state. If a thread executes the :wait() method, it waits for
the signal and suspends program execution. The thread resumes as soon as a
second thread executes the :signal() method with the same Signal object.

With the aid of Signal objects it becomes possible for the current thread to
control program flow in other threads. Each thread which executes the
:wait() method enters a wait state until another thread executes the
:signal() method.
Caution: Multiple Signal objects can be used to control program execution in
multiple threads. Since a Signal object limits program control in threads to
a wait state, so-called "dead lock" situations can occur when multiple
Signal objects are used. A dead lock is reached when thread A waits for a
signal from thread B, while, at the same time, thread B waits for a signal
from thread A.

Class methods

:new()
Creates an instance of the Signal class.

Instance variables

:cargo Instance variable for ad-hoc use.

Attribute: EXPORTED

Datatype: All data types

Methods

:signal() --> self

Triggers a signal for other threads.

:wait( [<nTimeOut>] ) --> lSignalled

Waits until another thread triggers the signal.

Example - 1

// Simple screen saver


// A simple screen saver for text mode applications is programmed
// in this example. The code for the screen saver runs in
// a separate thread which waits to be signalled for a maximum of
// 5 seconds. If no signal is triggered, the screen saver is
// activated. The thread is signalled from the Main procedure
// each time an event (keyboard, mouse) is taken from the
// event queue.
#include "AppEvent.ch"
PROCEDURE Main
LOCAL nEvent, oThread, oSignal
? "Press a key. ESC quits"
oThread := Thread():new()
oSignal := Signal():new()
oThread:start( "ScreenSaver", oSignal, 5 )
DO WHILE nEvent <> xbeK_ESC
nEvent := AppEvent(,,,0)
oSignal:signal()
? "Event =", nEvent
ENDDO
RETURN

** Procedure runs in separate thread

PROCEDURE ScreenSaver( oSignal, nSeconds )


LOCAL cScreen, cTemp
DO WHILE .T.
IF .NOT. oSignal:wait( nSeconds * 100 )
// This code is executed if no signal is triggered
// for more than <nSeconds> seconds
cScreen := SaveScreen( 0,0, MaxRow(), MaxCol() )
cTemp := SubStr( cScreen, 2 ) + Left( cScreen, 1 )
// Change screen until thread is signalled
DO WHILE .NOT. oSignal:wait(10)
cTemp := SubStr( cTemp, 3 ) + Left( cTemp, 2 )
RestScreen( 0,0, MaxRow(), MaxCol(), cTemp )
ENDDO
RestScreen( 0,0, MaxRow(), MaxCol(), cScreen )
cScreen := ;
cTemp := NIL
ENDIF
ENDDO
RETURN

Example - 2

// Controlling Append in two threads using signals


// A new database is created in this example and records from
// an existing database are appended. An array is used as a
// read/write buffer to transfer data from the existing to the
// new database. Reading and writing data runs simultaneously
// in two threads. Two Signal objects coordinate which thread
// may read and which one may write. A third signal causes
// the second thread to terminate.

PROCEDURE Main
LOCAL oReadDone, oWriteDone, oDoExit, oThread, aValues
USE LargeDB
DbCreate( "Test", DbStruct() )
oThread := Thread():new()
aValues := Array( FCount() )
oReadDone := Signal():new()
oWriteDone := Signal():new()
oDoExit := Signal():new()
oThread:start( "AppendTo", "Test" , aValues , ;
oReadDone, oWriteDone, oDoExit )
oWriteDone:wait() // Wait until 2nd database
// is open
DO WHILE .T. // Transfer record to array
Aeval( aValues, {|x,i| x:=FieldGet(i) },,, .T. )
IF ! Eof()
oReadDone:signal() // 1st signal: record is read
SKIP
ELSE
oDoExit:signal() // 2nd signal: terminate thread #2
EXIT
ENDIF
oWriteDone:wait() // Wait until data is written
ENDDO // to new database
oThread:synchronize(0)
USE
RETURN

** Procedure runs in separate thread

PROCEDURE AppendTo( cDbFile, aValues, ;


oReadDone, oWriteDone, oDoExit )
USE (cDbFile) EXCLUSIVE
oWriteDone:signal() // Signal: database is open
DO WHILE .T.
IF oReadDone:wait(10) // Check if record is read
DbAppend() // Write data
AEval( aValues, {|x,i| FieldPut(i,x) } )
oWriteDone:signal() // Signal: Data is written
ELSEIF oDoExit:wait(10) // Got an Exit signal?
EXIT
ENDIF
ENDDO
USE
RETURN

//-------------------------------------------------//

ThreadID() -> nThreadID


----------
Return

The function returns a numeric value > 0.

Description

The function ThreadID() is used to get the numeric ID of the thread in which
the function is called. The value is unique within a single process and may
occur multiple times in different processes.

ThreadInfo( <nWhichInfo>, [<nThreadID>] ) --> aInfo


------------------------------------------
Parameters

<nWhichInfo>

#define constants from the file THREAD.CH must be used for this parameter.
They determine the type of information returned by the function. Multiple
constants can be combined with '+'.

Constants for ThreadInfo()

Sequence Constant Description

A THREADINFO_TID The numeric thread ID


B THREADINFO_SYSTHND The numeric handle used by the operating
system for this thread.
C THREADINFO_FUNCINFO The function name and line number currently
being executed.
D THREADINFO_TOBJ The thread object representing the thread

<nThreadID>

An optional thread ID can be passed to obtain information about one


particular thread. If this parameter is omitted, all threads are queried.

Return

The function returns a two-dimensional array where each thread has one
sub-array containing one or more elements controlled by <nWhichInfo> . If
<nWhichInfo> is a combination of constants, the elements of the sub-array
(the array columns) appear in the sequence order of each constant (first
column in the table).
Description

The function ThreadInfo() returns various items of information about existing


threads
of the current process. The constant THREADINFO_TID retrieves the Xbase++
thread ID which is also returned by the ThreadId() function for the current
thread. THREADINFO_SYSTHND retrieves the thread IDs used by the operating
system. They are provided for calling API functions or for passing system
IDs to third-party tools which require this low-lovel information.
THREADINFO_FUNCINFO results in two elements being added to the sub-arrays.
They contain program line and name of the function or method currently being
executed in the corresponding thread. Both elements contain NIL if the
thread is idle and does not process program code. THREADINFO_TOBJ finally
retrieves the Thread object representing a thread.

Example

// Obtaining information about threads


// The example lists full and partial information
// about threads that can be retrieved with ThreadInfo()

#include "Thread.ch"

PROCEDURE Main
LOCAL aInfo, aThread := { Thread():new(), Thread():new() }

CLS
aThread[1]:start( "Test1" )
aThread[2]:start( "Test2" )

? "Retrieve all thread information"


aInfo := ThreadInfo( THREADINFO_TID + ;
THREADINFO_SYSTHND + ;

THREADINFO_FUNCINFO + ;
THREADINFO_TOBJ )

AEval( aInfo, {|a| QOut( Var2Char(a) ) } )

?
? "Retrieve partial thread information"
aInfo := ThreadInfo( THREADINFO_FUNCINFO + ;
THREADINFO_TID )

?
AEval( aInfo, {|a| QOut( Var2Char(a) ) } )
RETURN

PROCEDURE Test1
DO WHILE .T.
DispOutAt( 10, 10, Time() )

Sleep(100)
ENDDO
RETURN
PROCEDURE Test2
LOCAL nCount := 0
DO WHILE .T.
DispOutAt( 12, 10, nCount ++ )
Sleep(17)
ENDDO
RETURN

// Program output

// Retrieve all thread information


// {1, 324, 14, MAIN, thread}
// {2, 724, 31, TEST1, thread}
// {3, 748, 40, TEST2, thread}
//
// Retrieve partial thread information
// {1, 21, MAIN}
// {2, 31, TEST1}

// {3, 40, TEST2}

ThreadObject() --> oThread


--------------
Return

The function returns the Thread object which is currently executing program
code.

Description

The function ThreadObject() is used to retrieve the current Thread object


when multiple threads are running. This is especially useful if the same
user-defined function is executed multiple times and simultaneously in
different threads. The function can also be used to retrieve the implicit
Thread object which executes the Main procedure at program startup.

ThreadWait( <aThreads> , [<nTimeOut>], ) --> oThread | NIL


-----------------------------------------
Parameters

<aThreads>

<aThreads> is an array whose elements contain Thread objects. The current


thread waits for the termination of one or all of the corresponding threads.
The number of elements in <aThreads> is limited to the value of the constant
THREAD_WAIT_MAX from THREAD.CH.

<nTimeOut>

<nTimeOut> specifies the maximum amount of time the current thread will wait
for the termination of other threads. The unit is 1/100th of a second. The
default value is 0 and this causes the current thread to wait infinitely
until one Thread object in <aThreads> has finished with executing code. If a
value > 0 is specified for <nTimeOut> and no thread has terminated within
this period of time, the current thread resumes progam execution.
Return

If one thread terminates within the time limit defined with <nTimeOut> , the
function returns the corresponding Thread object. Otherwise, the return
value is NIL.

Description

The function ThreadWait() causes the current thread to wait until one thread
from a number of threads has terminated. Compared to the :synchronize()
method of the Thread class, the function has the advantage that the current
thread does not need to wait for a particular thread, but rather can wait
for any thread from a number of threads. Note that the number of threads the
current thread can wait for is limited to the constant THREAD_WAIT_MAX by
the operating system.

ThreadWaitAll( <aThreads> , [<nTimeOut>] ) --> lSuccess


-------------------------------------------
Parameters

<aThreads>

<aThreads> is an array whose elements contain Thread objects. The current


thread waits for the termination of all of the corresponding threads. The
number of elements in <aThreads> , or thread objects, respectively, is
limited to the value of the constant THREAD_WAIT_MAX from THREAD.CH.

<nTimeOut>

<nTimeOut> specifies the maximum amount of time the current thread will wait
for the termination of all other threads. The unit is 1/100th of a second.
The default value is 0 and this causes the current thread to wait infinitely
until all other Thread objects are finished with executing code. If a value
> 0 is specified for <nTimeOut> and no thread has terminated within this
period of time, the current thread resumes progam execution.

Return

When all threads specified in <aThreads> have terminated, the return value
is .T. (true). If the waiting period is limited by a value greater than 0
for <nTimeOut> and not all threads have terminated when this time limit has
expired, the function returns .F. (false).

Description

The function ThreadWaitAll() causes the current thread to wait for the
termination of multiple other threads. Compared to the :synchronize() method
of the Thread class, it has the advantage that the current thread does not
need to wait for one particular Thread object but rather can wait for
multiple threads to complete execution of code.

> I know that it can fall down performance for shared wa, but will be a
> programmer election to make it public or private wa

But you have such possibilities now. All examples you are
presenting so far need only one thread access or cloned workareas. So far none
of them needs shared workareas.
In the final analysis, everything can be done by simple table reopening. You
can still use mutexes instead of RDD locks to synchronize the access.

Harbour has exactly the same functionality using MUTEXES.


hb_mutexCreate() -> <pMtx>
hb_mutexLock( <pMtx> [, <nTimeOut> ] ) -> <lLocked>
hb_mutexUnlock( <pMtx> ) -> <lOK>
hb_mutexNotify( <pMtx> [, <xVal>] ) -> NIL
hb_mutexNotifyAll( <pMtx> [, <xVal>] ) -> NIL
hb_mutexSubscribe( <pMtx>, [ <nTimeOut> ] [, @<xSubscribed> ] ) ->
<lSubscribed>
hb_mutexSubscribeNow( <pMtx>, [ <nTimeOut> ] [, @<xSubscribed> ] ) ->
<lSubscribed>

SUBSCRIBE is the same as WAIT and NOTIFY as in SIGNAL. It was not clear to
me from the documentation Pritpal sent if xbase++ WAIT internally works like
hb_mutexSubscribe() or hb_mutexSubscribeNow(). The valid one should be used.
If you need a class which will work like in xBase++ then you can implement it
in a few lines.
We do not yet have SYNC methods at all but I implemented low level code
for them in thread.c and I have yet to update classes.c.
xHarbour have only CLASS SYNC methods but it also does not support normal
SYNC methods though it compiles code which uses them but internally convert
the function to class methods synchronization.

> it), maybe having the same support in harbour could make it easier to move
> code from xbase++/xharbour to harbour and vv.

Yes. But I'll prefer to create basic function interface which should allow
to easily write upper level code like synchronization object in xBase++.

Hello Przemek

Here are two functions in Xbase++ which amply demonstrate


the functionality of ZERO space where two threads can share a common WA.

When I was working with Xbase++ long back, probably in year 2002-03, I
exploited this feature mainly for only one table which was responsible for
some synchronization for a particular operation. Nowhere did I need this
feature otherwise.

MTs importance is gone once resources become shared between threads. MT has a
meaning only when everything in a thread is self-contained. Your
implementation of MT is PERFECT.
Just consider this scenario which Angel is emphasizing:

//------------------------------------------------//
Syntax
DbRelease( [<nWorkArea>|<cAlias>],[<bAreaBlock>] ) --> lSuccess

Parameters <nWorkArea>

<nWorkArea> is a positive integer specifying the work area whose alias name is
to be transferred into the Zero space. It defaults to the number of the
current work area.

<cAlias>

Alternately, <cAlias> can be specified for


<nWorkArea> . If used, <cAlias> is a character string containing
the alias name to transfer.

<bAreaBlock>

The optional argument <bAreaBlock> specifies a code


block to be associated with the work area.

Return

The function DbRelease() returns .T. (true) when the


alias name is successfully transferred into the Zero space, otherwise it is
.F.
(false).

Description

Together with DbRequest(), the function DbRelease() allows the utilization of


the work space concept of Xbase++. Work spaces are used for database access
across multiple work areas. Each thread has one work space with a maximum of
65.000 work areas. Within work areas, database files are opened and database
fields can be accessed. Access to field variables of one database is bound to
the work area where the database file is opened.
Normally, a work area exists only in the thread where the database is opened
(work areas are local to a thread).

Using DbRelease() and DbRequest() makes it possible to exchange alias names


between two work spaces. The alias name is the reference to an open database.
Thus, a specific work area can be accessed from different threads while the
corresponding database is opened once. This allows the advantages of multi-
threading in database programming to be utilized. For example, a complex
database query can be started in a separate thread while the user is entering
data in the main application thread. The user does not need to wait for the
operation to finish but can be notified when the query is complete.

Exchanging alias names between work spaces always involves a virtual work
space, the Zero space. Function DbRelease() releases the alias name of a work
area in the current work space. It is then collected in the Zero space and
transferred to a work area of another work space when DbRequest() is called.
If the work area is linked to other work areas (SET RELATION) alias names of
all child work areas are transferred as well.

The second parameter <bAreaBlock> is used to define an operation to be


performed within a work area of another work space. It is a code block that is
associated with the work area's alias name. When the alias name is transferred
using DbRequest(), the code block is retrieved as well. It can then be passed
to the function Eval() in order to perform the database operation.

Caution

When an alias name is transferred to the Zero space, database access in the
corresponding work area is not possible. The work area is then not used and
function Used() returns .F. (false). However, the database file is still open.

Example
// Perform database operations in a separate thread

// The example demonstrates how a database query can //


be performed in a separate thread. The task is to count // all customers whose
last name is Thompson. This is // accomplished by a thread that executes the
procedure // DbOperation(). This procedure is called from a code block //
which
is passed to DbRelease().
   PROCEDURE Main
      LOCAL bFor    := {|| Trim(FIELD->LASTNAME)== "Thompson" }
      LOCAL bJob    := {|| DbCount( bFor ) }
      LOCAL oThread := Thread():new()
      USE Customer                          // open database
oThread:start( "DbOperation" )        // thread executes code
      ? "Thread started"
      WAIT "Press a key to release work area"
      DbRelease( ,bJob )                   // start query
      CLS
      ? "Database operation (Query) runs in separate thread"
      ?
      ? "Used()", Used()                    // alias exists now
      ? "Performing some other tasks"      // in the work space of
             
                   // the secondthread.
      DbRequest( ,, @bJob, .T. )           // Get aliasback

      ?"Result of query:", Eval( bJob )   


// display result in  
   //main thread and
      CLOSE Customer                        // close database

   RETURN
 
  
*********************

PROCEDURE
DbOperation                    //
Procedure for any
      LOCAL bBlock,xResult                 // database operation.
      IF DbRequest(, , @bBlock, .T. )      // Wait for work area.
         xResult :=Eval( bBlock )          // Perform operation.
         DbRelease(, {|| xResult } )       // Release work area and
      ENDIF                                 // return result within 
                                            // a code block.
   RETURN

************************
FUNCTION DbCount( bFor )                 // Count all records
      LOCAL nCount := 0                     // that match a
      GO TOP                                // condition.
      DO WHILE ! Eof()
         IF Eval( bFor )
            nCount++
         ENDIF
         SKIP
      ENDDO

   RETURN nCount

//------------------------------------------------------------//

Syntax

DbRequest( [<cAlias>], [<lFreeArea>], ;           


[<@bAreaBlock>], [<lWait>] ) --> lSuccess

Parameters

<cAlias>

<cAlias> is a character expression specifying the alias name of the work area
to be transferred from the Zero space into the current work space. If not
specified, the next alias name pending in the Zero space is transferred.

<lFreeArea>

<lFreeArea> is a logical value specifying whether an unused work area should


be selected prior to the transfer. It defaults to .F. (false). This means that
an unused work area is not selected. If the work area of the current work
space is used, all files are closed in this area with DbCloseArea().
Then the alias name is transferred from the Zero space.

<@bAreaBlock>

<@bAreaBlock> is an optional variable that must be


passed by reference. It receives the code block associated with the work area
in the Zero space. The code block is specified when calling function
DbRelease(). As an alternative, this code block can be retrieved with the
function DbJob().

<lWait>
The optional parameter <lWait> determines whether DbRequest() waits until a
work area is found in the Zero space and can be transferred to the current
work space. It defaults to .F. (false). This means that the function does not
wait but returns immediately even if the Zero space is empty. If .T. (true) is
specified, the function waits until it detects an alias name in the Zero space
that can be transferred.
Return

The return value of DbRequest() is .T. (true) if an alias


name is transferred to the current work space, otherwise it is .F. (false).

Description

The function DbRequest() transfers alias names from the


virtual work space (Zero space) into the current work space. It is the
counterpart of DbRelease(), which transfers alias names into the Zero space.

The transfer of alias names follows the FIFO principle (First in first out).

If the Zero space contains multiple alias names,


DbRequest() transfers the alias that was released first. If <cAlias> is
specified DbRequest() transfers the first alias name that matches
<cAlias> . For more information, refer to function DbRelease().
automatic gc activation

> I see that fwh continues calling hb_gcall() after each time a dialog
> close. It remains necessary in current svn or we have a better choice

Not yet but Harbour is ready for automatic GC activation which I'll enable
soon. Anyhow it may effect 3-rd party code which keeps not
attached GC blocks. With current API it can be done quite easy even by
mistake.
This problem can be resolved by changing the semantic and/or names of few
hb_gc*() functions but it may force 3-rd party code modification if it uses
them.
In general hb_gcAlloc() should always return locked block. Storing this block
in HB_ITEM (hb_retPtrGC(), hb_storptrGC(), hb_itemPutPtrGC()) should
automatically unlock it by calling hb_gcAttach(). hb_gcLock() and
hb_gcUnlock() will be less important and in practice not longer necessary so
probably can be dropped from public API.
I do not want to make too many modifications simultaneously in order to be
able to easy rectify the problems reported by users. But if everything works
OK with new MT code then I'll add automatic GC activation.

Few Question 
Hi Mindaugas,

> 1) memvars are local to the thread including


PUBLICs. I use a few

> memvars to set some global variables/setting, like,comma separator,


> help file name, etc. I do not change it later during program
> execution. In MT these PUBLIC memvars are visible only in main thread.

To be precise in thread which defined them.

> Do you suggest to duplicate


> memvars in all threads, or use another approach?
We have few choices. F.e. we can add automatic memvars
inheritance so each child thread will share all parent thread memvars visible
when thread was started as its own PUBLIC variables.

The variables will be shared so you will have to add your


own synchronization code if you want to change them (write operation).

Such method is natural for our memvar code and can be


easy implemented.

AFAIK xbase++ shares public variables between threads.


But I do not know the detailed behavior of such a solution in unusual
situations (f.e. thread declares public when some other threads or even this
one has private variables. Clipper, in such cases, overwrites private and does
not create public one. I do not know if exact implementation of above feature
will not interact with Clipper compatibility. I can try to implement it
respecting only
Clipper behavior. It should be enough to keep public handles separated from
privates and use them when there is no private handle with the same symbol. As
a 3-rd option we can add support for GLOBAL variables though implemented
without hardcoded links like in xHarbour but by dynamic symbol table so they
will work also with .hrb files and will not reduce the speed in code which
does not use them.

> 2) We use HB_ATOM_INC for strings usage counters. Can HB_ITEMs used by
> a few threads, or each thread uses its own HB_ITEMs? For example, can
> we pass parameters to hb_threadStart() by reference? If HB_ITEMs can
> be shared among threads, then we need synchronization to access
> HB_ITEMs value (not reference counter only).

Variables can be fully shared between threads. You can pass them by reference,
detach and move between threads directly or indirectly f.e. by codeblocks.
There is only one exception which is not safe even in single thread mode just
like in Clipper. Storing local references in arrays ( { @localVar } ) does not
automatically detach them from HVM stack.
It allows to create code which may corrupt HVM stack, f.e you can create
references to unused part of stack...

Ups... I forgot to add detaching local vars passed by reference to


hb_threadStart(). I'll add it in a while. I was thinking about adding
automatic detaching which would resolve the problem with arrays also but I
haven't time to make speed tests.

Please only remember that HVM synchronizes only read only access to variables.
If you want to modify variables which can be accessed simultaneously by
different threads then you should add your own protection code. Now you can
uses mutexes (they are also conditional variables and message queues) for this
job. I'll add support for object sync methods soon. You can think about this
mechanism just like about automatic instance protection. Each object will
have it's own critical section which is gate for each methods
declared with SYNC attribute.

> 3) threads can be used to simplify implementation of IO. It's easier

> to implement synchronous (blocking) IO, let say,wait for some data on
> COM port, instead of asynchronous IO (processing some notification
> about data arival, etc). How can this blocking IO thread can be terminated?

I haven't implement low level cancel mechanism intentionally. To avoid


resource leaks each thread has to cleanly leave HVM
closing all handles and freeing memory. So now the only one way to terminate
other thread is sending QUIT request to this thread by hb_threadQuitRequest().
This request is recognized only by HVM code. I adde two functions
hb_vmUnlock() and hb_vmLock() and encapsulate potentially slow file IO
operations between their calls. It gives three things. It inform other threads
that the current one is not HVM active so it's possible to start GC pass (only
one thread can be HVM active during GC mark/scan pass), if necessary stops
execution if some threads forces GC usage and checks for pending QUITE
requests. After calling them, thread in C code may use hb_vmRequestQuery() to
check if there is QUIT request and interrupt the job by simple return. But it
will not interrupt thread waiting on IO operation. In some OSes IO operations
are not cancel points so it's not even techincally possible to cancel thread
in IO operation. For this, it's necessary to intorduce OS dependent mechanism
which we can wait on available events and also for kill signals. F.e. for MS-
Win WiatForMultipleObjects() can be use and it will be enough to add to HVM
function which will return handle to termination semaphore. If events comes
from this semaphore, the thread calls HVM functions to check what has happened
and receives information if it should terminate or can continue its job. But
before we try to implement it, first I suggest to collect some experience from
different OS-es and current to not hard code some local only solution.

> Is it OK to kill a thread if I do not want to listen port any more and
> want to exit application? We do not have function to terminate thread
> among hb_thread*().

When main thread leaves HVM (by calling QUIT or returning from MAIN procedure
then QUIT request is sent to all HVM threads and main thread waits for all
other HVM thread termination. Anyhow it does not break any blocking IO
operations. It has to be implemented in the thread code.

For our own code, f.e. using hb_inet*() functions I hope we will add
respecting HVM signals ASAP. But 3-r dparty code will have to be adapted to
respect it.

>From your own code you can use hb_threadQuitRequest( <pThID> ) which sends
thread request to given thread with blocking the caller and
hb_threadTerminateAll() which sends QUIT request to all threads and waits for
their termination blocking the caller. But as I said above sending QUIT
request will have immediate effect only on .prg code or .c code which uses
hb_vmLock()/hb_vmUnlock() and/or hb_vmRequestQuery() and will not cancel/wake
up threads waiting for blocking IO operations if the code was not adapted for
such situations.

> Sorry, for dumb questions. Just MT is new think for me. And I would
> like to put some communication code into synchronous mode, to avoid
> confusing protocol state machine.

There are not dumb questions but very good ones. They also greatly illustrate
the problems which we have to resolve and which have to be resolved by user.

BTW if you need some working solution now, then you can use simple pooling
with some timeout f.e. few seconds to leave hbinet.c code for a while and your
thread will be terminated inside .prg code immediately.

Example multiThread index.

I want it noticed that it is possible to use MultiThread in index, but, in


practice, in most cases it won't cause speed up because the bottleneck is not
the CPU.
So read for better understanding of MultiThread

/*
  Example multiThreads index.
  One thread by table , and one thread by index.
  2010 Rafa Carmona

  Thread Main
       |--------->  Thhread child table for test.dbf
       |                        |----> Thread child index fname
       |                        |
       |                        |----->Thread child index fcode
       |
       |--------->  Thhread child table for test2.dbf
                               |----->Thread child index fname2

 */
#include "hbthread.ch"

proc Main( uCreate )


   Local nSeconds
   Local aFiles  := { "test", "test2" }        // Arrays files dbf
   Local aNtx    := { { "fname", "fcode" },;   // files index for test
                      { "fName2" } }           // files index for test2
   Local aExpr   := { { "name", "code" },;
                      { "dtos(fecha)+str(code)" }  }     // Expresions
   Local cDbf

   if empty( lCreate )


      lCreate := "0"
   endif

   setmode( 25,80 )
   cls

   if uCreate = "1"


      ? "Create test.dbf and test2.dbf"
      dbCreate("test",{ {"name","C",1,0 },{"code","N",7,0 } } )
      use test
      while lastRec() < 1000000
            dbAppend()
            field->name := chr( recno() )
            field->code := recno()
      enddo
      close
      dbCreate("test2",{ {"fecha","D",8,0 },{"code","N",7,0 } } )
      use test2
      while lastRec() < 1000000
            dbAppend()
            field->fecha := date() + recno()
            field->code := recno()
      enddo
      close
   endif
   cls
   // Threads
   nSeconds := Seconds()
   for each cDbf in aFiles
      ? "Process.: " + cDbf
      hb_threadStart( @aCreateIndexe(), cDbf,
aNtx[ cDbf:__enumindex ],aExpr[ cDbf:__enumindex ], cDbf:__enumindex  )
   next

   ? "Wait for threads ...."


   hb_threadWaitForAll()

   ? hb_valTostr( Seconds() - nSeconds )

   ? "finish"

return

function aCreateIndexe( cFile, aNtx, aExpr, nPosDbf )


      Local nContador := 1
      Local cFileNtx, cExpr
      Local nLong := Len( aNtx )
      Local aThreads := {}
      Local cAlias

      use ( cFile )
      cAlias := alias()
      hb_dbDetach( cAlias )  // Libero el alias

      for each cFileNtx in aNtx


          cExpr  := aExpr[ cFileNtx:__enumindex ]
          nContador := 1
          nPos := cFileNtx:__enumindex
          aadd( aThreads, hb_threadStart( @crea(), cAlias,cExpr, cFileNtx,
nPos, nPosDbf ) )
      next

      aEval( aThreads, { |x| hb_threadJoin( x ) } )  // wait threads childs


      hb_dbRequest( cAlias, , , .T.)  // Restaura el alias
      close

RETURN NIL

proc crea( cAlias, cExpr, cFileNtx, nPos, nPosDbf )


     Local nContador := 1

      hb_dbRequest( cAlias, , , .T.)  // Restaura el alias

      INDEX ON &(cExpr) TO &(cFileNtx) ;


  EVAL {|| hb_dispOutAt( nPosDbf, iif( nPos = 1, 20,
40 ),alltrim( hb_valtostr( nContador) ), "GR+/N" ), nContador += INT(LASTREC()
/ 100 ) , .T. } ;
  EVERY INT( LASTREC() / 100 )
      hb_dbDetach( cAlias )          // Libera el alias

return

You might also like