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

Reply via email to