3

I use a mutex to lock and unlock a variable as I call getter from main thread continuously in the update cycle and I call setter from another thread. I provided the code for setter and getter below

Definition

bool _flag;
System::Mutex m_flag;

Calls

#define LOCK(MUTEX_VAR) MUTEX_VAR.Lock();
#define UNLOCK(MUTEX_VAR) MUTEX_VAR.Unlock();

void LoadingScreen::SetFlag(bool value)
{
    LOCK(m_flag);
    _flag = value;
    UNLOCK(m_flag);
}

bool LoadingScreen::GetFlag()
{
    LOCK(m_flag);
    bool value = _flag;
    UNLOCK(m_flag);

    return value;
}

This works well half the time, but at times the variable gets locked on calling SetFlag and hence it is never set thereby disturbing the flow of code.

Can anyone tell me how to solve this issue?

EDIT:

This is the workaround i finally did. This is just a temporary solution. If anyone has a better answer please let me know.

bool _flag;
bool accessingFlag = false;

void LoadingScreen::SetFlag(bool value)
{
    if(!accessingFlag)
    {
        _flag = value;
    }
}

bool LoadingScreen::GetFlag()
{
    accessingFlag = true;
    bool value = _flag;
    accessingFlag = false;

    return value;
}
glo
  • 1,408
  • 3
  • 25
  • 54
  • 3
    Can you show the definitions of `LOCK()` and `UNLOCK()`? – Andy Prowl Feb 27 '13 at 15:20
  • Also returning flag value the way being done is prone to logical error. – Adnan Akbar Feb 27 '13 at 15:22
  • @Andy Prowl I have added the definitions for LOCK() and UNLOCK() – glo Feb 27 '13 at 15:24
  • there's nothing wrong with this bit of the code. (Provided that System::Mutex is valid, I don't know that) – stefan Feb 27 '13 at 15:24
  • @Adnan Akbar can you suggest an alternate way – glo Feb 27 '13 at 15:24
  • This looks OK. The problem must be somewhere else. – Andy Prowl Feb 27 '13 at 15:24
  • Why not actually protecting the critical section rather than trying to do it with a flag? That's why there are mutexes and semaphores. – bash.d Feb 27 '13 at 15:25
  • 2
    Are these the only functions that lock the mutex? The big danger with explicitly unlocking like this (rather than using an RAII lock type as the standard library does) is that it's very easy to leave it locked forever, if the function returns early or throws an exception. But that can't happen in the code you've posted; if this is all the code using the mutex, then perhaps there's a bug in your non-standard `System::Mutex` class. – Mike Seymour Feb 27 '13 at 15:25
  • I have 2 more setters and getters that use different System::Mutex. They work fine. Only this one remains locked. – glo Feb 27 '13 at 15:27
  • set flag is fine , but when getting the value you might get the value which doesnt reflect the latest value (could be changed by other thread). – Adnan Akbar Feb 27 '13 at 15:27
  • 5
    @glo Did you consider using a `std::atomic` instead? No own locking would be needed with that. – stefan Feb 27 '13 at 15:29
  • flag is set on receiving certain data from server. On receiving that the next call is sent. Every data connection call must be sent from main thread in my case and hence we use a flag to set when data is received from communication response thread. This flag is then checked in main thread and next call is made when it is set. – glo Feb 27 '13 at 15:31
  • How have you determined that this is what is causing your problem? It looks correct to me too. – Mats Petersson Feb 27 '13 at 15:31
  • 3
    Get rid of `LOCK` and `UNLOCK`. First, write a RAII locker class: `struct locker { System::Mutex* m; locker(System::Mutex& m_):m(&m_){m->Lock();} void unlock(){if (m)m->Unlock();} ~locker() {unlock();} };`. Replace calls to `LOCK(m_flag);` with `locker lock(m_flag);` `UNLOCK` at the end of a function or other scope-exit for `lock` should be dropped, and in the rare other cases replaced with `lock.unlock();` This should reduce the chance you'll leak a lock to `m_flag` to nearly zilch. – Yakk - Adam Nevraumont Feb 27 '13 at 15:33
  • @glo did you see the code locked up in a debugger, or are you just relying on your print out logs? – Yakk - Adam Nevraumont Feb 27 '13 at 15:34
  • seems boundary level condition, the server doesnt get to know the about new data recieved. Kindly paste the server side code also. – Adnan Akbar Feb 27 '13 at 15:35
  • @Yakk RAII locker class did not work for me. It had same issue as Lock & UNLOCK – glo Mar 04 '13 at 11:37
  • Your EDIT is making things worse, as it introduces a data race both on `accessingFlag` and on `_flag`. I think you will have to show the entire code, the part you originally posted does not seem to have problems. – Andy Prowl Mar 05 '13 at 19:14
  • I know it makes it worse but i don't see any other way to solve locking. – glo Mar 06 '13 at 12:38
  • @glo: Why don't you post your real code, or a code which makes the problem reproducible? – Andy Prowl Mar 06 '13 at 13:00

6 Answers6

2

First of all you should use RAII for mutex lock/unlock. Second you either do not show some other code that uses _flag directly, or there is something wrong with mutex you are using (unlikely). What library provides System::Mutex?

Slava
  • 43,454
  • 1
  • 47
  • 90
  • @BillPringlemeir if mutex is your problem, how it is related to my answer? – Slava Mar 12 '13 at 19:17
  • `RAII` gives the illusion that a mutex is ok. I guess I meant your first sentence should be "If mutexes were needed you should use `RAII`...". There is no reason to use a mutex. They protect nothing, in this case. Somehow they give the illusion that the values are somehow synchronized. Really something like `pthread_cond_wait()` is probably what the initial poster needed. However, there are cases where just atomic read/writes work and in this case, the mutex is not needed. – artless noise Mar 12 '13 at 22:48
  • @BillPringlemeir it is nice that you can see improper use of synchronization. Did you try to think that when I answered there was much less information available? – Slava Mar 13 '13 at 14:17
  • Yes, I thought of that. There are two cases. The getter/setter is 'correct' and the higher level is lock free and needs memory barriers. The 2nd case is the higher level is completely wrong and something like `pthread_cond_wait()` is needed. In no case does `RAII` solve anything; there is no other advice in this answer, just questions. – artless noise Mar 13 '13 at 15:16
2

The issue you have (which user1192878 alludes to) is due to delayed compiler load/stores. You need to use memory barriers to implement the code. You may declare the volatile bool _flag;. But this is not needed with compiler memory barriers for a single CPU system. Hardware barriers (just below in the Wikipedia link) are needed for multi-cpu solutions; the hardware barrier's ensure the local processor's memory/cache is seen by all CPUs. The use of mutex and other interlocks is not needed in this case. What exactly do they accomplish? They just create deadlocks and are not needed.

bool _flag;
#define memory_barrier __asm__ __volatile__ ("" ::: "memory") /* GCC */

void LoadingScreen::SetFlag(bool value)
{
    _flag = value;
    memory_barrier(); /* Ensure write happens immediately, even for in-lines */
}

bool LoadingScreen::GetFlag()
{
   bool value = _flag;
   memory_barrier(); /* Ensure read happens immediately, even for in-lines */
   return value;
}

Mutexes are only needed when multiple values are being set at the same time. You may also change the bool type to sig_atomic_t or LLVM atomics. However, this is rather pedantic as bool will work on most every practical CPU architecture. Cocoa's concurrency pages also have some information on alternative API's to do the same thing. I believe gcc's in-line assembler is the same syntax as used with Apple's compilers; but that could be wrong.

There are some limitations to the API. The instance GetFlag() returns, something can call SetFlag(). GetFlag() return value is then stale. If you have multiple writers, then you can easily miss one SetFlag(). This maybe important if the higher level logic is prone to ABA problems. However, all of these issue exist with/without mutexs. The memory barrier only solves the issue that a compiler/CPU will not cache the SetFlag() for a prolonged time and it will re-read the value in GetFlag(). Declaring volatile bool flag will generally result in the same behavior, but with extra side-effects and does not solve multi-CPU issues.

std::atomic<bool>As per stefan and atomic_set(&accessing_flag, true); will generally do the same thing as describe above in their implementations. You may wish to use them if they are available on your platforms.

Community
  • 1
  • 1
artless noise
  • 21,212
  • 6
  • 68
  • 105
  • Thank you. It seems like this worked and +1 for the detailed explanation. – glo Mar 11 '13 at 05:41
  • @ruben2020: No. `memory barriers` are compiler and/or OS specific. That is why `C++11` has specified `std::atomic` so that you can do it in a *cross-platform* manner. If you don't have this, the wikipedia link has references for how to do it on each platform (the `C++11` library writers do this for you). It is also possible that a processor design can make `hardware memory barriers` impossible; but not for the cell phones **glo** is interested in. – artless noise Mar 11 '13 at 18:28
1

The code looks right if System::Mutex is correctly implemented. Something to be mentioned:

  1. As others pointed out, RAII is better than macro.

  2. It might be better to define accessingFlag and _flag as volatile.

  3. I think the temp solution you got is not correct if you compile with optimization.

    bool LoadingScreen::GetFlag()
    {
      accessingFlag = true;  // might be reordered or deleted
      bool value = _flag;  // might be optimized away
      accessingFlag = false;    // might be reordered before value set
      return value;   // might be optimized to directly returen _flag or register
    }
    
    In above code, optimizer could do nasty things. For example, there is nothing to prevent the compiler eliminate the first assignment to accessingFlag=true, or it could be reordered, cached. For example, for compiler point of view, if single-threaded, the first assignment to accessingFlag is useless because the value true is never used.
  4. Use mutex to protect a single bool variable is expensive since most of time spent on switching OS mode (from kernel to user back and forth). It might not be bad to use a spinlock (detail code depend on your target platform). It should be something like:

    spinlock_lock(&lock); _flag = value; spinlock_unlock(&lock);
  5. Also atomic variable is good here as well. It might look like:
atomic_set(&accessing_flag, true);
user1192878
  • 704
  • 1
  • 10
  • 20
0

Have you considered using CRITICAL_SECTION? This is only available on Windows, so you lose some portability, but it is an effective user level mutex.

0

The second block of code that you provided may modify the flag while it is being read, even in uni processor settings. The original code that you posted is correct, and cannot lead to deadlocks under two assumptions:

  1. The m_flags lock is correctly initialized, and not modified by any other code.
  2. The lock implementation is correct.

If you want a portable lock implementation, I would suggest using OpenMP: How to use lock in openMP?

From your description it seems like you want to busy wait for a thread to process some input. In this case, stefans solution (declare the flag std::atomic) is probably best. On semi-sane x86 systems, you could also declare the flag volatile int. Just don't do this for unaligned fields (packed structures).

You can avoid busy waiting with two locks. The first lock is unlocked by the slave when it finishes processing and locked by the main thread when waiting for the slave to finish. The second lock is unlocked by the main thread when providing input, and locked by the slave when waiting for input.

Community
  • 1
  • 1
DummyDDD
  • 52
  • 1
-1

Here's a technique I've seen somewhere, but couldn't find the source anymore. If I find it, I will edit the answer. Basically, the writer will just write, but the reader will read the value of the set variable more than once, and only when all copies are consistent, it would use it. And I've changed the writer so that it will try to keep writing the value as long as it does not match the value it expects.

bool _flag;

void LoadingScreen::SetFlag(bool value)
{
    do
    {
       _flag = value;
    } while (_flag != value);
}

bool LoadingScreen::GetFlag()
{
    bool value;

    do
    {
        value = _flag;
    } while (value != _flag);

    return value;
}
ruben2020
  • 1,549
  • 14
  • 24
  • -1 Why would you loop in the `SetFlag()`? This can lead to the same deadlock the poster has if there are multiple writers. This is intrinsic to the API and can not be fixed. Imagine two CPUs constantly fighting in `SetFlag()` with different values. Then in the `GetFlag()`, how long do we wish for the variable not to change? Again, this is a limitation in the API of the setter/getter with no way to fix. – artless noise Mar 10 '13 at 22:16
  • I feel evil having given you -1. The answer is wrong, but it shows more thought than some other answers. I had to think about your solution for a little while. – artless noise Mar 11 '13 at 16:23
  • I don't think it makes much difference for a boolean, but if it's more complex, e.g. two variables - hours and minutes - that need to be kept consistent, that both writer and reader need to ensure consistency. While it may seem like we will get into a loop, I don't think that is likely for a multithreaded single-core application because only one will run at a time and eventually prevail. But if it runs on multiple cores simultaneously, then maybe it's a problem. So, it's better to use RAII, atomic variables or other things. – ruben2020 Mar 11 '13 at 16:37
  • The issue is `why loop`? This is the same as `why mutexes`? If the loop is fast, you get a stale value on exit. Ie, when are `value` and `flag` allowed to be non-equal? The instant a method returns they maybe non-equal. So why check? It is the same with the `mutex`. You momentarily have them correct, but they can be incorrect to the caller. So either the interface is broken or the loop/mutex are not needed and introduce extra complications. – artless noise Mar 11 '13 at 18:40