multiple "lockMutex" from the same thread?

BlitzMax Forums/BlitzMax Programming/multiple "lockMutex" from the same thread?

Rozek(Posted 2010) [#1]
Hello!

Is it safe to "lockMutex" and "tryLockMutex" the same Mutex multiple times from the same thread? (reason: within the "catch" phrase of a "try" statement I would like to "check" - using "trylockmutex" - if the Mutex has been locked in order to unlock it again)


N(Posted 2010) [#2]
They should be recursive, so yes. As for tryLockMutex, it's safe to call that at any time from any thread, I believe, since if the lock cannot be acquired, it should simply return that it can't. However, you'd have to check pthread/Win32 documentation respectively to see how each platform handles those scenarios.

Either way, again, they should be recursive.


Otus(Posted 2010) [#3]
Unless the behavior has changed, mutexes on Windows are recursive, while on Linux and Mac they are not. You can always call TryLock, but it will return 0 if the mutex has already been taken by the same thread (on *nix).

If you need recursive behavior, it is quite simple to wrap mutexes into your own recursive type.


N(Posted 2010) [#4]
Otus: I took a look at the source code for 1.38 and it appears to be recursive now.


Otus(Posted 2010) [#5]
Yeah, sorry. It has been changed since I last tried. Mutexes are recursive on all platforms.


Rozek(Posted 2010) [#6]
Hmmm,

let me explain my situation using some source code:
superstrict

  global Mutex:TMutex = createMutex()

  function ThreadBody:Object (Data:Object)
    debuglog("Subthread started")
      lockMutex(Mutex)
      debuglog("Mutex locked by subthread")

      if (tryLockMutex(Mutex)) then
        debuglog("tryLockMutex succeeded in Subthread")
      else
        debuglog("tryLockMutex failed in Subthread")
      end if

      unlockMutex(Mutex)
      debuglog("Mutex unlocked by subthread")
    debuglog("Subthread finished")
  end function

debuglog("Mainthread started")

debuglog("spawning subthread")
  local Subthread:TThread = createThread(ThreadBody,null)

  delay(2000)

  if (tryLockMutex(Mutex)) then
    debuglog("tryLockMutex succeeded in MainThread")
  else
    debuglog("tryLockMutex failed in MainThread")
  end if
  
debuglog("Mainthread stopped")

If you run that code you will see the following output:
DebugLog:Mainthread started
DebugLog:spawning subthread
DebugLog:Subthread started
DebugLog:Mutex locked by subthread
DebugLog:tryLockMutex succeeded in Subthread
DebugLog:Mutex unlocked by subthread
DebugLog:Subthread finished
DebugLog:tryLockMutex failed in MainThread
DebugLog:Mainthread stopped

Process complete


In practice this means: there is no way to check if a thread has already locked a mutex (itself) or not - because "tryLockMutex" may succeed because the Mutex was not yet locked or because it was already locked by the thread which issued the "tryLockMutex"

Is there a good alternative?

Thanks in advance for any hint!


Rozek(Posted 2010) [#7]
Here is a potential "workaround"
superstrict

  global Mutex:TMutex        = createMutex()
  global MutexLocker:TThread = null

  function _lockMutex (Mutex:TMutex)
    lockMutex(Mutex)
    MutexLocker = currentThread()
  end function

  function _tryLockMutex:int (Mutex:TMutex)
    if (tryLockMutex(Mutex)) then
      if (MutexLocker = currentThread()) then ' Mutex was already locked
        unlockMutex(Mutex) ' reduce internal Mutex count!
      else
        MutexLocker = currentThread()
      end if

      return true
    else
      return false
    end if
  end function

  function _unlockMutex (Mutex:TMutex)
    MutexLocker = null
    unlockMutex(Mutex)
  end function


  function ThreadBody:Object (Data:Object)
    debuglog("Subthread started")
      _lockMutex(Mutex)
      debuglog("Mutex locked by subthread")

      if (_tryLockMutex(Mutex)) then
        debuglog("tryLockMutex succeeded in Subthread")
      else
        debuglog("tryLockMutex failed in Subthread")
      end if

      _unlockMutex(Mutex)
      debuglog("Mutex unlocked by subthread")
    debuglog("Subthread finished")
  end function

debuglog("Mainthread started")

debuglog("spawning subthread")
  local Subthread:TThread = createThread(ThreadBody,null)

  delay(2000)

  if (_tryLockMutex(Mutex)) then
    debuglog("tryLockMutex succeeded in MainThread")
  else
    debuglog("tryLockMutex failed in MainThread")
  end if
  
debuglog("Mainthread stopped")

Are the three functions (_lockMutex, _tryLockMutex and _unlockMutex) free of "race conditions"?


Otus(Posted 2010) [#8]
So you would like to have a reentrant mutex that is not recursive? So you can lock it any number of times from the same thread, but only have to unlock it once?

You are using a single global mutexLocker while passing the mutex as a parameter, which does not seem appropriate. Also, you don't really need to know the thread, just keep a flag to check for double locks:

Type TNRMutex
	
	Field _mutex:TMutex
	
	Field _locked:Int
	
	Method New()
		_mutex = CreateMutex()
	End Method
	
	Method Lock()
		_mutex.Lock
		If _locked Then _mutex.Unlock Else _locked = True
	End Method
	
	Method TryLock:Int()
		If Not _mutex.TryLock() Return False
		If _locked Then _mutex.Unlock Else _locked = True
		Return True
	End Method
	
	Method Unlock()
		_locked = False
		_mutex.Unlock
	End Method
	
End Type


(I would've used inheritance, but unfortunately New TMutex does not do the initialization in Create.)


Rozek(Posted 2010) [#9]
Otus,

thanks! Using a global MutexLocker was just a simple experiment (and test). Meanwhile, I created a type very similar to yours (but still with a MutexLocker that keeps the locking thread in order to catch the situation when a different thread tries to unlock a mutex)

Thanks to "Object Orientation", it's been really simple to adapt my program to the new Mutex type...