像秒表这样的东西,让使用我的程序的人大约30秒回答,如果没有答案让程序退出? 基本上,响应不应超过给定的时间,否则程序将退出。
答案 0 :(得分:2)
我发现Axalo的答案很有趣,但std::async
和std::future
的不幸细节却有致命的缺陷。因此,我提出了一个避免std::async
的替代方案,但遵循Axalo的基本设计。
当我在我的平台上运行Axalo的答案时(符合相关细节),如果客户端从未回答,getInputWithin
永远不会返回或退出。该程序只是挂起。如果客户端在超时内回答良好,getInputWithin
会返回正确的答案,但在超时期限到期之前不会这样做。
这个问题的原因很微妙。它在Herb Sutter's excellent paper N3630中有详细描述。 ~std::future()
可以阻止std::async()
返回async/future
并阻止相关任务完成。这个功能是故意放入future
的,在某些人的眼中,使r1
完全没用。
Axalo的r2
和std::future
就是这样的thread
,其析构函数应该阻止,直到相关任务完成为止。这就是为什么如果客户端永远不回答,这个解决方案就会挂起。
以下是从mutex
,condition_variable
和std::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_variable
和std::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
或者bool
,tuple
中的wait_for
回答了这个问题。而
主线程正在执行mutex
,bool
被解锁,因此辅助线程可以使用它。
如果主线程返回且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;
}
关于这一点的好处是你现在可以通过使用默认值来处理丢失的输入,或者只是给用户第二次机会等等......