如何给用户一些分配的时间来回答?
How to give the user some assigned time to answer?
类似秒表的东西,给使用我的程序的人大约 30 秒的时间回答,如果没有回答让程序退出?
基本上响应时间不应超过给定的时间,否则程序将退出。
如果您不想使用 exit 并终止进程,您可以这样做:
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;
}
这样做的好处是,您现在可以使用默认值处理丢失的输入,或者简单地给用户第二次机会,等等...
我发现 Axalo 的答案很有趣,但是 std::async
和 std::future
的不幸细节存在致命缺陷。所以我提出了一个替代方案,它避开了 std::async
但在其他方面遵循了 Axalo 的基本设计。
当我在我的平台上 运行 Axalo 的回答(符合相关细节)时,如果客户不回答,getInputWithin
永远不会 returns 或退出。该程序只是挂起。如果客户端在超时内回答正确,getInputWithin
return 会给出正确答案,但在超时期限到期之前不会这样做。
这个问题的原因很微妙。 Herb Sutter's excellent paper N3630 中对其进行了详细描述。如果 return 被 std::async()
编辑,~std::future()
可以阻塞,并且会阻塞直到相关任务完成。此功能是有意放入 async/future
中的,在某些人看来,这会使 future
完全无用。
Axalo 的 r1
和 r2
就是这样的 std::future
,其析构函数应该阻塞直到相关任务完成。这就是为什么如果客户不回答,这个解决方案就会挂起。
以下是根据 thread
、mutex
和 condition_variable
构建的备选答案。它在其他方面与 Axalo 的答案非常相似,但没有(某些人认为的)std::async
.
的设计缺陷
#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';
}
备注:
时间保持在 chrono
类型系统 always 内。更喜欢类型 std::chrono::seconds
而不是具有暗示性名称的标量(int timeoutInSeconds
与 std::chrono::seconds timeout
)。
我们需要启动一个 std::thread
来处理来自 std::cin
的读取,正如 Axalo 演示的那样。然而,我们将需要 std::mutex
和 std::condition_variable
来进行通信,而不是使用 std::future
的便利性。主线程和这个辅助线程都需要共享这些通信对象的所有权,我们不知道哪个先死。如果客户端从不响应,辅助线程可能永远存在,造成有效的内存泄漏,这是这里没有解决的另一个问题。但无论如何,共享所有权的最简单方法是使用 copied std::shared_ptr
存储通信对象。最后一个熄灯了
启动一个 std::thread
等待 std::cin
并在主线程得到它时向主线程发出信号。信号必须在 mutex
锁定的情况下完成。请注意,此 thread
可以(实际上必须)分离。 thread
不能触及任何不属于它的内存(因为 shared_ptr
拥有所有引用的内存)。如果 main
在辅助线程处于 运行ning 时退出,OS 将在没有 UB 的情况下优雅地关闭线程。
主线程然后锁定 mutex
并使用指定的超时在 condition_variable
上执行 wait_for
,以及检查 bool
中的tuple
转为true
。此 wait_for
将 return 提早 bool
设置为 true
,或者将 return 设置为 false
后 [=55] =]秒。如果他们比赛(超时和客户同时回答)没关系,要么会有 string
那里或没有,bool
在 tuple
回答那个问题。尽管
主线程正在执行 wait_for
,mutex
已解锁,因此辅助线程可以使用它。
如果主线程returns和tuple
中的bool
没有设置为true
,则抛出异常.如果这个异常没有被捕获,std::terminate()
将被调用。否则,tuple
中的string
会有客户端的响应。
这种方法很容易受到客户端创建许多它从不回答的响应的影响,从而有效地增加 shared_ptr
永远不会被破坏的内存泄漏。解决这个问题不是我知道如何在可移植 C++ 中做的事情。
在 C++14 中,可以对 getInputWithin
进行轻微修改,从而减少选择错误的 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)
。
更新
受到下面 TemplateRex 的好评的启发,我有点偏执,我添加了对 sp.reset()
的调用作为辅助线程所做的最后一件事。这迫使主线程成为破坏 tuple
的线程,消除了辅助线程在破坏其 sp
的本地副本之前停止的可能性,并让主线程通过 atexit 链,然后让辅助线程唤醒并 运行 tuple
析构函数。
可能还有其他原因使对 sp.reset()
的调用变得不必要。但是有了这种预防药,我们就不用担心了。
类似秒表的东西,给使用我的程序的人大约 30 秒的时间回答,如果没有回答让程序退出? 基本上响应时间不应超过给定的时间,否则程序将退出。
如果您不想使用 exit 并终止进程,您可以这样做:
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;
}
这样做的好处是,您现在可以使用默认值处理丢失的输入,或者简单地给用户第二次机会,等等...
我发现 Axalo 的答案很有趣,但是 std::async
和 std::future
的不幸细节存在致命缺陷。所以我提出了一个替代方案,它避开了 std::async
但在其他方面遵循了 Axalo 的基本设计。
当我在我的平台上 运行 Axalo 的回答(符合相关细节)时,如果客户不回答,getInputWithin
永远不会 returns 或退出。该程序只是挂起。如果客户端在超时内回答正确,getInputWithin
return 会给出正确答案,但在超时期限到期之前不会这样做。
这个问题的原因很微妙。 Herb Sutter's excellent paper N3630 中对其进行了详细描述。如果 return 被 std::async()
编辑,~std::future()
可以阻塞,并且会阻塞直到相关任务完成。此功能是有意放入 async/future
中的,在某些人看来,这会使 future
完全无用。
Axalo 的 r1
和 r2
就是这样的 std::future
,其析构函数应该阻塞直到相关任务完成。这就是为什么如果客户不回答,这个解决方案就会挂起。
以下是根据 thread
、mutex
和 condition_variable
构建的备选答案。它在其他方面与 Axalo 的答案非常相似,但没有(某些人认为的)std::async
.
#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';
}
备注:
时间保持在
chrono
类型系统 always 内。更喜欢类型std::chrono::seconds
而不是具有暗示性名称的标量(int timeoutInSeconds
与std::chrono::seconds timeout
)。我们需要启动一个
std::thread
来处理来自std::cin
的读取,正如 Axalo 演示的那样。然而,我们将需要std::mutex
和std::condition_variable
来进行通信,而不是使用std::future
的便利性。主线程和这个辅助线程都需要共享这些通信对象的所有权,我们不知道哪个先死。如果客户端从不响应,辅助线程可能永远存在,造成有效的内存泄漏,这是这里没有解决的另一个问题。但无论如何,共享所有权的最简单方法是使用 copiedstd::shared_ptr
存储通信对象。最后一个熄灯了启动一个
std::thread
等待std::cin
并在主线程得到它时向主线程发出信号。信号必须在mutex
锁定的情况下完成。请注意,此thread
可以(实际上必须)分离。thread
不能触及任何不属于它的内存(因为shared_ptr
拥有所有引用的内存)。如果main
在辅助线程处于 运行ning 时退出,OS 将在没有 UB 的情况下优雅地关闭线程。主线程然后锁定
mutex
并使用指定的超时在condition_variable
上执行wait_for
,以及检查bool
中的tuple
转为true
。此wait_for
将 return 提早bool
设置为true
,或者将 return 设置为false
后 [=55] =]秒。如果他们比赛(超时和客户同时回答)没关系,要么会有string
那里或没有,bool
在tuple
回答那个问题。尽管 主线程正在执行wait_for
,mutex
已解锁,因此辅助线程可以使用它。如果主线程returns和
tuple
中的bool
没有设置为true
,则抛出异常.如果这个异常没有被捕获,std::terminate()
将被调用。否则,tuple
中的string
会有客户端的响应。这种方法很容易受到客户端创建许多它从不回答的响应的影响,从而有效地增加
shared_ptr
永远不会被破坏的内存泄漏。解决这个问题不是我知道如何在可移植 C++ 中做的事情。
在 C++14 中,可以对 getInputWithin
进行轻微修改,从而减少选择错误的 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)
。
更新
受到下面 TemplateRex 的好评的启发,我有点偏执,我添加了对 sp.reset()
的调用作为辅助线程所做的最后一件事。这迫使主线程成为破坏 tuple
的线程,消除了辅助线程在破坏其 sp
的本地副本之前停止的可能性,并让主线程通过 atexit 链,然后让辅助线程唤醒并 运行 tuple
析构函数。
可能还有其他原因使对 sp.reset()
的调用变得不必要。但是有了这种预防药,我们就不用担心了。