How do I use critical section locks ?

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

How do I use critical section locks ?

Stefan Schoenleitner
Hi,

in my DeviceAdapter I have communication code that should be usable by
multiple threads at the same time.
Basically, it just sends data to the stage controller and waits until
the response is received.
With multiple threads, I need to ensure that after each "send" always a
"receive" is done.

I implemented my communication function in the following way:
-------------------------------------------------------------------
// the following should be atomic
MMThreadGuard myLock(g_communication_lock);

send()
receive()
-------------------------------------------------------------------

In the constructor of MMThreadGuard the mutex is locked, whereas in the
destructor it is unlocked again.
So if another thread calls the above function, it is suspended until the
first thread is done communicating.

However, I discovered that MMThreadGuard does not lock at all !

I tracked it down to MM_THREAD_INITIALIZE_GUARD in
MMDevice/DeviceThreads.h and discovered, that for Linux the locks are
configured as recursive locks.

While usually after the first call to pthread_mutex_lock, a subsequent
call to pthread_mutex_lock() would block until the already locked mutex
is unlocked, with recursive mutexes is does not block at all.
Instead it returns a number indicating how many times a mutex has been
locked.

Why are the locks in micromanager recursive ? Is it due to the windows
thread implementation ?

How is one supposed to use the locking classes MMThreadLock and
MMThreadGuard if they do not block ?

My first idea would have been to wrap a timeout around Lock() by waiting
until the mutex is either locked only once or a timeout occurred.
But then I saw that Lock() does not have a return value.


Regards,
Stefan

------------------------------------------------------------------------------
Get your Android app more play: Bring it to the BlackBerry PlayBook
in minutes. BlackBerry App World™ now supports Android™ Apps
for the BlackBerry® PlayBook™. Discover just how easy and simple
it is! http://p.sf.net/sfu/android-dev2dev
_______________________________________________
micro-manager-general mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/micro-manager-general
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How do I use critical section locks ?

Stefan Schoenleitner
Hi,

On 10/30/2011 12:18 PM, Stefan Schoenleitner wrote:
> Why are the locks in micromanager recursive ? Is it due to the windows
> thread implementation ?

--------------------------------------------------------------------------------------------------
  5023      karlh   // #define MM_THREAD_INITIALIZE_GUARD(plock) pthread_mutex_init(plock, NULL)
  5023      karlh
  5023      karlh #ifdef linux
  5023      karlh   #define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP
  5023      karlh #else
  5023      karlh   /* OS X, ... */
  5023      karlh   #define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE
  5023      karlh #endif
  5023      karlh   #define MM_THREAD_INITIALIZE_GUARD(plock) \
  5023      karlh       { pthread_mutexattr_t a; pthread_mutexattr_init( &a ); \
  5023      karlh         pthread_mutexattr_settype( &a, _MUTEX_RECURSIVE ); \
  5023      karlh         pthread_mutex_init(plock,&a); pthread_mutexattr_destroy( &a ); \
  5023      karlh       }
--------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------
r5023 | karlh | 2010-08-09 21:00:19 +0200 (Mon, 09 Aug 2010) | 1 line
merge from 1.3   --  on Unix, always init the mutex with RECURSIVE attribute so that behavior is as on Windows
--------------------------------------------------------------------------------------------------


I think this is wrong.

On Windows, EnterCriticalSection() always blocks if a mutex is already locked with one exception:
If the thread that locked a mutex tries to lock the same mutex again, it will not block.
"This prevents a thread from deadlocking itself while waiting for a critical section that it already owns."[1]

However, on Linux
"If   the   mutex   is   of   the   ``recursive''   kind,  the  call  to
       pthread_mutex_lock(3) returns immediately with a success  return  code.
       The  number  of  times  the  thread  owning  the mutex has locked it is
       recorded   in   the   mutex.    The    owning    thread    must    call
       pthread_mutex_unlock(3)  the  same  number  of  times  before the mutex
       returns to the unlocked state." [pthread_mutexattr_init manpage]

So if one really wants to have the Windows behavior (which does not block if the owner of the mutex tries to lock it again),
setting a pthread mutex to recursive will not do the job.

The only solution I see at the moment it is to remember which thread successfully locked a mutex (i.e. in a global variable).
It isn't pretty, but I guess it would work.

Still, to me it seems that it is a programming error if a thread that already successfully locked a mutex tries to lock it again.

So why not just revert back to:
        #define MM_THREAD_INITIALIZE_GUARD(plock) pthread_mutex_init(plock, NULL)
?

Regards,
Stefan




[1] http://msdn.microsoft.com/en-us/library/windows/desktop/ms682608%28v=vs.85%29.aspx

------------------------------------------------------------------------------
Get your Android app more play: Bring it to the BlackBerry PlayBook
in minutes. BlackBerry App World™ now supports Android™ Apps
for the BlackBerry® PlayBook™. Discover just how easy and simple
it is! http://p.sf.net/sfu/android-dev2dev
_______________________________________________
micro-manager-general mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/micro-manager-general
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How do I use critical section locks ?

Nico Stuurman-4
In reply to this post by Stefan Schoenleitner
Hi Stefan,

Thanks for bringing this issue up!  It is quite fundamental.

in my DeviceAdapter I have communication code that should be usable by multiple threads at the same time.
Basically, it just sends data to the stage controller and waits until the response is received. With multiple threads, I need to ensure that after each "send" always a "receive" is done.

I implemented my communication function in the following way:
-------------------------------------------------------------------
// the following should be atomic
MMThreadGuard myLock(g_communication_lock);

send()
receive()
-------------------------------------------------------------------

In the constructor of MMThreadGuard the mutex is locked, whereas in the destructor it is unlocked again.
So if another thread calls the above function, it is suspended until the first thread is done communicating.

However, I discovered that MMThreadGuard does not lock at all !

How did you discover that?  Are you sure that the second thread entering this code block (while the first one is still in there) did not block?

I tracked it down to MM_THREAD_INITIALIZE_GUARD in MMDevice/DeviceThreads.h and discovered, that for Linux the locks are configured as recursive locks.

While usually after the first call to pthread_mutex_lock, a subsequent call to pthread_mutex_lock() would block until the already locked mutex is unlocked, with recursive mutexes is does not block at all.

Not true.  With recursive mutexes, a second call to pthread_mutex_lock() will block, but only if it is coming from another thread than the first.

Instead it returns a number indicating how many times a mutex has been locked.

Why are the locks in micromanager recursive ? Is it due to the windows thread implementation ?

Exactly.

How is one supposed to use the locking classes MMThreadLock and MMThreadGuard if they do not block ?

They do block, but only to other thread entering the same critical section.

(Continuing with your next mail to keep things in context):

--------------------------------------------------------------------------------------------------
 5023      karlh #ifdef linux
 5023      karlh   #define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP
 5023      karlh #else
 5023      karlh   /* OS X, ... */
 5023      karlh   #define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE
 5023      karlh #endif
 5023      karlh   #define MM_THREAD_INITIALIZE_GUARD(plock) \
 5023      karlh       { pthread_mutexattr_t a; pthread_mutexattr_init( &a ); \
 5023      karlh         pthread_mutexattr_settype( &a, _MUTEX_RECURSIVE ); \
 5023      karlh         pthread_mutex_init(plock,&a); pthread_mutexattr_destroy( &a ); \
 5023      karlh       }
--------------------------------------------------------------------------------------------------

I think this is wrong.

On Windows, EnterCriticalSection() always blocks if a mutex is already locked with one exception:
If the thread that locked a mutex tries to lock the same mutex again, it will not block.
"This prevents a thread from deadlocking itself while waiting for a critical section that it already owns."[1]

However, on Linux
"If   the   mutex   is   of   the   ``recursive''   kind,  the  call  to
      pthread_mutex_lock(3) returns immediately with a success  return  code.
      The  number  of  times  the  thread  owning  the mutex has locked it is
      recorded   in   the   mutex.    The    owning    thread    must    call
      pthread_mutex_unlock(3)  the  same  number  of  times  before the mutex
      returns to the unlocked state." [pthread_mutexattr_init manpage]

The statement above must refer to the same thread entering this section.   Quoting from http://sourceware.org/pthreads-win32/manual/pthread_mutexattr_init.html :
"The mutex type determines what happens if a thread attempts to lock a mutex it already owns with pthread_mutex_lock(3) ." Of course, a mutex of the "recursive" kind must block to another thread trying to enter the same section (what else would it be doing?).  Thus, it does lead to the same behavior as on Windows.

Still, to me it seems that it is a programming error if a thread that already successfully locked a mutex tries to lock it again.

It may not be best practice, but since different platforms have facilities for this behavior, I would hardly call it a programming error.

In fact, this change to the unix code was introduced since we had a deadlock somewhere in code that mad use of the Micro-Manaer threadguards.  Karl decided on this approach, I do not remember much of the details other than that it successfully dealt with the deadlock.

So why not just revert back to:
#define MM_THREAD_INITIALIZE_GUARD(plock) pthread_mutex_init(plock, NULL)

That will most certainly lead to deadlocks in various places and to different behavior on the different platforms.  

If you want a non-recursive lock, why not add one in DeviceThreads.h?  I would certainly encourage everyone to use non-recursive threads and it would be great to add that behavior so that device adapter writers can use that approach.

Best,

Nico



------------------------------------------------------------------------------
Get your Android app more play: Bring it to the BlackBerry PlayBook
in minutes. BlackBerry App World™ now supports Android™ Apps
for the BlackBerry® PlayBook™. Discover just how easy and simple
it is! http://p.sf.net/sfu/android-dev2dev

_______________________________________________
micro-manager-general mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/micro-manager-general
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How do I use critical section locks ?

Stefan Schoenleitner
Hi Nico,


On 10/30/2011 07:21 PM, Nico Stuurman wrote:
>> However, I discovered that MMThreadGuard does not lock at all !
>
> How did you discover that?  Are you sure that the second thread entering this code block (while the first one is still in there) did not block?

I tested this from the *same* thread. Hence, with regard to the windows thread behavior, it did not block.
Since was expecting it to block, I though there was an error.
(Especially after reading the man page saying that "pthread_mutex_lock(3) returns immediately".)

However, I just wrote a small example program and was able to verify that for different threads, the mutex locking works as expected.

>> However, on Linux
>> "If   the   mutex   is   of   the   ``recursive''   kind,  the  call  to
>>       pthread_mutex_lock(3) returns immediately with a success  return  code.
>>       The  number  of  times  the  thread  owning  the mutex has locked it is
>>       recorded   in   the   mutex.    The    owning    thread    must    call
>>       pthread_mutex_unlock(3)  the  same  number  of  times  before the mutex
>>       returns to the unlocked state." [pthread_mutexattr_init manpage]
>
> The statement above must refer to the same thread entering this section.   Quoting from http://sourceware.org/pthreads-win32/manual/pthread_mutexattr_init.html :
> "The mutex type determines what happens if a thread attempts to lock a mutex it already owns with pthread_mutex_lock(3) ." Of course, a mutex of the "recursive" kind must block to another thread trying to enter the same section (what else would it be doing?).  Thus, it does lead to the same behavior as on Windows.

Indeed. The man page misses the part regarding the blocking to other threads.


> If you want a non-recursive lock, why not add one in DeviceThreads.h?  I would certainly encourage everyone to use non-recursive threads and it would be great to add that behavior so that device adapter writers can use that approach.

I changed DeviceThreads.h like this:
----------------------------------------
  #ifdef MM_THREAD_NONRECURSIVE_LOCK
  #define MM_THREAD_INITIALIZE_GUARD(plock) pthread_mutex_init(plock, NULL)
  #else
  #define MM_THREAD_INITIALIZE_GUARD(plock) \
      { pthread_mutexattr_t a; pthread_mutexattr_init( &a ); \
        pthread_mutexattr_settype( &a, _MUTEX_RECURSIVE ); \
        pthread_mutex_init(plock,&a); pthread_mutexattr_destroy( &a ); \
      }
  #endif
----------------------------------------

So in the code, one can:
----------------------------------------
#define MM_THREAD_NONRECURSIVE_LOCK
#include "DeviceThreads.h"
----------------------------------------

However, this approach has a number of limitations.
Also I'm not sure how to implement this with Windows mutexes.

cheers,
Stefan

------------------------------------------------------------------------------
Get your Android app more play: Bring it to the BlackBerry PlayBook
in minutes. BlackBerry App World™ now supports Android™ Apps
for the BlackBerry® PlayBook™. Discover just how easy and simple
it is! http://p.sf.net/sfu/android-dev2dev
_______________________________________________
micro-manager-general mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/micro-manager-general
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How do I use critical section locks ?

Nico Stuurman-4
Hi Seamus,

Sorry for the delay in answering.  Since your code is waiting for an answer from the device, there is no chance that the thread will hit the same mutes again, so you should be safe to use the current locking mechanism.

>> If you want a non-recursive lock, why not add one in DeviceThreads.h?  I would certainly encourage everyone to use non-recursive threads and it would be great to add that behavior so that device adapter writers can use that approach.
>
> I changed DeviceThreads.h like this:
> ----------------------------------------
>  #ifdef MM_THREAD_NONRECURSIVE_LOCK
>  #define MM_THREAD_INITIALIZE_GUARD(plock) pthread_mutex_init(plock, NULL)
>  #else
>  #define MM_THREAD_INITIALIZE_GUARD(plock) \
>      { pthread_mutexattr_t a; pthread_mutexattr_init( &a ); \
>        pthread_mutexattr_settype( &a, _MUTEX_RECURSIVE ); \
>        pthread_mutex_init(plock,&a); pthread_mutexattr_destroy( &a ); \
>      }
>  #endif
> ----------------------------------------

I was more thinking about adding another lock that behaves non-recursively, or by adding a flag to the existing lock mechanism to give the device adapter author the choice between a recursive and non-recursive lock.  I am not too wild about doing this through a pre-processor directive.

Since we are now including boost in the build, we may as well switch to using the boost locks.  It makes building device adapters a bit more complicated though.

Best,

Nico




------------------------------------------------------------------------------
RSA(R) Conference 2012
Save $700 by Nov 18
Register now
http://p.sf.net/sfu/rsa-sfdev2dev1
_______________________________________________
micro-manager-general mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/micro-manager-general
Loading...