Detener C++ 11 std::hilos esperando una variable std:: condition


Estoy tratando de entender los mecanismos básicos de multihilo en el nuevo estándar C++ 11. El ejemplo más básico que se me ocurre es el siguiente:

  • Un productor y un consumidor se implementan en hilos separados
  • El productor coloca una cierta cantidad de elementos dentro de una cola
  • El consumidor toma artículos de la cola si hay algún presente

Este ejemplo también se usa en muchos libros escolares sobre multihilo y todo sobre el el proceso de comunicación funciona bien. Sin embargo, tengo un problema cuando se trata de detener el hilo del consumidor.

Quiero que el consumidor se ejecute hasta que reciba una señal de stop explícita (en la mayoría de los casos esto significa que espero a que el productor termine para poder detener al consumidor antes de que finalice el programa). Desafortunadamente los subprocesos de C++ 11 carecen de un mecanismo de interrupción (que conozco de multithreading en Java, por ejemplo). Por lo tanto, tengo que usar banderas como isRunning para señalar que quiero un hilo para dejar.

El principal problema ahora es: Después de que he detenido el hilo productor, la cola está vacía y el consumidor está esperando un condition_variable para obtener una señal cuando la cola se llena de nuevo. Así que necesito despertar el hilo llamando a notify_all() en la variable antes de salir.

He encontrado una solución que funciona, pero parece algo desordenado. El código de ejemplo se muestra a continuación (lo siento, pero de alguna manera no pude reducir el tamaño del código para un ejemplo "mínimo" mínimo):

El Clase de cola:

class Queue{
public:
    Queue() : m_isProgramStopped{ false } { }

    void push(int i){
        std::unique_lock<std::mutex> lock(m_mtx);
        m_q.push(i);
        m_cond.notify_one();
    }

    int pop(){
        std::unique_lock<std::mutex> lock(m_mtx);
        m_cond.wait(lock, [&](){ return !m_q.empty() || m_isProgramStopped; });

        if (m_isProgramStopped){
            throw std::exception("Program stopped!");
        }

        int x = m_q.front();
        m_q.pop();

        std::cout << "Thread " << std::this_thread::get_id() << " popped " << x << "." << std::endl;
        return x;
    }

    void stop(){
        m_isProgramStopped = true;
        m_cond.notify_all();
    }

private:
    std::queue<int> m_q;
    std::mutex m_mtx;
    std::condition_variable m_cond;
    bool m_isProgramStopped;
};

El Productor:

class Producer{
public:
    Producer(Queue & q) : m_q{ q }, m_counter{ 1 } { }

    void produce(){
        for (int i = 0; i < 5; i++){
            m_q.push(m_counter++);
            std::this_thread::sleep_for(std::chrono::milliseconds{ 500 });
        }
    }

    void execute(){
        m_t = std::thread(&Producer::produce, this);
    }

    void join(){
        m_t.join();
    }

private:
    Queue & m_q;
    std::thread m_t;

    unsigned int m_counter;
};

El Consumidor:

class Consumer{
public:
    Consumer(Queue & q) : m_q{ q }, m_takeCounter{ 0 }, m_isRunning{ true }
    { }

    ~Consumer(){
        std::cout << "KILL CONSUMER! - TOOK: " << m_takeCounter << "." << std::endl;
    }

    void consume(){
        while (m_isRunning){
            try{
                m_q.pop();
                m_takeCounter++;
            }
            catch (std::exception e){
                std::cout << "Program was stopped while waiting." << std::endl;
            }
        }
    }

    void execute(){
        m_t = std::thread(&Consumer::consume, this);
    }

    void join(){
        m_t.join();
    }

    void stop(){
        m_isRunning = false;
    }

private:
    Queue & m_q;
    std::thread m_t;

    unsigned int m_takeCounter;
    bool m_isRunning;
};

Y, finalmente, el main():

int main(void){
    Queue q;

    Consumer cons{ q };
    Producer prod{ q };

    cons.execute();
    prod.execute();

    prod.join();

    cons.stop();
    q.stop();

    cons.join();

    std::cout << "END" << std::endl;

    return EXIT_SUCCESS;
}

¿Es esta la correcta manera de terminar un hilo que está esperando una variable de condición a o hay mejores métodos? Actualmente, la cola necesita saber si el programa se ha detenido (lo que en mi opinión destruye el acoplamiento suelto de los componentes) y necesito llamar a stop() en la cola explícitamente, lo que no parece correcto.

Además, el la variable de condición que debería usarse como singal si la cola está vacía ahora representa otra condición-si el programa ha terminado. Si no me equivoco, cada vez que un subproceso espera en una variable de condición para que ocurra algún evento, también tendría que verificar si el subproceso tiene que detenerse antes de continuar su ejecución (lo que también parece incorrecto).

Tengo estos problemas porque todo mi diseño es defectuoso o me faltan algunos mecanismos que se pueden usar para salir de los hilos en un ¿camino limpio?

Author: Devon Cornwall, 2014-02-13

2 answers

No, no hay nada malo con su diseño, y es el enfoque normal adoptado para este tipo de problema.

Es perfectamente válido para usted tener múltiples condiciones (por ejemplo, cualquier cosa en la cola o detención del programa) adjunta a una variable de condición. La clave es que los bits en la condición se comprueban cuando regresa wait.

En lugar de tener una bandera en Queue para indicar que el programa se está deteniendo, debe pensar en la bandera como "puedo aceptar". Esta es una mejor paradigma general y funciona mejor en un entorno multihilo.

También, en lugar de tener pop lanzar una excepción si alguien lo llama y stop ha sido llamado, podría reemplazar el método con bool try_pop(int &value) que devolverá true si se devolvió un valor, de lo contrario false. De esta manera el llamante puede comprobar si falla para ver si la cola se ha detenido (agregue un método bool is_stopped() const). Aunque el manejo de excepciones funciona aquí, es un poco pesado y no es realmente un caso excepcional en un multi-threaded programa.

 5
Author: Sean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-13 14:58:43

wait se puede llamar con un tiempo de espera. El control se devuelve al hilo y stop se puede comprobar. Dependiendo de ese valor puede wait en más elementos a ser consumidos o terminar la ejecución. Una buena introducción al multihilo con c++ es C++11 Concurrency.

 1
Author: knivil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-13 15:51:25