如何给用户一些指定的时间来回答?

时间:2015-01-22 19:47:56

标签: c++ c++11 time timer

像秒表这样的东西,让使用我的程序的人大约30秒回答,如果没有答案让程序退出? 基本上,响应不应超过给定的时间,否则程序将退出。

2 个答案:

答案 0 :(得分:2)

我发现Axalo的答案很有趣,但std::asyncstd::future的不幸细节却有致命的缺陷。因此,我提出了一个避免std::async的替代方案,但遵循Axalo的基本设计。

当我在我的平台上运行Axalo的答案时(符合相关细节),如果客户端从未回答,getInputWithin永远不会返回或退出。该程序只是挂起。如果客户端在超时内回答良好,getInputWithin会返回正确的答案,但在超时期限到期之前不会这样做。

这个问题的原因很微妙。它在Herb Sutter's excellent paper N3630中有详细描述。 ~std::future()可以阻止std::async()返回async/future并阻止相关任务完成。这个功能是故意放入future的,在某些人的眼中,使r1完全没用。

Axalo的r2std::future就是这样的thread,其析构函数应该阻止,直到相关任务完成为止。这就是为什么如果客户端永远不回答,这个解决方案就会挂起。

以下是从mutexcondition_variablestd::async构建的替代答案。它与Axalo的答案非常相似,但不会受到#include <chrono> #include <condition_variable> #include <iostream> #include <memory> #include <mutex> #include <stdexcept> #include <string> #include <thread> #include <tuple> std::string getInputWithin(std::chrono::seconds timeout) { auto sp = std::make_shared<std::tuple<std::mutex, std::condition_variable, std::string, bool>>(); std::thread([sp]() mutable { std::getline(std::cin, std::get<2>(*sp)); std::lock_guard<std::mutex> lk(std::get<0>(*sp)); std::get<3>(*sp) = true; std::get<1>(*sp).notify_one(); sp.reset(); }).detach(); std::unique_lock<std::mutex> lk(std::get<0>(*sp)); if (!std::get<1>(*sp).wait_for(lk, timeout, [&]() {return std::get<3>(*sp);})) throw std::runtime_error("time out"); return std::get<2>(*sp); } int main() { std::cout << "please answer within 10 seconds...\n"; std::string answer = getInputWithin(std::chrono::seconds(10)); std::cout << answer << '\n'; } 的设计缺陷的影响(<1}}。

chrono

注意:

  • 时间保持在std::chrono::seconds类型系统始终。首选类型int timeoutInSeconds为带有暗示名称的标量(std::chrono::seconds timeout vs std::thread)。

  • 我们需要启动std::cin来处理来自std::mutex的读取,正如Axalo所展示的那样。但是,我们需要std::condition_variablestd::future进行通信,而不是使用std::shared_ptr的便利性。主线程和辅助线程都需要共享这些通信对象的所有权,而我们不知道哪个会先死掉。如果客户端从不响应,则辅助线程可能永远存在,从而产生有效的内存泄漏,这是本文未解决的另一个问题。但无论如何,共享所有权的最简单方法是使用复制 std::thread存储通信对象。最后一个出来了灯。

  • 启动等待std::cin的{​​{1}},并在主线程获得时发出信号。必须在mutex锁定的情况下完成信令。请注意,thread可以(实际上必须)分离。 thread无法触及它不拥有的任何内存(因为shared_ptr拥有所有引用的内存)。如果在辅助线程运行时main退出,操作系统将使线程正常运行而没有UB。

  • 主线程然后锁定mutex并使用指定的超时对wait_for执行condition_variable,并检查bool的谓词在tuple转到true。这个wait_for会在bool设置为true的情况下提前返回,或者会在false秒后将其设置为timeout。如果他们竞争(超时和客户端同时回答)就可以了,或者有string或者booltuple中的wait_for回答了这个问题。而 主线程正在执行mutexbool被解锁,因此辅助线程可以使用它。

  • 如果主线程返回且tuple中的true尚未设置为std::terminate(),则抛出异常。如果未捕获此异常,将调用string。否则,tuple中的shared_ptr会得到客户的回复。

  • 这种方法很容易受到客户端创建许多响应的影响,因为它永远不会回答,因此有效地增加了getInputWithin所保留的永不被破坏的内存泄漏。解决这个问题不是我知道如何在便携式C ++中做的事情。

在C ++ 14中,可以使用tuple稍作修改,这样可以减少选择tuple错误成员的错误。由于我们的std::string getInputWithin(std::chrono::seconds timeout) { auto sp = std::make_shared<std::tuple<std::mutex, std::condition_variable, std::string, bool>>(); std::thread([sp]() mutable { std::getline(std::cin, std::get<std::string>(*sp)); // here std::lock_guard<std::mutex> lk(std::get<std::mutex>(*sp)); // here std::get<bool>(*sp) = true; // here std::get<std::condition_variable>(*sp).notify_one(); // here sp.reset(); }).detach(); std::unique_lock<std::mutex> lk(std::get<std::mutex>(*sp)); // here if (!std::get<std::condition_variable>(*sp).wait_for(lk, timeout, [&]() {return std::get<bool>(*sp);})) // here throw std::runtime_error("time out"); return std::get<std::string>(*sp); // here } 由所有不同类型组成,我们可以按类型而不是按位置对其进行索引:

// here

也就是说,标记为std::get<type>(*sp)的行已更改为std::get<index>(*sp)而非sp.reset()

<强>更新

在下面的TemplateRex的好评中启发的偏执狂中,我已经添加了对tuple的调用作为aux线程的最后一件事。这会强制主线程成为破坏sp的主线程,从而消除了在破坏tuple的本地副本之前辅助线程可能停止的可能性,并让主线程穿过atexit链,然后让aux线程唤醒并运行sp.reset()析构函数。

可能还有其他原因使调用{{1}}变得不必要。但是通过添加这种预防性药物,我们不必担心它。

答案 1 :(得分:1)

如果您不想使用退出并终止该过程,您可以这样做:

std::string getInputWithin(int timeoutInSeconds, bool *noInput = nullptr)
{
    std::string answer;

    bool exceeded = false;
    bool gotInput = false;

    auto r1 = std::async([&answer, &gotInput]()
    {
        std::getline(std::cin, answer);
        gotInput = true;
    });

    auto r2 = std::async([&timeoutInSeconds, &exceeded]()
    {
        std::this_thread::sleep_for(std::chrono::seconds(timeoutInSeconds));
        exceeded = true;
    });

    while(!gotInput && !exceeded)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }

    if(gotInput)
    {
        if(noInput != nullptr) *noInput = false;
        return answer;
    }

    if(noInput != nullptr) *noInput = true;
    return "";
}

int main()
{
    std::cout << "please answer within 10 seconds...\n";

    bool noInput;
    std::string answer = getInputWithin(10, &noInput);

    return 0;
}

关于这一点的好处是你现在可以通过使用默认值来处理丢失的输入,或者只是给用户第二次机会等等......