Hi Mindaugas,
> So,I assume our current code is:
[...]
> and I still have two questions...
> 1) s_lStop access is synchronized using s_mutex. Is hb_backgroundExit()
> code OK? Don't we need to change to:
> function hb_backgroundExit()
> if s_thread != NIL
> hb_mutexLock( s_mutex )
> s_lStop := .t.
> hb_mutexNotify( s_mutex )
> hb_mutexUnLock( s_mutex )
> hb_threadJoin( s_thread )
> endif
> return nil
> otherwise s_lStop can be assigned (by hb_backgroundExit) and accessed
> (by hb_backgroundThread) simultaneous and give unpredictable results.
Yes, access to s_lStop is unprotected but as a side effect of current
implementation it will work because it simple variable which holds result
in memory well aligned which is read/changed by single CPU register call.
so on currently used hardware it will work as we want. Anyhow it's
undocumented side effect of current implementation and probably I should
not show it on public forum.
> And here is a new question (well, 3 new questions...):
> 1a) is it OK to call hb_mutexNotify() if mutex is locked?
Yes it is and hb_mutexNotify() is not blocking function so it will
store notification without blocking calling process.
> 1b) will hb_mutexSubscribe() exit after hb_mutexUnLock() only? (because
> it tries to lock mutex before exit)
Yes it is. It's important for synchronization.
> 1c) if 1b answer is "yes", then code:
> hb_mutexLock( s_mutex )
> s_lStop := .t.
> hb_mutexNotify( s_mutex )
> hb_threadJoin( s_thread )
> hb_mutexUnLock( s_mutex )
> will create deadlock. hb_threadJoin() will wait for child forever, but
> child does not return from hb_mutexSubscribe() because trying to lock
> mutex. Am I right?
Exactly.
> The second question is:
> 2) I assume hb_backgroundAdd() could be called from any thread, and
> hb_backgroundExit() is called from the main thread on application exit.
> It is possible that s_thread != NIL will be false for the main thread,
> but another thread is executing hb_backgroundAdd()/hb_backgroundInit()
> that time. Let's say some code like:
> s_mutex := hb_mutexCreate()
> In this case the new thread will be started, but main thread will forget
> to join it.
Yes it is. And in such case s_thread should be protected by mutex
because it can be complex variable.
I simply assumed that hb_backgroundExit() will be called when main
thread joined all other threads so there is no thread which can call
hb_backgroundAdd()/hb_backgroundInit() simultaneously. If the above
condition is not true then we have to change the code. We can simply
remove s_lStop changing the stop condition and protect all access to
s_thread by mutex:
static s_mutex := hb_mutexCreate()
static s_thread := NIL
static function hb_backgroundThread( )
local n, nTime, nMin, pThID
pThID := hb_threadSelf()
hb_mutexLock( s_mutex )
while s_thread == pThID
nTime := hb_MilliSeconds()
nMin := NIL
for n := 1 to s_nCounter
// {nHandle, bAction, nMilliSec, lActive, nNextTime}
if s_aTasks[ n, 4 ]
if s_aTasks[ n, 5 ] <= nTime
eval( s_aTasks[ n, 2 ] )
s_aTasks[ n, 5 ] += s_aTasks[ n, 3 ]
endif
nMin := IIF( nMin == NIL, s_aTasks[ n, 5 ], ;
MIN( nMin, s_aTasks[ n, 5 ] ) )
endif
next
hb_mutexSubscribe( s_mutex, IIF( nMin == NIL, NIL, nMin / 1000 ) )
enddo
hb_mutexUnlock( s_mutex )
return nil
static function hb_backgroundInit()
if s_thread == NIL
s_thread := hb_threadStart( @hb_backgroundThread() )
endif
return nil
function hb_backgroundExit()
local pThID
hb_mutexLock( s_mutex )
if s_thread != NIL
pThId := s_thread
s_thread := NIL
hb_mutexNotify( s_mutex )
hb_mutexUnlock( s_mutex )
// here it's possible that other thread will activate new
// task manager but we are sure that the current one will
// be finished because we changed s_thread so we can wait
// for it in join operation.
hb_threadJoin( pThID )
else
hb_mutexUnlock( s_mutex )
endif
return nil
function hb_backgroundAdd( bAction, nMilliSec, lActive )
local nHandler
if !HB_ISNUMERIC( nMilliSec )
nMilliSec := 0
endif
if ! HB_ISLOGICAL( lActive )
lActive := .t.
endif
hb_mutexLock( s_mutex )
/* call hb_backgroundInit() after mutex locking */
hb_backgroundInit()
aadd( s_aTasks, { nHandler := ++s_nCounter, bAction, nMilliSec, ;
lActive, hb_MilliSeconds() + nMilliSec } )
hb_mutexNotify( s_mutex )
hb_mutexUnlock( s_mutex )
return nHandler
> 2a) do we need a mutex to protect s_thread access/assign?
If it can be assigned by some thread one other one is accessing
it then yes.
> 2b) can we manage to use the same s_mutex, or we will need s_mutex2?
We can see above. For such simple situation one mutex is enough.
> 2c) where s_mutex2 should be initialized?
The question is where do you want to use it.
> 2d) I see STATIC s_mutex2 := hb_mutexCreate() in one of your last
> commits. So, perhaps 2c answer is "Use STATIC initialization". Is it
> possible to avoid static initialization? Or we will need one more
> s_mutex3 to protect s_mutex2 initialization and this will be
> never-ending story without STATICs.
The problem is 1-st call. As you can see static initialization greatly
resolve it. Anyhow in some cases it may not be used and in such case
I introduced hb_threadOnce() function which needs one variable used
as control item which should be initialized as NIL and then is internally
changed hb_threadOnce() to indicate current initialization state:
2008-10-06 02:18 UTC+0200 Przemyslaw Czerpak (druzus/at/priv.onet.pl)
* added hb_threadOnce( @<onceControl> [, <bAction> ] ) -> <lFirstCall>
This function allow to execute some code only once. It's usefull in
MT environment for initialization.
<onceControl> is variable which holds the execution status and have
to be initialized to NIL. In most of cases it will be simple static
variable in user code.
<bAction> is optional codeblock which is executed only once (on 1-st
call with given <onceControl>)
I used this function in previous version but now it's not necessary now.
Static initialization reduces RT overhead.
best regards,
Przemek
_______________________________________________
Harbour mailing list
[email protected]
http://lists.harbour-project.org/mailman/listinfo/harbour