Я использую комбинацию std::async
а также std::future
от C++ 11
, Я использую, чтобы принудительно установить time_out для определенного действия, которое я делаю в своем коде, который может быть занять время, как я пытаюсь подключиться к серверу.
Вот как выглядит код:
#include <future>
#include <chrono>
std::size_t PotentiallyLongRunningActivity() {
using namespace std::chrono_literals;
std::this_thread::sleep_for(10000s);
return 10;
}
bool DoActivity() {
bool activity_done = false;
auto my_future_result(std::async(std::launch::async, []() {
return PotentiallyLongRunningActivity(); //returns size_t
}));
std::future_status my_future_status = my_future_result.wait_for(std::chrono::milliseconds(800));
if (my_future_status == std::future_status::timeout) {
activity_done = false;
}
else if (my_future_status == std::future_status::ready) {
if (my_future_result.valid() && my_future_result.get() > 0) {
activity_done = true;
}
}
return activity_done;
//my_future_result hangs while exiting this method !!!
}
int main(int argc, char *argv[])
{
DoActivity();
return 0;
}
Вещи работают нормально в большинстве случаев. Будущее время & сообщается о готовности во многих случаях. Но странное поведение, которое я наблюдаю, заключается в том, что в некоторых случаях пользовательский интерфейс зависает, потому что my_future_result
при выходе за рамки зависает. Я подтвердил это, повторив звонок my_future_result.get()
который никогда не возвращается, если вызывается непосредственно перед выходом из метода.
Как я могу обойти это? Есть ли способ отменить или удалить или прекратить std::future
?
Принимая из образец сравнения, только «начало», «f2 закончено» и «конец» будут напечатаны из этого кода (потому что f1
не «зависает»):
#include <future>
#include <thread>
#include <iostream>
int main() {
using namespace std::literals;
{
std::packaged_task<int()> task([]() {
std::this_thread::sleep_for(5s);
std::cout << "f1 finished" << std::endl;
return 42;
});
std::future<int> f1 = task.get_future();
std::thread(std::move(task)).detach();
std::future<int> f2 = std::async(std::launch::async, []() {
std::this_thread::sleep_for(3s);
std::cout << "f2 finished" << std::endl;
return 42;
});
f1.wait_for(1s);
f2.wait_for(1s);
std::cout << "the start" << std::endl;
}
// std::this_thread::sleep_for(7s);
std::cout << "the end" << std::endl;
}
Для хорошего обсуждения смотрите: http://scottmeyers.blogspot.com.br/2013/03/stdfutures-from-stdasync-arent-special.html.
Стандартная библиотека C ++ не поддерживает операции уничтожения потоков.
Берегите себя нитями detach
, Отстранение само по себе не является «чрезвычайно плохим», оно может быть полезно, например, в демонах, которые могут быть завершены пользователем, или если у вас есть какое-то другое представление об оркестровке и разрыве. Иначе, detach
не имеет смысла быть предоставленным стандартной библиотекой.
Вы выходите из своей функции до std::async
задание выполнено При определенных обстоятельствах деструктор для std::future
будет блокироваться, пока задача не будет завершена.
http://en.cppreference.com/w/cpp/thread/future/wait_for
Здесь в документации для wait_for
пример показывает несколько звонков wait_for
после тайм-аута, показывая, что акт тайм-аута не отменяет std::async
задача.
Нет встроенной поддержки (которую я мог бы обнаружить), которая позволяла бы уничтожать потоки извне. Это имеет смысл, поскольку нет способа правильно очистить состояние системных ресурсов, используемых потоком, если он завершается таким образом.
Вместо этого лучше поместить логику тайм-аута в сам поток, чтобы он мог завершить себя и очистить должным образом.
Как правило, потерять след потока очень плохо. Наличие кода, запущенного в другом потоке, когда main
выход является получателем неопределенного поведения.
Таким образом, std::future
который возвращается из std::async
имеет специальное свойство, которое он будет ждать std::async
завершить, когда он будет уничтожен.
Это то, что вы описываете как «зависание». Это не зависание — оно ждет завершения задачи.
Примитивы потоков в C ++ 11 являются примитивами; они не являются законченными типами для полнофункционального приложения. Вы можете использовать их для записи отложенных очередей задач пулов потоков и т. П .; наивное использование их «в сыром виде» приводит к тому, что они склоняются к правильности, но не дают вам того, что вы хотите.
Простой пул потоков просто:
template<class T>
struct threaded_queue {
using lock = std::unique_lock<std::mutex>;
void push_back( T t ) {
{
lock l(m);
data.push_back(std::move(t));
}
cv.notify_one();
}
boost::optional<T> pop_front() {
lock l(m);
cv.wait(l, [this]{ return abort || !data.empty(); } );
if (abort) return {};
auto r = std::move(data.back());
data.pop_back();
return std::move(r);
}
void terminate() {
{
lock l(m);
abort = true;
data.clear();
}
cv.notify_all();
}
~threaded_queue()
{
terminate();
}
private:
std::mutex m;
std::deque<T> data;
std::condition_variable cv;
bool abort = false;
};
struct thread_pool {
thread_pool( std::size_t n = 1 ) { start_thread(n); }
thread_pool( thread_pool&& ) = delete;
thread_pool& operator=( thread_pool&& ) = delete;
~thread_pool() = default; // or `{ terminate(); }` if you want to abandon some tasks
template<class F, class R=std::result_of_t<F&()>>
std::future<R> queue_task( F task ) {
std::packaged_task<R()> p(std::move(task));
auto r = p.get_future();
tasks.push_back( std::move(p) );
return r;
}
template<class F, class R=std::result_of_t<F&()>>
std::future<R> run_task( F task ) {
if (threads_active() >= total_threads()) {
start_thread();
}
return queue_task( std::move(task) );
}
void terminate() {
tasks.terminate();
}
std::size_t threads_active() const {
return active;
}
std::size_t total_threads() const {
return threads.size();
}
void clear_threads() {
terminate();
threads.clear();
}
void start_thread( std::size_t n = 1 ) {
while(n-->0) {
threads.push_back(
std::async( std::launch::async,
[this]{
while(auto task = tasks.pop_front()) {
++active;
try{
(*task)();
} catch(...) {
--active;
throw;
}
--active;
}
}
)
);
}
}
private:
std::vector<std::future<void>> threads;
threaded_queue<std::packaged_task<void()>> tasks;
std::atomic<std::size_t> active;
};
Теперь вы где-то создаете некоторый пул потоков, отбрасываете на него задачи, и вы можете ждать рассматриваемого будущего. В пуле есть ограниченное количество потоков.
run_task
обеспечит наличие потока для запуска любой задачи, поставленной в очередь. queue_task
будет использовать только существующие потоки, если они доступны.
Возвращенный std::future<void>
не блокирует выполнение задачи; но thread_pool
Деструктор объекта делает.
Обратите внимание, что он прервет все поставленные в очередь задачи и будет ждать завершения текущих задач по умолчанию при уничтожении.
Что-то, что оборачивает unique_ptr<thread_pool>
полезно, чтобы облегчить перемещение. Перемещение должно быть отключено, потому что активные потоки сохраняют указатель наthis
,
thread_pool
по иронии судьбы не безопасна для ниток; это потому что мы не охраняем std::vector<std::future<void>> threads;
; Я имею в виду, кроме потоковой безопасности в отношении самих потоков потоков. Он предназначен для прямого доступа только к одному внешнему потоку.
queue_task
а также terminate
Поток безопасен в основном случайно.
Причина ошибки в том, что компилятору не сказали, что результат вашей функции DoModify () будет доступен асинхронно (следовательно, он будет объявлен как std :: future<>) и ожидал синхронного результата типа bool, который так и не поступил. Вы можете использовать std :: future :: is_ready () или std :: future_status. Вот пример кода
std::future<size_t> DoActivity()
{
return std::async(std::launch::async, []()
{
return PotentiallyLongRunningActivity();
});
}
int main()
{
auto result = DoActivity();
if ( result. Is_ready() )
{
auto data = result.get();
//do something with data
}
}