代码之家  ›  专栏  ›  技术社区  ›  TheWaterProgrammer

如何在线程休眠时唤醒它

  •  14
  • TheWaterProgrammer  · 技术社区  · 6 年前

    我用C++ 11,我有一个 std::thread 它是类成员,每2分钟向侦听器发送一次信息。另外,它只是睡觉。所以,我让它睡了2分钟,然后发送所需的信息,然后再睡2分钟。

    // MyClass.hpp
    class MyClass {
    
        ~MyClass();
        RunMyThread();
    
    private:
        std::thread my_thread;
        std::atomic<bool> m_running;
    }
    
    
    MyClass::RunMyThread() {
    
        my_thread = std::thread { [this, m_running] {
        m_running = true;
        while(m_running) {
            std::this_thread::sleep_for(std::chrono::minutes(2));
            SendStatusInfo(some_info);
        }
    }};
    }
    
    // Destructor
    ~MyClass::MyClass() {
        m_running = false; // this wont work as the thread is sleeping. How to exit thread here?
    }
    

    问题:
    这种方法的问题是,当线程处于休眠状态时,我无法退出它。我从阅读中了解到,我可以用 std::condition_variable 优雅地离开?但我正在努力寻找 简单实例 这是上述场景中所要求的最低限度。所有的 condition_variable 我发现对于我在这里要做的事情来说,这些例子看起来太复杂了。

    问题:
    我怎样才能使用 std::条件变量 唤醒线程并在其睡眠时优雅地退出?或者有没有其他的方法来达到同样的效果 条件变量 技术?

    此外,我发现我需要使用 std::mutex std::条件变量 ?这真的有必要吗?是否无法通过添加 std::条件变量 逻辑只到这里代码中所需的位置?

    环境:
    带有编译器gcc和clang的Linux和Unix。

    8 回复  |  直到 6 年前
        1
  •  5
  •   Maxim Egorushkin    6 年前

    一个可供使用的示例 std::condition_variable :

    struct MyClass {
        MyClass()
            : my_thread([this]() { this->thread(); })
        {}
    
        ~MyClass() {
            {
                std::lock_guard<std::mutex> l(m_);
                stop_ = true;
            }
            c_.notify_one();
            my_thread.join();
        }
    
        void thread() {
            while(this->wait_for(std::chrono::minutes(2)))
                SendStatusInfo(some_info);
        }
    
        // Returns false if stop_ == true.
        template<class Duration>
        bool wait_for(Duration duration) {
            std::unique_lock<std::mutex> l(m_);
            return !c_.wait_for(l, duration, [this]() { return stop_; });
        }
    
        std::condition_variable c_;
        std::mutex m_;
        bool stop_ = false;
        std::thread my_thread;
    };
    
        2
  •  15
  •   Jonathan Wakely    6 年前

    我怎么用 std::condition_variable 唤醒线程并在其休眠时优雅地退出?或者有没有其他的方法来达到同样的效果 condition_variable 技术?

    不,不在C++的标准C++中(当然有非标准的、平台特定的方法来做,而且很可能会将某种信号量添加到C++ 2A中)。

    此外,我发现我需要使用 std::mutex std::条件变量 ?这真的有必要吗?

    对。

    是否无法通过添加 std::条件变量 逻辑只到代码段中的所需位置?

    不,首先,你不能等待 条件变量 如果不锁定互斥体(并将锁定对象传递给wait函数),则无论如何都需要有互斥体。因为无论如何你都必须有一个互斥体,要求服务生和通知者都使用这个互斥体并不是什么大问题。

    条件变量受到“虚假唤醒”的影响,这意味着它们可以毫无理由地停止等待。为了判断它是因为被通知而唤醒,还是被错误地唤醒,您需要一个状态变量,由通知线程设置并由等待线程读取。因为该变量由多个线程共享,所以需要安全地访问它,而互斥体确保了这一点。

    即使对共享变量使用原子变量,通常仍需要互斥来避免错过通知。

    这一切在 https://github.com/isocpp/CppCoreGuidelines/issues/554

        3
  •  8
  •   Slava    6 年前

    如何使用std::condition_变量在线程休眠时唤醒线程并优雅地退出?

    你用 std::condition_variable::wait_for() 而不是 std::this_thread::sleep_for() 第一个可以被 std::condition_variable::notify_one() std::condition_variable::notify_all()

    另外,我看到我需要将std::mutex与std::condition_变量结合使用。这真的有必要吗?是否不可能通过将std::condition_变量逻辑仅添加到代码段中所需的位置来实现目标?

    是的,有必要使用 std::mutex 具有 std::condition_variable 你应该用它而不是做你的旗帜 std::atomic 尽管标记本身具有原子性,但您的代码中会有竞态条件,并且您会注意到,如果您不在这里使用互斥,有时休眠线程会错过通知。

        4
  •  5
  •   SergeyA    6 年前

    有一个可悲但真实的事实——您正在寻找的是一个信号,而POSIX线程没有真正的信号机制。

    另外,与任何类型的时序相关联的唯一POSIX线程基元是条件变量,这就是为什么您的在线搜索引导您去它的原因,并且因为C++线程模型在POSIX API上大量构建,所以在标准C++中POSIX兼容的原语就是您所能得到的。

    除非您愿意离开POSIX(您不表示平台,但有一些本地平台方法可以处理不受这些限制的事件,尤其是 eventfd 在Linux中)您必须坚持使用条件变量,是的,使用条件变量需要一个互斥体,因为它是内置在API中的。

    您的问题并没有特别要求代码示例,所以我没有提供任何示例。如果您要,请告诉我。

        5
  •  1
  •   Yakk - Adam Nevraumont    6 年前

    另外,我看到我需要将std::mutex与std::condition_变量结合使用。这真的有必要吗?是否不可能通过将std::condition_变量逻辑仅添加到代码段中所需的位置来实现目标?

    std::condition_variable 是低级原语。实际上,使用它还需要处理其他低级原语。

    struct timed_waiter {
      void interrupt() {
        auto l = lock();
        interrupted = true;
        cv.notify_all();
      }
      // returns false if interrupted
      template<class Rep, class Period>
      bool wait_for( std::chrono::duration<Rep, Period> how_long ) const {
        auto l = lock();
        return !cv.wait_until( l,
          std::chrono::steady_clock::now() + how_long,
          [&]{
            return !interrupted;
          }
        );
      }
    private:
      std::unique_lock<std::mutex> lock() const {
        return std::unique_lock<std::mutex>(m);
      }
      mutable std::mutex m;
      mutable std::condition_variable cv;
      bool interrupted = false;
    };
    

    简单创建一个 timed_waiter 在某个地方,想要等待的线程和想要中断的代码都可以看到它。

    等待线程可以

    while(m_timer.wait_for(std::chrono::minutes(2))) {
        SendStatusInfo(some_info);
    }
    

    中断做某事 m_timer.interrupt() (在DTOR中说)那么 my_thread.join() 让它结束。

    Live example :

    struct MyClass {
        ~MyClass();
        void RunMyThread();
    private:
        std::thread my_thread;
        timed_waiter m_timer;
    };
    
    
    void MyClass::RunMyThread() {
    
        my_thread = std::thread {
          [this] {
          while(m_timer.wait_for(std::chrono::seconds(2))) {
            std::cout << "SendStatusInfo(some_info)\n";
          }
        }};
    }
    
    // Destructor
    MyClass::~MyClass() {
        std::cout << "~MyClass::MyClass\n";
        m_timer.interrupt();
        my_thread.join();
        std::cout << "~MyClass::MyClass done\n";
    }
    
    int main() {
        std::cout << "start of main\n";
        {
            MyClass x;
            x.RunMyThread();
            using namespace std::literals;
            std::this_thread::sleep_for(11s);
        }
        std::cout << "end of main\n";
    }
    
        6
  •  0
  •   Galik    6 年前

    或者有没有其他方法可以在没有条件的情况下达到相同的效果?

    一个替代方案 条件变量 您可以更定期地唤醒线程,检查“正在运行”标志,如果未设置该标志,并且分配的时间尚未到期,则返回睡眠状态:

    void periodically_call(std::atomic_bool& running, std::chrono::milliseconds wait_time)
    {
        auto wake_up = std::chrono::steady_clock::now();
    
        while(running)
        {
            wake_up += wait_time; // next signal send time
    
            while(std::chrono::steady_clock::now() < wake_up)
            {
                if(!running)
                    break;
    
                // sleep for just 1/10 sec (maximum)
                auto pre_wake_up = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
    
                pre_wake_up = std::min(wake_up, pre_wake_up); // don't overshoot
    
                // keep going to sleep here until full time
                // has expired
                std::this_thread::sleep_until(pre_wake_up);
            }
    
            SendStatusInfo(some_info); // do the regular call
        }
    }
    

    注: 你可以根据自己的需要设定实际的等待时间。在这个例子中,我做了100毫秒 std::chrono::milliseconds(100) . 这取决于您希望线程对停止信号的响应程度。

    例如,在一个应用程序中,我做了整整一秒钟,因为我很高兴我的应用程序在所有线程停止前等待一整秒钟,然后在退出时关闭。

    您需要它的响应能力取决于您的应用程序。起床时间越短越好 CPU 它消耗。然而,即使是非常短的几毫秒的间隔,也可能不会在 中央处理器 时间。

        7
  •  0
  •   OznOg    6 年前

    您还可以使用Promise/Future,这样您就不必费心于条件和/或线程了:

    #include <future>
    #include <iostream>
    
    struct MyClass {
    
        ~MyClass() {
            _stop.set_value();
        }
    
        MyClass() {
            auto future = std::shared_future<void>(_stop.get_future());
            _thread_handle = std::async(std::launch::async, [future] () {
                std::future_status status;
                do {
                    status = future.wait_for(std::chrono::seconds(2));
                    if (status == std::future_status::timeout) {
                        std::cout << "do periodic things\n";
                    } else if (status == std::future_status::ready) {
                        std::cout << "exiting\n";
                    }
                } while (status != std::future_status::ready);
            });
        }
    
    
    private:
        std::promise<void> _stop;
        std::future<void> _thread_handle;
    };
    
    
    // Destructor
    int main() {
        MyClass c;
        std::this_thread::sleep_for(std::chrono::seconds(9));
    }
    
        8
  •  0
  •   apophis42    6 年前

    或者在没有条件变量技术的情况下,有没有其他方法可以达到同样的效果?

    你可以使用 std::promise / std::future 作为比 bool / condition_variable / mutex 在这种情况下。一 future 不易受虚假唤醒的影响,不需要 互斥 用于同步。

    基本实例:

    std::promise<void> pr;
    std::thread thr{[fut = pr.get_future()]{
        while(true)
        {
            if(fut.wait_for(2min) != future_status::timeout)
                return;
        }
    }};
    //When ready to stop
    pr.set_value();
    thr.join();