如何在std :: thread休眠时唤醒

时间:2018-10-02 14:43:50

标签: c++ c++11 mutex condition-variable stdthread

我正在使用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::condition_variable唤醒线程并在睡眠时正常退出?还是有其他没有condition_variable技术的方法来实现相同目标?

此外,我还需要将std::mutexstd::condition_variable一起使用吗?那真的有必要吗?仅在此处的代码中必需的位置添加std::condition_variable逻辑就不可能实现目标吗?

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

8 个答案:

答案 0 :(得分:15)

  

如何使用std::condition_variable唤醒线程并在其休眠时正常退出?还是没有condition_variable技术就可以达到同样的目的吗?

不,从C ++ 17开始,不是在标准C ++中使用(当然,有一些非标准的,特定于平台的方法可以执行此操作,并且可能会在C ++ 2a中添加某种信号量)。 / p>

  

此外,我还需要将std::mutexstd::condition_variable一起使用吗?真的有必要吗?

是的

  

是否仅通过在此处的代码段的必需位置添加std::condition_variable逻辑来实现目标?

不。首先,您不能在condition_variable上等待而不锁定互斥锁(并将锁定对象传递给wait函数),因此无论如何都需要有一个互斥锁。由于无论如何都必须有一个互斥锁,所以要求服务员和通知者都使用该互斥锁并不是什么大问题。

条件变量会遭受“虚假唤醒”,这意味着它们可以无故停止等待。为了告诉它是由于通知还是伪造而醒来的,您需要一些由通知线程设置并由等待线程读取的状态变量。由于该变量是由多个线程共享的,因此需要安全地访问它,这是互斥锁确保的。

即使您为共享变量使用原子变量,通常仍需要使用互斥体来避免丢失通知。

所有这些都在下面更详细地说明 https://github.com/isocpp/CppCoreGuidelines/issues/554

答案 1 :(得分:8)

  

如何使用std :: condition_variable唤醒线程并在其休眠时正常退出?

您使用std::condition_variable::wait_for()而不是std::this_thread::sleep_for(),第一个可以被std::condition_variable::notify_one()std::condition_variable::notify_all()中断

  

此外,我还需要将std :: mutex与std :: condition_variable结合使用?那真的有必要吗?通过仅将std :: condition_variable逻辑添加到此处的代码段中的必需位置来实现目标是不可能的吗?

是的,有必要将std::mutexstd::condition_variable一起使用,并且您应该使用它而不是将标志std::atomic用作标志本身,因为尽管标志本身是原子性的,但是您的代码中会存在竞争条件,并且您会发现,如果您在此处不使用互斥锁,有时您的睡眠线程会丢失通知。

答案 2 :(得分:7)

使用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;
};

答案 3 :(得分:5)

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

此外,与任何类型的计时相关的唯一Posix线程原语是条件变量,这就是为什么您的在线搜索将您引向它的原因,并且由于C ++线程模型很大程度上是基于Posix API构建的,因此在标准的C ++ Posix兼容原语中就是全部。

除非您愿意走出Posix(您不指出平台,但是有本机平台来处理没有这些限制的事件,尤其是Linux中的eventfd),否则您必须坚持使用条件变量,是的,使用条件变量需要互斥体,因为它是内置在API中的。

您的问题不是专门询问代码示例,因此我不提供任何示例。让我知道你是否想要一些。

答案 4 :(得分:1)

  

此外,我还需要将std :: mutex与std :: condition_variable结合使用?那真的有必要吗?通过仅将std :: condition_variable逻辑添加到此处的代码段中的必需位置来实现目标是不可能的吗?

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";
}

答案 5 :(得分:0)

  

或者没有条件可变技术还有其他方法可以实现相同目标吗?

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

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
    }
}

注意:您可以根据需要设置实际的等待时间。在此示例中,我将其设置为100ms std::chrono::milliseconds(100)。这取决于您希望线程对信号停止的响应程度。

例如,在一个应用程序中,我花了整整一秒钟的时间,因为我很高兴我的应用程序能够等待一秒钟,以使所有线程在退出之前关闭。

您需要如何响应取决于您的应用程序。唤醒时间越短,消耗的CPU就越多。但是,就CPU时间而言,即使是几毫秒的非常短的间隔也可能不会记录太多。

答案 6 :(得分:0)

  

或者如果没有condition_variable技术,还有其他方法可以实现相同目标吗?

在这种情况下,您可以使用std::promise / std::future作为bool / condition_variable / mutex的更简单替代方法。 future不易受到虚假唤醒,也不需要mutex进行同步。

基本示例:

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

答案 7 :(得分:0)

您还可以使用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));
}