std::condition_variable::notify_one 和 wait_for 能同时发生吗?

huangapple go评论62阅读模式
英文:

Can std::condition_variable::notify_one and wait_for occurr at the same time?

问题

It seems you want a translation of your code comments and questions. Here's the translated code with comments:

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();
}

I have translated the code comments and left the code as is. If you have any specific questions about the code or its behavior, please let me know.

英文:

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&lt;std::mutex&gt; lk(m);
        task++;
    }
    cv.notify_all();
    LOG(&quot;notify_all&quot;);
}
int main()
{
    std::thread worker(worker_thread2);
    {
        std::unique_lock&lt;std::mutex&gt; lk(m);
        cv.wait_for(lk, std::chrono::milliseconds(5000), [&amp;]{
            if (task == 1) {
                LOG(&quot;1111111&quot;);
                return true;
            } else {
                LOG(&quot;0000000&quot;);
                return false;
            }
        });
    }
    std::string m = &quot;Back in main(), data = &quot; + 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

答案1

得分: 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)

Output:

$ ./a.out 
notify_all
1111111
Back in main(), data = 1
英文:

If I am correct in thinking your main question is "Why do I need std::lock_guard&lt;std::mutex&gt; 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 &lt;iostream&gt;
#include &lt;mutex&gt;
#include &lt;condition_variable&gt;
#include &lt;thread&gt;
#define LOG(x) std::cout &lt;&lt; x

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

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

void worker_thread2(){
  {
    std::lock_guard&lt;std::mutex&gt; lk(m);
    task++;
    sleep_usec(100000);
  }
  cv.notify_all();
  LOG(&quot;notify_all\n&quot;);
}

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

Output:

$ ./a.out 
notify_all
1111111
Back in main(), data = 1

huangapple
  • 本文由 发表于 2023年6月8日 21:28:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76432357.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定