Як перевірити, чи все ще працює потік std ::?


84

Як я можу перевірити, чи std::threadвсе ще працює a (незалежно від платформи)? У ньому бракує timed_join()методу, і joinable()він не призначений для цього.

Я думав зафіксувати мьютекс за допомогою а std::lock_guardу потоці та використати try_lock()метод мьютексу, щоб визначити, чи він все ще заблокований (потік запущений), але мені здається це надмірно складним.

Чи знаєте ви більш елегантний метод?

Оновлення: Щоб бути зрозумілим: я хочу перевірити, чи правильно вийшов потік чи ні. "Висяча" нитка вважається запущеною для цієї мети.


Думаю, перевірка, чи працює потік, все-таки має значення лише тоді, коли ви очікуєте на wait()це, і якщо так, якщо ви ще цього не зробили wait(), він повинен працювати за визначенням. Але ці міркування можуть бути неточними.
Ере

Насправді у мене є потік, який виходить у виняткових умовах, і я хочу перевірити з основного потоку, чи він все ще працює, але не хочу чекати (приєднатися)
kispaljr

1
Що саме ви маєте на увазі під бігом? Ви маєте на увазі, що вона активно обробляється, а не в стані очікування, чи ви маєте на увазі, що потік все ще існує і не завершився?
CashCow

Ви завжди можете використати підсилення :)
CashCow

4
Вам не слід було приймати відповідь, якщо ви не були задоволені нею.
Nicol Bolas

Відповіді:


117

Якщо ви бажаєте використовувати 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::threadтоді ви можете використовувати, 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.
}

2
Приємна відповідь. Я хотів би додати, що він також працює з потоками без будь-якого поверненого значення та майбутнього <
уникати

У чому причина цього коду std::atomic<bool> done(false);? Хіба boolатомний не за замовчуванням?
Hi-Angel

6
@YagamyLight У C ++ за замовчуванням ніщо не є атомарним, якщо воно не загорнуте у файл std::atomic. sizeof(bool)є визначеною реалізацією і може бути> 1, тому можливо, що може відбутися часткова запис. Є також питання узгодженості кеш-пам’яті ..
Snps


1
зверніть увагу, що std :: chrono_literals вимагатиме компіляції C ++ 14
Patrizio Bertoni

6

Найпростішим рішенням є наявність логічної змінної, для якої потік встановлює істину через рівні проміжки часу, і яку перевіряє і встановлює значення false потік, який хоче знати статус. Якщо змінна хибна довгий час, тоді потік більше не вважається активним.

Більш безпечним для потоку способом є наявність лічильника, збільшеного на дочірній потік, і основний потік порівнює лічильник із збереженим значенням, і якщо той самий через занадто довгий час, тоді дочірній потік вважається неактивним.

Зауважте, однак, у C ++ 11 немає способу насправді вбити або видалити завислий потік.

Редагувати Як перевірити, чи правильно вийшов потік чи ні: В основному той самий спосіб, що описаний у першому абзаці; Нехай булева змінна ініціалізується як false. Останнє, що робить дочірній потік, - це встановити для нього значення true. Потім основний потік може перевірити цю змінну, і якщо true, то з’єднати дочірній потік без великих (якщо такі є) блокувань.

Edit2 Якщо потік виходить через виняток, тоді є дві основні функції потоку: Перша має a 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

Може, ти мене неправильно зрозумів. Я хочу перевірити, чи потік чисто вийшов чи ні. Вибачте за незрозумілі формулювання.
kispaljr

7
Якщо різні потоки читають і пишуть thread_done, тоді цей код порушується без бар'єру пам'яті. Використовуйте std::atomic<bool>замість цього.
ildjarn

1
Я не мав на увазі кілька робочих потоків, я мав на увазі один робочий потік, який пише в той boolчас, коли основний потік читає з нього - для цього потрібен бар'єр пам'яті.
ildjarn 02

3
Ознайомтеся з цим запитанням, щоб обговорити, чому std::atomic<bool>тут потрібен a .
Роберт Рюгер,

3

Цей простий механізм можна використовувати для виявлення фінішної обробки нитки без блокування методу з'єднання.

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

while(thread.joinable())
    sleep(1);

2
від'єднання потоку в кінцевому підсумку не те, що хочеться, а якщо цього не зробити, то вам доведеться зателефонувати join()з якогось потоку, який не чекає, поки потік втратить своє joinable()властивість, інакше він буде циклічно нескінченно (тобто joinable()повертає true, поки потік насправді join()від і не до кінця)
Niklas R

об'єднується означає, що нитка тримає ручку нитки. якщо нитка зроблена, вона все одно буде доступною. якщо вам потрібно перевірити, не чекаючи кінця потоку, ось рішення. Це лише кілька рядків коду. Чому ти не спробував спочатку?
Євген Карпов

Я зробив це, і головне, що я намагаюся зробити, полягає в тому, що якщо ви видалите thread.detach()деталь, програма вище ніколи не припиниться.
Niklas R

так, не буде. саме тому врешті-решт це називає від'єднання.
Євген Карпов

1
Цей метод виклику відокремлення відмовляється від потреби в мьютексі та інших більш складних рішеннях. Я використовую це, і воно працює! Дякую за відповідь.
вересня

1

Створіть мьютекс, до якого має доступ як запущений потік, так і викличний потік. Коли запущена нитка запускається, вона блокує мьютекс, а коли закінчується - розблоковує мьютекс. Щоб перевірити, чи потік все ще працює, викличний потік викликає mutex.try_lock (). Повернене значення - це статус потоку. (Просто переконайтеся, що розблокуєте мьютекс, якщо функція try_lock спрацювала)

Одна невелика проблема з цим, mutex.try_lock () поверне значення false між часом, коли потік буде створений, і коли він заблокує мьютекс, але цього можна уникнути, використовуючи трохи більш складний метод.


-1 Ви не повинні використовувати std::mutexдля цього виду сигналізації (здебільшого через причини того, як зазвичай реалізується mutex). В atomic_flagтакому випадку твір працює настільки ж добре, що значно менше витрат. A std::futureможе бути навіть кращим, оскільки він чіткіше виражає намір. Крім того, пам’ятайте, що це try_lockможе призвести до помилкових помилок, тому повернення не обов’язково є статусом потоку (хоча це, мабуть, не сильно вам зашкодить).
ComicSansMS

1

Ви завжди можете перевірити, чи ідентифікатор потоку відрізняється від побудованого за замовчуванням std :: thread :: id (). Запуск потоку завжди має справжній асоційований ідентифікатор. Намагайтеся уникати занадто багато вишуканих речей :)


0

Напевно, має змінну, обгорнуту мьютексами, ініційовану до false, яку потік встановлює trueяк останнє, що він робить перед виходом. Чи достатньо цього атомного для ваших потреб?


1
Якщо ви все-таки використовуєте мьютекс, то я вважаю, що моє рішення (використовуючи лише мьютекс, без булевого значення) є більш елегантним. Якщо ви абсолютно хочете використати логічно безпечний потік, я б замість цього рекомендував std :: atomic <bool>. У більшості реалізацій це буде блокування.
kispaljr

Навіщо взагалі блокувати? Один ланцюжок тільки коли-небудь читає, один тільки коли-небудь пише. І записування розміру слова є атомним у будь-якому випадку IIRC.
Xeo

1
@Xeo: запис може бути атомним, але бар'єр пам'яті все ще потрібен, якщо ви очікуєте побачити записане значення в іншому потоці (який може виконуватися на іншому процесорі). std::atomic<bool>подбає про це за вас, саме тому це справжня відповідь IMO.
ildjarn
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.