1
votes

I've a function that accesses(reads and writes to) a std::atomic<bool> variable. I'm trying to understand the order of execution of instructions so as to decide whether atomic will suffice or will I've to use mutexes here. The function is given below -

// somewhere member var 'executing' is defined as std::atomic<bool>`

int A::something(){
    
    int result = 0;
    // my intention is only one thread should enter next block
    // others should just return 0
    if(!executing){
        executing = true;

        ...
        // do some really long processing
        ...
       
        result    = processed;
        executing = false;
    }
    
    return result;
}

I've read this page on cppreference which mentions -

Each instantiation and full specialization of the std::atomic template defines an atomic type. If one thread writes to an atomic object while another thread reads from it, the behavior is well-defined (see memory model for details on data races)

and on Memory model page the following is mentioned -

When an evaluation of an expression writes to a memory location and another evaluation reads or modifies the same memory location, the expressions are said to conflict. A program that has two conflicting evaluations has a data race unless either

  • both conflicting evaluations are atomic operations (see std::atomic)

  • one of the conflicting evaluations happens-before another (see std::memory_order)

If a data race occurs, the behavior of the program is undefined.

and slight below it reads -

When a thread reads a value from a memory location, it may see the initial value, the value written in the same thread, or the value written in another thread. See std::memory_order for details on the order in which writes made from threads become visible to other threads.


This is slightly confusing to me, which one of above 3 statements are actually happening here?

When I perform if(!executing){ is this instruction an atomic instruction here? and more important - is it guaranteed that no other thread will enter that if loop if one two threads will enter that if body since first one will set executing to true?

And if something's wrong with the mentioned code, how should I rewrite it so that it reflects original intention..

1
if(!executing){ executing = true; another thread can get in between these 2 statements. Have a look at std::atomic<>.compare_exchange_XXXRichard Critten
Yes, disable the button, or change its state so that it tells the user what's going on.Mat
No, you said you didn't want to, not that you couldn't, and you said nothing about an API. If you want advice about how to do whatever it is you're trying to achieve, you'll need to provide more accurate details about what that is exactly. (In your question.)Mat
@Mat my question isn't about hiding or showing button or any other interface to the user.Abhinav Gauniyal
Got that. But what is it exactly then? The code you posted is broken in terms of synchronization. If you want to know how to do something correctly, you need to describe that thing correctly too, especially when threads are involved. These things are hard, and missing details or misunderstood requirements usually lead to pain and tears in production.Mat

1 Answers

5
votes

If I understand correctly, you are trying to ensure that only one thread will ever execute a stretch of code at the same time. This is exactly what a mutex does. Since you mentioned that you don't want threads to block if the mutex is not available, you probably want to take a look at the try_lock() method of std::mutex. See the documentation of std::mutex.

Now to why your code does not work as intended: Simplifying a little, std::atomic guarantees that there will be no data races when accessing the variable concurrently. I.e. there is a well defined read-write order. This doesn't suffice for what you are trying to do. Just imagine the if branch:

if(!executing) {
   executing = true;

Remember, only the read-write operations on executing are atomic. This leaves at least the negation ! and the if itself unsynchronized. With two threads, the execution order could be like this:

  1. Thread 1 reads executing (atomically), value is false
  2. Thread 1 negates the value read from executing, value = true
  3. Thread 1 evaluates the condition and enters the branch
  4. Thread 2 reads executing (atomically), value is false
  5. Thread 1 set executing to true
  6. Thread 2 negates the value, which was read as false and is now true again
  7. Thread 2 enters the branch...

Now both threads have entered the branch.

I would suggest something along these lines:

std::mutex myMutex;

int A::something(){

    int result = 0;
    // my intention is only one thread should enter next block
    // others should just return 0
    if(myMutex.try_lock()){

        ...
        // do some really long processing
        ...

        result    = processed;
        myMutex.unlock();
    }

    return result;
}