Waiting for an atomic_bool
Asked Answered
C

3

11

I have two threads and a flag that gets set by the second thread. I could use an atomic_bool, but I want to be able to wait* for the flag being set on the first thread. How can I do that?

I can't use a condition_variable I guess, because if the second thread calls notify_one before the first thread starts waiting, the thread won't wake up.

Also, checking if the flag has already been set should be reasonably fast. I guess this should be rather simple, but I'm just stuck, so I'm asking here. Thanks in advance.

*Edit: Block of course, not busy-wait. Sorry if that wasn't clear.

Cacique answered 17/2, 2013 at 11:47 Comment(5)
You're trying to reinvent the conditional variable using an atomic variable - not a good idea. A conditional variable should be good enough, even if wake up is done before sleep.Endo
@Endo So what you're saying is that when I call notify_one and then wait later, the thread should return immediately? Because I've tried it with VS 2012, and that didn't work. Is that an implementation bug?Cacique
@Cacique - It is not a bug, this is how cond variable should behave. Please see fe. en.cppreference.com/w/cpp/thread/condition_variable which has a solution to your problem in an example. Before you are calling wait, you should acquire lock, after acquiring lock you can check your flag to see if it's set, if not - then wait. Because wait will atomically release the lock and wait for notification, you are safe.Parget
When you use a condition variable you also have a flag of some sort, and sit in a loop checking the flag. If the flag is already set you don't wait on the condition variable.Mev
... and you typically protect reads and writes of the flag with a mutex, so if the reader thread has locked the mutex to check the flag you know the writer can't set the flag between the check and waiting on the condition_variable (which atomically unlocks the mutex and waits)Tellurate
C
12

With the help of cbreak and Ravadre (comments) I got from here:

int main()
{
    std::mutex m;
    std::condition_variable cv;

    std::thread t([&] {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            std::unique_lock<std::mutex> lock(m);
            cv.wait(lock);
            std::cout << "Yay!\n";
    });

    cv.notify_one();
    t.join();
}

Which does not usually terminate at all, to here:

int main()
{
    std::mutex m;
    std::condition_variable cv;
    bool flag = false;

    std::thread t([&] {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::unique_lock<std::mutex> lock(m);
        cv.wait(lock, [&] { return flag; });
        std::cout << "Yay!\n";
    });

    {
        std::lock_guard<std::mutex> lock(m);
        flag = true;
    }

    cv.notify_one();
    t.join();
}

Which actually does the job, but still seems like a lot of unnecessary overhead. Feel free to post an equivalent but more performant (or more elegant) answer, I'll happily accept it. Please do only use standard-C++11 though, and if not, explain why standard-C++11 cannot do this.

Edit: I also wrote a class safe_flag to encapsulate this (thanks again to cbreak); feel free to suggest any improvements.

class safe_flag
{
    mutable std::mutex m_;
    mutable std::condition_variable cv_;
    bool flag_;

public:
    safe_flag()
        : flag_(false)
    {}

    bool is_set() const
    {
        std::lock_guard<std::mutex> lock(m_);
        return flag_;
    }

    void set()
    {
        {
            std::lock_guard<std::mutex> lock(m_);
            flag_ = true;
        }
        cv_.notify_all();
    }

    void reset()
    {
        {
            std::lock_guard<std::mutex> lock(m_);
            flag_ = false;
        }
        cv_.notify_all();
    }

    void wait() const
    {
        std::unique_lock<std::mutex> lock(m_);
        cv_.wait(lock, [this] { return flag_; });
    }

    template <typename Rep, typename Period>
    bool wait_for(const std::chrono::duration<Rep, Period>& rel_time) const
    {
        std::unique_lock<std::mutex> lock(m_);
        return cv_.wait_for(lock, rel_time, [this] { return flag_; });
    }

    template <typename Rep, typename Period>
    bool wait_until(const std::chrono::duration<Rep, Period>& rel_time) const
    {
        std::unique_lock<std::mutex> lock(m_);
        return cv_.wait_until(lock, rel_time, [this] { return flag_; });
    }
};
Cacique answered 17/2, 2013 at 12:29 Comment(0)
M
8
bool go = false;
std::mutex mtx;
std::condition_variable cnd;

// waiting thread:
std::unique_lock<std::mutex> lck(mtx);
while (!go)
    cnd.wait(lock);
// when we get here we know that go is true, and we have the lock

// signalling thread:
{
std::unique_lock<std::mutex> lck(mtx);
go = true;
cnd.notify_one();
}
// now we've released the lock, so the waiting thread will make progress
Mev answered 17/2, 2013 at 13:13 Comment(6)
How is that really different from my answer? :)Cacique
@Cacique - the loop is explicit here, making it clearer to beginners what's going on.Mev
Not only explicit, but the syntax of while (!go) is simpler (and less typing) than a lambda expression that does nothing but return a boolTellurate
A condition_variable cannot be passed to functions. So if I have a function in another module file, how can I apply this scheme?Balbriggan
@Balbriggan — pass by reference.Mev
@PeteBecker I do, but I get an error to the tune of explicitly deleted constructor in this scope. This is when I try to pass it from the main window to a dialog in Qt.Balbriggan
S
0

What exactly is your platform? On posix-compliant platforms we use

  sem_t semaphore;
  sem_init( &semaphore , 0 , x );

to get a semaphore with initial value of x. Then with

 sem_wait(&semaphore ); sem_post(&semaphore);

you can synchronize the two threads. Remember to declare semaphore as a global variable to make sure both threads can access it (or by any other means that achieve the same).

So long story short, you can:

sem_t semaphore;
sem_init(&semaphore, 0 , 0 );
void thread2(){
   sem_post(&semaphore);    //second thread --A
}
void thread1(){
    sem_wait(&semaphore);   // wait until thread2() executes line A
}

There should be similar utilities to achieve the same on Win32 too.

Subreption answered 17/2, 2013 at 12:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.