Что-то вроде секундомера, дайте человеку, который использует мою программу около 30 секунд, чтобы ответить, если нет ответа на выход программы? В основном ответ не должен превышать указанное время, иначе программа выйдет.Как дать пользователю определенное время для ответа?
ответ
Если вы не хотите использовать выход и убить процесс, который вы могли бы сделать это следующим образом:
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
никогда не возвращается и не выходит. Программа просто зависает. И если клиент хорошо отвечает за время ожидания, getInputWithin
возвращается с правильным ответом, но не делает этого до истечения периода ожидания.
Причина этой проблемы тонкая. Он хорошо описан в Herb Sutter's excellent paper N3630. A ~std::future()
может блокировать, если он был возвращен std::async()
и будет блокироваться, пока соответствующая задача не будет выполнена. Эта функция была намеренно помещена в async/future
, и в глазах некоторых, делает future
совершенно бесполезным.
Axalo's r1
и r2
такие std::future
s, чей деструктор должен блокировать до выполнения связанной задачи. И вот почему это решение зависает, если клиент никогда не отвечает.
Ниже приведен альтернативный вариант, который строится от 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
типа всегда. Предпочитают типstd::chrono::seconds
скаляру с наводящим на размышления именем (int timeoutInSeconds
противstd::chrono::seconds timeout
).Нам необходимо запустить
std::thread
, чтобы обработать считывание отstd::cin
, как продемонстрировал Axalo. Однако нам понадобитсяstd::mutex
иstd::condition_variable
для связи, а не для удобства использованияstd::future
. И основной поток, и этот вспомогательный поток должны делиться собственностью этих объектов связи, и мы не знаем, кто умрет первым. Если клиент никогда не отвечает, вспомогательный поток может жить вечно, создавая эффективную утечку памяти, что является еще одной проблемой, которая здесь не решена. Но, во всяком случае, самым простым способом совместного владения является сохранение объектов связи с помощью скопированныхstd::shared_ptr
. В последнем выключен свет.Запуск
std::thread
, который ждетstd::cin
и сигнализирует об основной теме, если она получена. Сигнал должен быть сделан с заблокированным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
, которые никогда не разрушаются. Решение этой проблемы не то, что я знаю, как сделать в переносном 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)
.
Update
В приступе паранойи вдохновленной хороший комментарий от TemplateRex ниже, я добавил вызов sp.reset()
, как последнее, что вспомогательный поток делает. Это заставляет основной поток уничтожать tuple
, исключая возможность того, что поток aux может остановиться, прежде чем уничтожить его локальную копию sp
, и позволить основному удару по цепочке atexit, а затем пробудить и запустить поток aux деструктор tuple
.
Могут быть другие причины, по которым необходимо позвонить sp.reset()
. Но, добавив эту профилактическую медицину, нам не нужно беспокоиться об этом.
Re OS убивает отдельный поток, гарантирует ли это стандарт (и где?) или это QoI от поставщиков ОС? – TemplateRex
3.3.3 [basic.start.term] обсуждает эту проблему и четко заявляет, что если код обращается к стандартным библиотечным объектам (например, std :: cin) после «цепочки atexit», которые я подразумеваю для включения статических деструкторов, то вы в земле УБ. Однако, если программа заканчивается неперехваченным исключением, это вызовет 'terminate()', который по умолчанию вызывает 'abort()', который завершает программу без выполнения «цепочки atexit». Это довольно просто вытаскивает шнур питания из-под вашего приложения (включая все потоки). –
Otoh, если основные выходы обычно, то по логике, созданной 'getInputWithin', поток aux не будет касаться ничего после освобождения блокировки на мьютексе, за исключением подсчетов ссылок shared_ptr, которые могут вызывать деструктор на этом кортеже. Aux-поток и основная гонка, чтобы увидеть, кто разрушает кортеж. Один победит, и один пропустит этот шаг. Если основные гонки впереди и пробиваются через цепочку atexit до того, как поток aux завершит разрушение кортежа и очистку, возможно, будет некоторый теоретический UB, но это звучит довольно далеко. –
GUI или консоль? – cdhowie
не gui, консоль – Lendrit
это звучит как вопрос для http: //ux.stackexchange.com – DLeh