C ++线程安全和notify_all()

时间:2016-04-22 20:50:45

标签: c++ multithreading thread-safety

真正的代码更复杂但我认为我设法制作了一个mcve。

我试图执行以下操作:

  1. 让一些线程正常工作
  2. 将它们全部置于暂停状态
  3. 唤醒他们中的第一个,等待它完成,然后唤醒第二个,等待它完成,唤醒第三个......等等。
  4. 我使用的代码如下,似乎有效

    std::atomic_int which_thread_to_wake_up;
    std::atomic_int threads_asleep;
    threads_asleep.store(0);
    std::atomic_bool ALL_THREADS_READY;
    ALL_THREADS_READY.store(false);
    int threads_num = .. // Number of threads
    bool thread_has_finished = false;
    
    std::mutex mtx;
    std::condition_variable cv;
    
    std::mutex mtx2;
    std::condition_variable cv2;
    
    
    auto threadFunction = [](int my_index) {
    
      // some heavy workload here..
      ....
    
      {
            std::unique_lock<std::mutex> lck(mtx);
            ++threads_asleep;
            cv.notify_all(); // Wake up any other thread that might be waiting
      } 
    
      std::unique_lock<std::mutex> lck(mtx);
      bool all_ready = ALL_THREADS_READY.load();
      size_t index = which_thread_to_wake_up.load();
      cv.wait(lck, [&]() {
          all_ready = ALL_THREADS_READY.load();
          index = which_thread_to_wake_up.load();
          return all_ready && my_index == index;
      });
    
      // This thread was awaken for work!
      .. do some more work that requires synchronization..
    
      std::unique_lock<std::mutex> lck2(mtx2);
      thread_has_finished = true;
      cv2.notify_one(); // Signal to the main thread that I'm done
    };
    
    // launch all the threads..
    std::vector<std::thread> ALL_THREADS;
    for (int i = 0; i < threads_num; ++i)
      ALL_THREADS.emplace_back(threadFunction, i);      
    
    
    // Now the main thread needs to wait for ALL the threads to finish their first phase and go to sleep    
    
    std::unique_lock<std::mutex> lck(mtx);
    size_t how_many_threads_are_asleep = threads_asleep.load();
        while (how_many_threads_are_asleep < threads_num) {
          cv.wait(lck, [&]() {
            how_many_threads_are_asleep = threads_asleep.load();
            return how_many_threads_are_asleep == numThreads;
          });
        }
    
    // At this point I'm sure ALL THREADS ARE ASLEEP!
    
    // Wake them up one by one (there should only be ONE awake at any time before it finishes his computation)
    
    for (int i = 0; i < threads_num; i++) 
    {
      which_thread_to_wake_up.store(i);
      cv.notify_all(); // (*) Wake them all up to check if they're the chosen one
      std::unique_lock<std::mutex> lck2(mtx2);
      cv2.wait(lck, [&]() { return thread_has_finished; }); // Wait for the chosen one to finish
      thread_has_finished = false;
    }
    

    我担心上一次notify_all()电话(我用(*)标记的电话)可能会导致以下情况:

    • 所有线程都睡着了
    • 通过调用notify_all()
    • 从主线程中唤醒所有线程
    • 具有正确索引的线程完成最后一次计算并释放锁
    • 所有其他的线已经被唤醒但是他们已经检查了原子变量
    • 主线程发出第二个notify_all()并且这已经失去了(因为线程已全部被唤醒,它们还没有简单地检查原子)

    这会发生吗?如果notify_all()的调用以某种方式缓冲或与实际检查条件变量的函数同步的顺序,我找不到core-js的任何措辞。

2 个答案:

答案 0 :(得分:0)

根据(notify_all

上的文档

notify_all只是继续线程的一半要求。条件陈述也必须是真实的。所以必须有一个交通警察设计为唤醒第一个,唤醒第二个,唤醒第三个。 notify函数告诉线程检查那个条件。

我的答案比特定代码更高,但我希望有所帮助。

答案 1 :(得分:0)

您考虑的情况可能会发生。如果在调用notify_all()时唤醒您的工作线程(从属),那么它们可能会错过该信号。

防止出现这种情况的一种方法是在mtx之前锁定cv.notify_all()并在之后将其解锁。正如wait()文档中所建议的那样,lock用作pred()访问的保护。如果主线程获取mtx,则没有其他线程在同一时刻检查条件。虽然他们当时可能正在从事其他工作,但在您的代码中他们不太可能再次输入wait