-1

like my code, and i learn in https://en.cppreference.com/w/cpp/thread/condition_variable, my question is why i remove lock_guard with task++; then result is wait timeout. i learned, wait check task is 0, then unlock and to wait, but before wait, thread2 run finish task++ and notify, so cause Lost wake-up problem?

so i think should to lock task++ and cv.notify_all(); otherwise, Unable to solve Lost wake-up problem? but in cppreference example, unlock before notify_all, to avoid the waiting thread only to block again.

unlock+wait, wakeup, and lock

wait_for is lock , check, wait, unlock? recv notify is wakeup + lock check exit?

std::mutex m;
std::condition_variable cv;
int task = 0;
void worker_thread2()
{
    {
         // std::lock_guard<std::mutex> lk(m);
        task++;
    }
    cv.notify_all();
    LOG("notify_all");
}
int main()
{
    std::thread worker(worker_thread2);
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait_for(lk, std::chrono::milliseconds(5000), [&]{
            if (task == 1) {
                LOG("1111111");
                return true;
            } else {
                LOG("0000000");
                return false;
            }
        });
    }
    std::string m = "Back in main(), data = " + std::to_string(task);
    LOG(m);
    worker.join();
}

D:\code\c++11\condition_variable\cmake-build-debug\condition_variable.exe 2023-6-8 20:37:15.208 00000002023-6-8 20:37:15.208 notify_all

2023-6-8 20:37:20.210 1111111 2023-6-8 20:37:20.212 Back in main(), data = 1

Process finished with exit code 0

result

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • I'm not quite sure I understand your question. Is it about [spurious wakeups](https://en.wikipedia.org/wiki/Spurious_wakeup)? – Jesper Juhl Jun 08 '23 at 18:17
  • 1
    This is not a [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). For example, the definition of `LOG` is missing, so we can't cut'n'paste, compile and test this. There are also `#include`s missing. – Jesper Juhl Jun 08 '23 at 18:32

1 Answers1

1

If I am correct in thinking your main question is "Why do I need std::lock_guard<std::mutex> lk(m); in worker_thread2" it is because of this requirement listed on the cppreference page you link:

Even if the shared variable is atomic, it must be modified while owning the mutex to correctly publish the modification to the waiting thread.

If you want more details as to exactly why, you can see this answer which explains in more technical detail and links to some other answers.

If you are asking why you are seeing 0000000 printed before 1111111, it is because the condition variable is working as intended. The main thread is locking the mutex before the worker thread has a chance to spawn, so the main thread checks if the predicate (task) is the required value (1 in this case) to continue. Since it is not, it drops ownership of the lock (which is why a unique_lock is required) allowing the worker thread to lock the mutex and modify task, at which point the main thread is notified, wakes up, relocks the mutex, and sees that the task is now the required value.

You can see from this example where I have added delay to your code that if the worker thread locks the mutex first, the main thread does not have a reason to check the predicate before waiting, and in fact, it cannot. (You may need to modify sleep_usec(10); up or down a bit to see what I mean)

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
#define LOG(x) std::cout << x

// Portable usleep() analogue
template <typename T>
void sleep_usec(T duration){
  std::this_thread::sleep_for(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::duration<T, std::micro>(duration)));
}

std::mutex m;
std::condition_variable cv;
int task = 0;

void worker_thread2(){
  {
    std::lock_guard<std::mutex> lk(m);
    task++;
    sleep_usec(100000);
  }
  cv.notify_all();
  LOG("notify_all\n");
}

int main(){
  std::thread worker(worker_thread2);
  sleep_usec(10);
  {
    std::unique_lock<std::mutex> lk(m);
    cv.wait_for(lk, std::chrono::milliseconds(5000), [&]{
      if (task == 1){
        LOG("1111111\n");
        return true;
      } else{
        LOG("0000000\n");
        return false;
      }
      });
  }
  std::string m = "Back in main(), data = " + std::to_string(task) + "\n";
  LOG(m);
  worker.join();
}

Output:

$ ./a.out 
notify_all
1111111
Back in main(), data = 1
Douglas B
  • 585
  • 2
  • 13
  • thx you reply, infact i want to konw, i remove the work thread lock, as result. main thread wait_for‘s condition check failed and work thread notify print the same time, and wait_for real wait 5 second. so i care the Concurrency on wait_for and notify_all. how to fix Lost wake-up problem? My confusion is, add lock in work thread, can not soleve this problem? unless lock task++ and cv.notify_all() together – Windows_Girl Jun 09 '23 at 01:18
  • @Windows_Girl you will not have lost wakeup problems if you do things as shown in the example on cppreference or in my answer; That is, you lock the mutex in the worker, modify the variable, unlock the mutex by leaving its scope, and then notify (note that `notify_one()` would be the best choice here). You should not modify the shared variable (`task`) if you do not first lock the mutex in the worker thread, and you should not call `notify_one/all` while the mutex is still locked, as that could lead to lost wakeup. Perhaps you are misunderstanding how the `lock_guard` is working? – Douglas B Jun 09 '23 at 15:59