Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Industrial programming >> Lenguaje C

Dominar la multihilo en C++: Programación concurrente eficiente

La multihilo es una forma especializada de multitarea, y la multitarea es la característica que permite a tu computadora ejecutar dos o más programas concurrentemente. En general, existen dos tipos de multitarea: basada en procesos y basada en hilos.

La multitarea basada en procesos maneja la ejecución concurrente de programas. La multitarea basada en hilos trata la ejecución concurrente de partes del mismo programa.

Un programa multihilo contiene dos o más partes que pueden ejecutarse concurrentemente. Cada parte de dicho programa se llama hilo, y cada hilo define una ruta de ejecución separada.

C++ no contiene soporte integrado para aplicaciones multihilo. En su lugar, depende completamente del sistema operativo para proporcionar esta característica.

Este tutorial asume que estás trabajando en Linux OS y vamos a escribir un programa multihilo en C++ usando POSIX. POSIX Threads, o Pthreads, proporciona una API disponible en muchos sistemas tipo Unix POSIX como FreeBSD, NetBSD, GNU/Linux, macOS y Solaris.

Creación de hilos

La siguiente rutina se usa para crear un hilo POSIX:

#include <pthread.h>
pthread_create(thread, attr, start_routine, arg)

Aquí, pthread_create crea un nuevo hilo y lo hace ejecutable. Esta rutina puede llamarse cualquier número de veces desde cualquier parte de tu código. Aquí está la descripción de los parámetros:

Nº Parámetro y descripción
1 thread
Identificador único y opaco para el nuevo hilo devuelto por la subrutina.
2 attr
Objeto de atributo opaco que puede usarse para establecer atributos del hilo. Puedes especificar un objeto de atributos de hilo, o NULL para valores por defecto.
3 start_routine
La rutina de C++ que el hilo ejecutará una vez creado.
4 arg
Un único argumento que puede pasarse a start_routine. Debe pasarse por referencia como un puntero casteado a tipo void. NULL puede usarse si no se pasa ningún argumento.

El número máximo de hilos que puede crear un proceso depende de la implementación. Una vez creados, los hilos son pares y pueden crear otros hilos. No hay jerarquía ni dependencia implícita entre hilos.

Terminación de hilos

Existe la siguiente rutina para terminar un hilo POSIX:

#include <pthread.h>
pthread_exit(status)

Aquí pthread_exit se usa para salir explícitamente de un hilo. Típicamente, la rutina pthread_exit() se llama después de que un hilo ha completado su trabajo y ya no necesita existir.

Si main() termina antes que los hilos que ha creado, y sale con pthread_exit(), los otros hilos continuarán ejecutándose. De lo contrario, se terminarán automáticamente cuando main() finalice.

Ejemplo

Este código simple crea 5 hilos con la rutina pthread_create(). Cada hilo imprime un mensaje "¡Hola Mundo!" y luego termina con una llamada a pthread_exit().

#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

void *PrintHello(void *threadid) {
    long tid;
    tid = (long)threadid;
    cout << "¡Hola Mundo! ID de hilo, " << tid << endl;
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    int rc;
    int i;

    for(i = 0; i < NUM_THREADS; i++) {
        cout << "main() : creando hilo, " << i << endl;
        rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
        if(rc) {
            cout << "Error: no se puede crear hilo," << rc << endl;
            exit(-1);
        }
    }

    pthread_exit(NULL);
}

Compila el siguiente programa usando la biblioteca -lpthread de la siguiente manera:

$gcc test.cpp -lpthread

Ahora, ejecuta tu programa que produce la siguiente salida:

main() : creando hilo, 0
main() : creando hilo, 1
main() : creando hilo, 2
main() : creando hilo, 3
main() : creando hilo, 4
¡Hola Mundo! ID de hilo, 0
¡Hola Mundo! ID de hilo, 1
¡Hola Mundo! ID de hilo, 2
¡Hola Mundo! ID de hilo, 3
¡Hola Mundo! ID de hilo, 4

Pasar argumentos a hilos

Este ejemplo muestra cómo pasar múltiples argumentos mediante una estructura. Puedes pasar cualquier tipo de datos en una devolución de llamada de hilo porque apunta a void, como se explica en el siguiente ejemplo:

#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

struct thread_data {
    int thread_id;
    char *message;
};

void *PrintHello(void *threadarg) {
    struct thread_data *my_data;
    my_data = (struct thread_data *) threadarg;
    cout << "ID de hilo: " << my_data->thread_id;
    cout << " Mensaje: " << my_data->message << endl;
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    struct thread_data td[NUM_THREADS];
    int rc;
    int i;

    for(i = 0; i < NUM_THREADS; i++) {
        cout << "main() : creando hilo, " << i << endl;
        td[i].thread_id = i;
        td[i].message = "Este es el mensaje";
        rc = pthread_create(&threads[i], NULL, PrintHello, (void *)&td[i]);
        if(rc) {
            cout << "Error: no se puede crear hilo," << rc << endl;
            exit(-1);
        }
    }

    pthread_exit(NULL);
}

Cuando el código anterior se compila y ejecuta, produce el siguiente resultado:

main() : creando hilo, 0
main() : creando hilo, 1
main() : creando hilo, 2
main() : creando hilo, 3
main() : creando hilo, 4
ID de hilo: 3 Mensaje: Este es el mensaje
ID de hilo: 2 Mensaje: Este es el mensaje
ID de hilo: 0 Mensaje: Este es el mensaje
ID de hilo: 1 Mensaje: Este es el mensaje
ID de hilo: 4 Mensaje: Este es el mensaje

Unir y separar hilos

Existen las siguientes dos rutinas que podemos usar para unir o separar hilos:

pthread_join(threadid, status)
pthread_detach(threadid)

La subrutina pthread_join() bloquea el hilo llamador hasta que el hilo especificado 'threadid' termina. Cuando se crea un hilo, uno de sus atributos define si es unible o separado. Solo los hilos creados como unibles pueden unirse. Si un hilo se crea como separado, nunca puede unirse.

Este ejemplo demuestra cómo esperar la finalización de hilos usando la rutina de unión de Pthread.

#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>

using namespace std;

#define NUM_THREADS 5

void *wait(void *t) {
    int i;
    long tid;
    tid = (long)t;
    sleep(1);
    cout << "Durmiendo en hilo " << endl;
    cout << "Hilo con id: " << tid << " ...saliendo " << endl;
    pthread_exit(NULL);
}

int main() {
    int rc;
    int i;
    pthread_t threads[NUM_THREADS];
    pthread_attr_t attr;
    void *status;

    // Inicializar y establecer hilo como unible
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for(i = 0; i < NUM_THREADS; i++) {
        cout << "main() : creando hilo, " << i << endl;
        rc = pthread_create(&threads[i], &attr, wait, (void *)i);
        if(rc) {
            cout << "Error: no se puede crear hilo," << rc << endl;
            exit(-1);
        }
    }

    // Liberar atributo y esperar a los otros hilos
    pthread_attr_destroy(&attr);
    for(i = 0; i < NUM_THREADS; i++) {
        rc = pthread_join(threads[i], &status);
        if(rc) {
            cout << "Error: no se puede unir," << rc << endl;
            exit(-1);
        }
        cout << "Main: hilo completado id: " << i;
        cout << " saliendo con estado: " << status << endl;
    }

    cout << "Main: programa saliendo." << endl;
    pthread_exit(NULL);
}

Cuando el código anterior se compila y ejecuta, produce el siguiente resultado:

main() : creando hilo, 0
main() : creando hilo, 1
main() : creando hilo, 2
main() : creando hilo, 3
main() : creando hilo, 4
Durmiendo en hilo
Hilo con id: 0 .... saliendo
Durmiendo en hilo
Hilo con id: 1 .... saliendo
Durmiendo en hilo
Hilo con id: 2 .... saliendo
Durmiendo en hilo
Hilo con id: 3 .... saliendo
Durmiendo en hilo
Hilo con id: 4 .... saliendo
Main: hilo completado id:0 saliendo con estado:0
Main: hilo completado id:1 saliendo con estado:0
Main: hilo completado id:2 saliendo con estado:0
Main: hilo completado id:3 saliendo con estado:0
Main: hilo completado id:4 saliendo con estado:0
Main: programa saliendo.

Lenguaje C

  1. Dominando los Operadores en C++: Guía Completa con Ejemplos Prácticos
  2. Comentarios en C++: Mejores Prácticas para Código Legible y Mantenible
  3. Dominando las plantillas de clase en C++: guía práctica
  4. Lenguaje de Programación C++: Visión General y Características Clave
  5. Constantes y Literales en C++: Tipos, Sintaxis y Mejores Prácticas
  6. Guía Completa de Operadores en C++: Tipos, Uso y Mejores Prácticas
  7. Dominando los Tipos Numéricos en C++: Guía Práctica
  8. Entendiendo las Referencias en C++: Conceptos Clave y Diferencias con Punteros
  9. Dominando Plantillas en C++: Guía de Programación Genérica
  10. Dominar la multihilo en C++: Programación concurrente eficiente
  11. Multihilo en C#: Mejora el rendimiento y la eficiencia