C++: Как реализовать время ожидания для произвольного вызова функции?
Мне нужно вызвать библиотечную функцию, которая, к сожалению, иногда не завершается в течение заданного времени. Есть ли способ вызвать функцию, но прервать ее, если она не заканчивается n
секунд?
Я не могу изменить функцию, поэтому я не могу поместить условие прерывания непосредственно в нее. Я должен добавить таймаут к функции извне.
Возможно ли это решение, чтобы запустить его как (буст) поток, который я могу прекратить через определенное время? Будет ли что-то подобное работать? Я на самом деле считаю, что функция не является поточно-ориентированной, но это не имеет значения, если я запускаю ее как единственный поток, верно? Есть ли другие (лучшие) решения?
9 ответов
Вы могли бы породить boost::thread
вызвать API:
boost::thread api_caller(::api_function, arg1, arg2);
if (api_caller.timed_join(boost::posix_time::milliseconds(500)))
{
// API call returned within 500ms
}
else
{
// API call timed out
}
Boost не позволяет вам уничтожить рабочий поток. В этом примере он просто осиротел.
Вы должны быть осторожны с тем, что делает этот вызов API, потому что он может никогда не высвободить полученные ресурсы.
Я думаю, что единственный безопасный способ сделать это - создать отдельный процесс песочницы, который вызывает функцию библиотеки в качестве прокси для вашего приложения. Вам нужно будет реализовать какой-то тип IPC между вашим приложением и прокси. Внедрение тайм-аута при чтении ответа IPC довольно тривиально. Если при чтении происходит сбой из-за тайм-аута, вы можете безопасно завершить работу прокси-сервера, не рискуя здоровьем вашего приложения.
То, о чем вы говорите, обычно называется "сторожевой" системой. Сторожевой таймер, как правило, является вторым потоком, который проверяет состояние всех других потоков. Сторожевой таймер обычно настроен на периодическую работу. Если от других потоков не было получено никакого ответа, сторожевой таймер может уведомить пользователя или даже уничтожить нарушающий поток, если это возможно сделать безопасно (зависит от вашего приложения).
Execution_monitor Boost.Test делает то, что вы хотите:
http://www.boost.org/doc/libs/1_39_0/libs/test/doc/html/execution-monitor/reference.html
Проблема в том, что с внутрипроцессным решением без поддержки со стороны функции вы можете получить потенциально недопустимое состояние.
Пример. Когда вы завершаете поток во время выделения памяти, ваша куча процесса может быть повреждена.
Таким образом, вы можете завершить вызов, но тогда вам также придется завершить процесс. Во многих случаях шансы на разрушительные побочные эффекты невелики, но я бы не стал делать ставку на это.
Вы можете, как предлагает Бен Штрауб, просто осиротеть поток: поставить его на самый низкий приоритет и позволить ему работать бесконечно. Это, конечно, только ограниченное решение: если поток потребляет ресурсы (вероятно), они будут замедлять работу системы, также существует ограничение на количество потоков на процесс (обычно из-за адресного пространства для стека потоков).
Как правило, я бы предпочел решение для внешнего процесса. Простой шаблон это:
Запишите входные данные в файл, запустите внешний процесс с файлом в качестве аргумента. Внешний процесс записывает ход выполнения (если есть) в файл на диске, который можно отслеживать, и может даже позволить процессу возобновиться с того места, с которого он был запущен. Результаты записываются на диск, и родительский процесс может их читать.
Когда вы завершаете процесс, вам все равно приходится иметь дело с синхронизацией доступа к внешним ресурсам (например, с файлами), а также с тем, как обращаться с заброшенными перехватчиками, наполовину записанными файлами и т. Д. Но это, как правило, ПУТЬ к надежному решению.
Проблема с потоками заключается в том, что некоторые ресурсы вы не сможете освободить после завершения потока. Если вы не приобретаете ресурсы, которые вы должны освободить, переходите к потокам.
"Мне нужно вызвать библиотечную функцию, которая иногда не завершается в течение заданного времени, к сожалению. Есть ли способ вызвать функцию, но прервать ее, если она не завершится в течение n секунд?"
Краткий ответ: нет. Обычно это проблема... Сам вызов должен завершиться в какое-то время (реализуя свой собственный тайм-аут), но блокирование вызовов обычно является проблемой (например, gethostbyname()), потому что тогда это зависит от их (или системного) таймаута, а не от вас.
Поэтому, когда это возможно, старайтесь заставить код, работающий в потоке, корректно завершать работу при необходимости - сам код должен обнаруживать и обрабатывать ошибку. Он может отправлять сообщения и / или устанавливать статусы, чтобы основной (или другой) поток знал, что произошло.
Личные предпочтения, в системах с высокой доступностью, мне нравится, когда мои потоки часто вращаются (хотя и не заняты блокировкой) с определенными таймаутами, вызовом неблокирующих функций и с точными условиями выхода. Глобальная или зависящая от потока переменная 'done' помогает добиться чистого выхода.
Перейти к процессу Orphan, запустить его и время его выполнения. Если время истекает, вызовите ОС, чтобы убить его.
Как избежать гонок по этой схеме:
создайте файл для хранения в args (конечно, все передается как VAL). Сиротскому процессу разрешено только чтение данных из этого файла.
Сирота обрабатывает входные данные, создает выходной файл со значениями результатов и закрывает его.
Только когда все сделано, сирота удаляет входной файл, факт, который сигнализирует основному процессу, что работа была выполнена.
Это позволяет избежать проблемы чтения наполовину записанных файлов, так как мастер сначала замечает отсутствие входного файла, открывает для чтения выходной файл, который обязательно завершен (потому что был закрыт до удаления ввода, а стеки вызовов ОС являются последовательными).