Как проверить, работает ли все еще std :: thread?

86

Как я могу проверить, работает ли std::threadеще (независимо от платформы)? У него нет timed_join()метода, и joinable()он не предназначен для этого.

Я думал заблокировать мьютекс с помощью a std::lock_guardв потоке и использовать try_lock()метод мьютекса, чтобы определить, заблокирован ли он по-прежнему (поток выполняется), но мне это кажется излишне сложным.

Вы знаете более изящный метод?

Обновление: для ясности: я хочу проверить, завершился ли поток чисто или нет. Для этой цели «висящая» нить считается запущенной.

Kispaljr
источник
Я предполагаю, что проверка того, работает ли поток по-прежнему, имеет значение только тогда, когда вы этого ожидаете, wait()и если это так, если вы еще этого не сделали wait(), он должен работать по определению. Но это рассуждение может быть неточным.
ereOn 01
На самом деле у меня есть поток, который завершается в исключительных условиях, и я хочу проверить из основного потока, работает ли он все еще, но не хочу ждать (присоединиться) к нему
kispaljr 01
1
Что именно вы имеете в виду под бегом? Вы имеете в виду, что он активно обрабатывается, а не находится в состоянии ожидания, или вы имеете в виду, что поток все еще существует и не завершился?
CashCow 01
Вы всегда можете использовать ускорение :)
CashCow
4
Вы не должны были принимать ответ, если он вас не удовлетворил.
Никол Болас

Ответы:

120

Если вы хотите использовать C ++ 11 std::asyncи std::futureдля выполнения своих задач, вы можете использовать wait_forфункцию, std::futureчтобы проверить, работает ли все еще поток, таким аккуратным способом:

#include <future>
#include <thread>
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono_literals;

    /* Run some task on new thread. The launch policy std::launch::async
       makes sure that the task is run asynchronously on a new thread. */
    auto future = std::async(std::launch::async, [] {
        std::this_thread::sleep_for(3s);
        return 8;
    });

    // Use wait_for() with zero milliseconds to check thread status.
    auto status = future.wait_for(0ms);

    // Print status.
    if (status == std::future_status::ready) {
        std::cout << "Thread finished" << std::endl;
    } else {
        std::cout << "Thread still running" << std::endl;
    }

    auto result = future.get(); // Get result.
}

Если вы должны использовать std::threadthen, вы можете использовать std::promiseдля получения будущего объекта:

#include <future>
#include <thread>
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono_literals;

    // Create a promise and get its future.
    std::promise<bool> p;
    auto future = p.get_future();

    // Run some task on a new thread.
    std::thread t([&p] {
        std::this_thread::sleep_for(3s);
        p.set_value(true); // Is done atomically.
    });

    // Get thread status using wait_for as before.
    auto status = future.wait_for(0ms);

    // Print status.
    if (status == std::future_status::ready) {
        std::cout << "Thread finished" << std::endl;
    } else {
        std::cout << "Thread still running" << std::endl;
    }

    t.join(); // Join thread.
}

Оба этих примера выведут:

Thread still running

Это, конечно, связано с тем, что статус потока проверяется до завершения задачи.

Но опять же, может быть проще просто сделать это, как уже упоминалось другими:

#include <thread>
#include <atomic>
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono_literals;

    std::atomic<bool> done(false); // Use an atomic flag.

    /* Run some task on a new thread.
       Make sure to set the done flag to true when finished. */
    std::thread t([&done] {
        std::this_thread::sleep_for(3s);
        done = true;
    });

    // Print status.
    if (done) {
        std::cout << "Thread finished" << std::endl;
    } else {
        std::cout << "Thread still running" << std::endl;
    }

    t.join(); // Join thread.
}

Редактировать:

Есть также std::packaged_taskдля использования с std::threadболее чистым решением, чем использование std::promise:

#include <future>
#include <thread>
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono_literals;

    // Create a packaged_task using some task and get its future.
    std::packaged_task<void()> task([] {
        std::this_thread::sleep_for(3s);
    });
    auto future = task.get_future();

    // Run task on new thread.
    std::thread t(std::move(task));

    // Get thread status using wait_for as before.
    auto status = future.wait_for(0ms);

    // Print status.
    if (status == std::future_status::ready) {
        // ...
    }

    t.join(); // Join thread.
}
Snps
источник
2
Хороший ответ. Я бы добавил, что он также работает с потоками без какого-либо возвращаемого значения и будущего <void>
kispaljr
В чем причина этого кода std::atomic<bool> done(false);? Разве boolпо умолчанию не атомарный?
Hi-Angel
6
@YagamyLight В C ++ по умолчанию нет ничего атомарного, если оно не заключено в std::atomic. sizeof(bool)определяется реализацией и может быть> 1, поэтому возможна частичная запись. Также существует проблема согласованности кеша ..
Snps
1
обратите внимание, что std :: chrono_literals потребует компиляции C ++ 14
Патрицио Бертони
5

Простое решение - иметь логическую переменную, которой поток устанавливает значение true через регулярные промежутки времени, и которая проверяется и устанавливается в значение false потоком, желающим узнать состояние. Если значение переменной равно false на слишком долгое время, поток больше не считается активным.

Более потокобезопасный способ - иметь счетчик, который увеличивается дочерним потоком, а основной поток сравнивает счетчик с сохраненным значением, и если то же самое по прошествии слишком длительного времени, дочерний поток считается неактивным.

Обратите внимание, однако, что в C ++ 11 нет способа фактически убить или удалить зависший поток.

Изменить Как проверить, завершился ли поток чисто или нет: В основном тот же метод, что описан в первом абзаце; Инициализировать логическую переменную значением false. Последнее, что делает дочерний поток, - устанавливает для него значение true. Затем основной поток может проверить эту переменную и, если это правда, выполнить соединение с дочерним потоком без особой блокировки (если таковая имеется).

Edit2 Если поток завершается из-за исключения, тогда есть две «основные» функции потока: у первой есть try- catchвнутри которой она вызывает вторую «настоящую» функцию основного потока. Эта первая основная функция устанавливает переменную have_exited. Что-то вроде этого:

bool thread_done = false;

void *thread_function(void *arg)
{
    void *res = nullptr;

    try
    {
        res = real_thread_function(arg);
    }
    catch (...)
    {
    }

    thread_done = true;

    return res;
}
Какой-то чувак-программист
источник
1
Если это определение OP для "бега".
CashCow 01
Может вы меня неправильно поняли. Я хочу проверить, завершился ли поток чисто или нет. Извините за непонятную формулировку.
kispaljr 01
7
Если разные потоки читают и пишут thread_done, то этот код не работает без барьера памяти. std::atomic<bool>Вместо этого используйте .
ildjarn 01
1
Я не имел в виду несколько рабочих потоков, я имел в виду, что один рабочий поток записывает в то boolвремя, как основной поток читает из него - для этого нужен барьер памяти.
ildjarn 02
3
Прочтите этот вопрос, чтобы обсудить, почему здесь std::atomic<bool>необходим a .
Роберт Рюгер
2

Этот простой механизм можно использовать для определения завершения потока без блокировки метода соединения.

std::thread thread([&thread]() {
    sleep(3);
    thread.detach();
});

while(thread.joinable())
    sleep(1);
Евгений Карпов
источник
2
отсоединение потока в конечном итоге не то, что нужно, и если вы этого не сделаете, вам придется вызывать join()из какого-то потока, который не ждет, пока поток потеряет свое joinable()свойство, иначе он будет зацикливаться бесконечно (т.е. joinable()возвращает истину, пока поток фактически не join()ed и не до его завершения)
Niklas R
joinable означает, что поток удерживает дескриптор потока. если поток завершен, он все еще может быть присоединен. Если вам нужно проверить, не дожидаясь конца потока, вот решение. Это всего лишь несколько строк кода. Почему ты не попробовал сначала?
Евгений Карпов
Я сделал это и хочу сказать, что если вы удалите thread.detach()деталь, вышеуказанная программа никогда не завершится.
Niklas R
да не будет. вот почему в конце он вызывает отстранение.
Евгений Карпов
1
Этот метод вызова detach избавляет от необходимости использовать мьютекс и другие более сложные решения. Пользуюсь и работает! Спасибо за ответ.
Сентябрь
1

Создайте мьютекс, к которому у запущенного и вызывающего потоков есть доступ. Когда запущенный поток запускается, он блокирует мьютекс, а когда он заканчивается, он разблокирует мьютекс. Чтобы проверить, работает ли поток, вызывающий поток вызывает mutex.try_lock (). Возвращаемое значение - это статус потока. (Просто убедитесь, что мьютекс разблокирован, если try_lock сработал)

Одна небольшая проблема с этим: mutex.try_lock () будет возвращать false между моментом создания потока и блокировкой мьютекса, но этого можно избежать, используя немного более сложный метод.

Натан Фокс
источник
-1 Вы не должны использовать std::mutexдля этого типа сигнализации (в основном по причинам, как обычно реализуется мьютекс). В atomic_flagэтом случае An работает также с гораздо меньшими накладными расходами. А std::futureможет быть даже лучше, поскольку он более четко выражает намерение. Кроме того, помните, что это try_lockможет привести к ложному сбою, поэтому возврат не обязательно является статусом потока (хотя в данном конкретном случае это, вероятно, не повредит вам).
ComicSansMS
1

Вы всегда можете проверить, отличается ли идентификатор потока от созданного по умолчанию std :: thread :: id (). У запущенного потока всегда есть подлинный связанный идентификатор. Старайтесь избегать лишних наворотов :)

Михал Турлик
источник
0

Разумеется, инициализированная переменная, обернутая мьютексом false, устанавливается потоком trueв последнюю очередь перед завершением. Достаточно ли этого атомарного для ваших нужд?

Гонки легкости на орбите
источник
1
Если вы все равно используете мьютекс, я считаю, что мое решение (с использованием только мьютекса, без логического) будет более элегантным. Если вы абсолютно хотите использовать потокобезопасное логическое значение, я бы рекомендовал вместо этого std :: atomic <bool>. В большинстве реализаций он будет без блокировки.
kispaljr 01
Зачем вообще блокировать? Один поток только читает, один только пишет. И запись размером слово в любом случае является атомарной IIRC.
Xeo 01
1
@Xeo: запись может быть атомарной, но барьер памяти все равно необходим, если вы ожидаете увидеть записанное значение в другом потоке (который может выполняться на другом процессоре). std::atomic<bool>позаботится об этом за вас, поэтому это настоящий ответ ИМО.
ildjarn 01