Crear procesos con for en C++

Jinku Hu 12 octubre 2023
  1. Use fork() para crear dos procesos dentro del programa en C++
  2. Use fork() y execve para crear múltiples procesos en C++
  3. Use fork() y execve para crear múltiples procesos con la función de limpieza automática de los hijos en C++
Crear procesos con for en C++

Este artículo explicará varios métodos de cómo crear procesos con una llamada de sistema fork() en C++.

Use fork() para crear dos procesos dentro del programa en C++

La función for es la llamada de sistema compatible con POSIX disponible en la mayoría de los sistemas operativos basados en Unix. La función crea un nuevo proceso, que es un duplicado del programa de llamada original. Este último proceso se llama parent y el recién creado child. Estos dos procesos pueden ser vistos como los dos hilos que se ejecutan en espacios de memoria separados. Nótese que la implementación actual de Linux no tiene el concepto de hilo internamente, por lo que los hilos son estructuras similares a los procesos, excepto que comparten regiones de memoria. La función fork puede implementar la ejecución simultánea dentro del mismo programa o ejecutar un nuevo ejecutable desde el sistema de archivos (demostrado en los ejemplos posteriores).

En el siguiente ejemplo, utilizamos fork para demostrar el multiprocesamiento dentro del mismo programa. fork no toma ningún argumento y devuelve en ambos procesos. El valor de retorno es el PID del hijo en el proceso padre, y 0 es devuelto en el proceso hijo. En caso de que la llamada falle, se devuelve -1 en el proceso padre. Por lo tanto, podemos construir declaraciones if basadas en la evaluación del valor de retorno, y cada bloque if es ejecutado por el proceso correspondiente, resultando en una ejecución concurrente.

#include <sys/wait.h>
#include <unistd.h>

#include <iostream>

using std::cout;
using std::endl;

int main() {
  pid_t c_pid = fork();

  if (c_pid == -1) {
    perror("fork");
    exit(EXIT_FAILURE);
  } else if (c_pid > 0) {
    cout << "printed from parent process " << getpid() << endl;
    wait(nullptr);
  } else {
    cout << "printed from child process " << getpid() << endl;
    exit(EXIT_SUCCESS);
  }

  return EXIT_SUCCESS;
}

Producción :

printed from parent process 27295
printed from child process 27297

Use fork() y execve para crear múltiples procesos en C++

Un uso más práctico de la llamada de la función fork es crear múltiples procesos y ejecutar diferentes programas dentro de estos procesos. Observe que, en este ejemplo, necesitamos dos archivos de código fuente: uno para el proceso padre y el otro para los procesos hijos. El código del proceso hijo es el simple bucle infinito que se añade al único entero y puede ser detenido enviando la señal SIGTERM.

El programa padre declara un nombre de archivo que necesita ser ejecutado por los procesos hijos bifurcados y luego llama a la función spawnChild 6 veces. La función spawnChild envuelve las llamadas fork/execve y devuelve el ID del proceso recién creado. Fíjate que execve requiere un nombre de programa y una lista de argumentos como argumentos para lanzar un nuevo código de programa dentro de los procesos hijo. Una vez que los 6 procesos hijo son creados, el padre continúa en el bucle while donde llama a la función wait. wait detiene el proceso padre y espera hasta que alguno de los procesos hijo termine.

Tenga en cuenta que necesita terminar cada proceso hijo para que el padre salga normalmente. Si interrumpe el proceso padre, los procesos hijo continuarán ejecutándose, y sus padres se convertirán en un proceso del sistema.

#include <sys/wait.h>
#include <unistd.h>

#include <atomic>
#include <filesystem>
#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::filesystem::exists;

constexpr int FORK_NUM = 6;

pid_t spawnChild(const char* program, char** arg_list) {
  pid_t ch_pid = fork();
  if (ch_pid == -1) {
    perror("fork");
    exit(EXIT_FAILURE);
  } else if (ch_pid > 0) {
    cout << "spawn child with pid - " << ch_pid << endl;
    return ch_pid;
  } else {
    execve(program, arg_list, nullptr);
    perror("execve");
    exit(EXIT_FAILURE);
  }
}

int main() {
  string program_name("child");
  char* arg_list[] = {program_name.data(), nullptr};
  vector<int> children;
  children.reserve(FORK_NUM);

  if (!exists(program_name)) {
    cout << "Program file 'child' does not exist in current directory!\n";
    exit(EXIT_FAILURE);
  }

  for (int i = 0; i < FORK_NUM; ++i)
    children[i] = spawnChild(program_name.c_str(), arg_list);
  cout << endl;

  pid_t child_pid;
  while ((child_pid = wait(nullptr)) > 0)
    cout << "child " << child_pid << " terminated" << endl;

  return EXIT_SUCCESS;
}

Código fuente del proceso hijo (archivo diferente):

#include <sys/wait.h>
#include <unistd.h>

#include <iostream>

volatile sig_atomic_t shutdown_flag = 1;

void GracefulExit(int signal_number) { shutdown_flag = 0; }

int main() {
  // Register SIGTERM handler
  signal(SIGTERM, GracefulExit);

  unsigned int tmp = 0;
  while (shutdown_flag) {
    tmp += 1;
    usleep(100);
  }

  exit(EXIT_SUCCESS);
}

Use fork() y execve para crear múltiples procesos con la función de limpieza automática de los hijos en C++

El código de ejemplo anterior tiene un comportamiento torpe si el proceso de los padres se terminó antes de que todos los niños salieran. En este caso, añadimos la función de manejo de señales al proceso padre que terminará automáticamente todos los procesos hijos una vez que se reciba la señal SIGQUIT. Usa el comando kill -SIGQUIT pid_num_of_parent para enviar la señal.

Observe que algunas de las variables globales que necesitan ser accedidas en el manejador de señales son declaradas como tipos std::atomic, que es el requisito estricto para la corrección del programa.

#include <sys/wait.h>
#include <unistd.h>

#include <atomic>
#include <filesystem>
#include <iostream>

using std::cout;
using std::endl;
using std::string;
using std::filesystem::exists;

constexpr std::atomic<int> FORK_NUM = 6;
constexpr std::atomic<int> handler_exit_code = 103;

std::atomic<int> child_pid;
std::atomic<int> *children;

void sigquitHandler(int signal_number) {
  for (int i = 0; i < FORK_NUM; ++i) {
    kill(children[i], SIGTERM);
  }
  while ((child_pid = wait(nullptr)) > 0)
    ;
  _exit(handler_exit_code);
}

pid_t spawnChild(const char *program, char **arg_list) {
  pid_t ch_pid = fork();
  if (ch_pid == -1) {
    perror("fork");
    exit(EXIT_FAILURE);
  } else if (ch_pid > 0) {
    cout << "spawn child with pid - " << ch_pid << endl;
    return ch_pid;
  } else {
    execve(program, arg_list, nullptr);
    perror("execve");
    exit(EXIT_FAILURE);
  }
}

int main() {
  string program_name("child");
  char *arg_list[] = {program_name.data(), nullptr};

  if (!exists(program_name)) {
    cout << "Program file 'child' does not exist in current directory!\n";
    exit(EXIT_FAILURE);
  }

  children = reinterpret_cast<std::atomic<int> *>(new int[FORK_NUM]);
  signal(SIGQUIT, sigquitHandler);

  for (int i = 0; i < FORK_NUM; ++i) {
    children[i] = spawnChild(program_name.c_str(), arg_list);
  }
  cout << endl;

  while ((child_pid = wait(nullptr)) > 0)
    cout << "child " << child_pid << " terminated" << endl;

  return EXIT_SUCCESS;
}
Autor: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook