¿Cómo comprobar si un hilo std :: todavía se está ejecutando?

84

¿Cómo puedo comprobar si std::threadtodavía se está ejecutando (de forma independiente de la plataforma)? Carece de un timed_join()método y joinable()no es para eso.

Pensé en bloquear un mutex con un std::lock_guarden el hilo y usar el try_lock()método del mutex para determinar si todavía está bloqueado (el hilo se está ejecutando), pero me parece innecesariamente complejo.

¿Conoces un método más elegante?

Actualización: Para ser claro: quiero verificar si el hilo salió limpiamente o no. Un hilo 'colgante' se considera en ejecución para este propósito.

kispaljr
fuente
Supongo que comprobar si un hilo todavía se está ejecutando solo importa cuando lo esperas wait()y, de ser así, si aún no wait()lo has hecho, debe estar ejecutándose por definición. Pero este razonamiento puede ser inexacto.
ereOn
En realidad, tengo un hilo que sale en condiciones excepcionales, y quiero comprobar desde el hilo principal si todavía se está ejecutando, pero no quiero esperar (unirme) a él
kispaljr
1
¿A qué te refieres exactamente con correr? ¿Quiere decir que se está procesando activamente en lugar de estar en un estado de espera, o quiere decir que el hilo todavía existe y no ha terminado?
CashCow
Siempre puedes usar boost :)
CashCow
4
No debería haber aceptado una respuesta si no estaba satisfecho con ella.
Nicol Bolas

Respuestas:

117

Si está dispuesto a utilizar C ++ 11 std::asyncy std::futureejecutar sus tareas, puede utilizar la wait_forfunción de std::futurepara verificar si el hilo todavía se está ejecutando de una manera ordenada como esta:

#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.
}

Si debe usar std::thread, puede usar std::promisepara obtener un objeto futuro:

#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.
}

Ambos ejemplos darán como resultado:

Thread still running

Por supuesto, esto se debe a que el estado del hilo se comprueba antes de que finalice la tarea.

Pero, de nuevo, podría ser más sencillo hacerlo como otros ya han mencionado:

#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.
}

Editar:

También existe la opción std::packaged_taskde usar std::threadpara una solución más limpia que usar 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
fuente
2
Buena respuesta.
Agregaría
¿Cuál es el motivo de este código std::atomic<bool> done(false);? ¿No es el boolatómico por defecto?
Hi-Angel
6
@YagamyLight En C ++ nada es atómico por defecto a menos que esté envuelto en un std::atomic. sizeof(bool)está definida por la implementación y puede ser> 1, por lo que es posible que se produzca una escritura parcial. También está el problema de la coherencia de la caché ...
Snps
2
@YagamyLight Más información aquí: ¿ Cuándo realmente necesito usar atomic <bool> en lugar de bool?
Snps
1
tenga en cuenta que std :: chrono_literals requerirá C ++ 14 para compilar
Patrizio Bertoni
6

Una solución fácil es tener una variable booleana que el subproceso establezca en verdadero en intervalos regulares, y que el subproceso que desee saber el estado verifique y establezca en falso. Si la variable es falsa durante demasiado tiempo, el hilo ya no se considera activo.

Una forma más segura de subprocesos es tener un contador que aumenta con el subproceso secundario, y el subproceso principal compara el contador con un valor almacenado y, si es el mismo después de demasiado tiempo, el subproceso secundario se considera no activo.

Sin embargo, tenga en cuenta que en C ++ 11 no hay forma de matar o eliminar un hilo que se ha colgado.

Editar Cómo comprobar si un hilo ha salido limpiamente o no: Básicamente, la misma técnica que se describe en el primer párrafo; Tener una variable booleana inicializada en falso. Lo último que hace el subproceso secundario es establecerlo en verdadero. El hilo principal puede verificar esa variable y, si es verdadero, hacer una unión en el hilo secundario sin mucho (si lo hay) bloqueo.

Edit2 Si el hilo sale debido a una excepción, entonces tenga dos funciones "principales" del hilo: La primera tiene un try- catchdentro del cual llama a la segunda función del hilo principal "real". Esta primera función principal establece la variable "have_exited". Algo como esto:

bool thread_done = false;

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

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

    thread_done = true;

    return res;
}
Un tipo programador
fuente
1
Si esa es la definición del OP de "correr".
CashCow
Quizás me entendiste mal. Quiero comprobar si un hilo ha salido limpiamente o no. Perdón por la redacción poco clara.
kispaljr
7
Si diferentes hilos están leyendo y escribiendo thread_done, este código se rompe sin una barrera de memoria. Úselo en su std::atomic<bool>lugar.
ildjarn
1
No me refería a varios subprocesos de trabajo, me refería a un solo subproceso de trabajo que escribe boolmientras el subproceso principal lee de él; esto necesita una barrera de memoria.
ildjarn
3
Consulte esta pregunta para ver una explicación de por qué std::atomic<bool>es necesario aquí.
Robert Rüger
3

Este sencillo mecanismo se puede utilizar para detectar el acabado de un hilo sin bloquear el método de unión.

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

while(thread.joinable())
    sleep(1);
Evgeny Karpov
fuente
2
separar el hilo eventualmente no es lo que uno quiere, y si no lo hace, debe llamar join()desde algún hilo que no espere a que el hilo pierda su joinable()propiedad, de lo contrario, se repetirá sin fin (es decir, joinable()regresará verdadero hasta que el hilo sea realmente join()ed y no hasta que esté terminado)
Niklas R
ensamblable significa que un hilo sujeta un asa de hilo. si el hilo está terminado, aún se podrá unir. Si necesita verificar sin esperar el final del hilo, aquí está la solución. Son solo unas pocas líneas de código. ¿Por qué no lo probaste primero?
Evgeny Karpov
Lo hice, y el punto que estoy tratando de hacer es que si elimina la thread.detach()pieza, el programa anterior nunca terminará.
Niklas R
sí, no lo hará. es por eso que llama desapegarse al final.
Evgeny Karpov
1
Este método de llamar a detach prescinde de la necesidad de mutex y otras soluciones más complejas. ¡Lo uso y funciona! Gracias por la respuesta.
Sep
1

Cree un mutex al que tengan acceso el hilo en ejecución y el hilo que llama. Cuando se inicia el subproceso en ejecución, bloquea el mutex, y cuando termina, lo desbloquea. Para comprobar si el hilo aún se está ejecutando, el hilo de llamada llama a mutex.try_lock (). El valor de retorno de eso es el estado del hilo. (Solo asegúrese de desbloquear el mutex si el try_lock funcionó)

Un pequeño problema con esto, mutex.try_lock () devolverá falso entre el momento en que se crea el hilo y cuando bloquea el mutex, pero esto se puede evitar usando un método un poco más complejo.

Nathan Fox
fuente
-1 No debe usar std::mutexpara este tipo de señalización (principalmente debido a las razones por las que generalmente se implementa mutex). Un atomic_flagfunciona igual de bien en este caso con menos gastos generales. A std::futurepodría ser incluso mejor ya que expresa la intención con mayor claridad. Además, recuerde que try_lockpuede fallar de manera falsa, por lo que la devolución no es necesariamente el estado del hilo (aunque probablemente no le hará mucho daño en este caso particular).
ComicSansMS
1

Siempre puede verificar si la identificación del hilo es diferente a std :: thread :: id () construido por defecto. Un hilo en ejecución siempre tiene un ID asociado genuino. Intenta evitar demasiadas cosas elegantes :)

Michal Turlik
fuente
0

Seguramente tenga una variable envuelta en mutex inicializada en false, que el hilo establece truecomo lo último que hace antes de salir. ¿Es eso lo suficientemente atómico para sus necesidades?

Carreras de ligereza en órbita
fuente
1
Si usa un mutex de todos modos, entonces creo que mi solución (usando solo el mutex, sin el booleano) es más elegante. Si absolutamente desea usar un booleano seguro para subprocesos, recomendaría std :: atomic <bool> en su lugar. En la mayoría de las implementaciones, estará libre de bloqueos.
kispaljr
¿Por qué bloquear en absoluto? Un hilo solo lee, uno solo escribe. Y las escrituras del tamaño de una palabra son atómicas en cualquier caso IIRC.
Xeo
1
@Xeo: La escritura puede ser atómica, pero aún se necesita una barrera de memoria si espera ver el valor escrito en un hilo diferente (que puede estar ejecutándose en una CPU diferente). std::atomic<bool>se encarga de esto por usted, por lo que es la verdadera respuesta en mi opinión.
ildjarn