Hoy toca otra vez de esos tostones informáticos que no interesan a nadie, como el de GCD, pero que lo pongo para hacer copia de seguridad de estas pequeñas investigaciones.
Estoy haciendo un proyecto donde tengo que utilizar multiproceso para que varias tareas se ejecuten simultáneamente (nunca mejor dicho) que consiste en conectar a varios equipos lanzando varios hilos para que el intento de conexión a uno no haga esperar las conexiones a los demás. Una vez lanzados todos se espera por ellos hasta que terminen de conectar para iniciar el resto del proceso.
El desarrollo es en Linux y en C++ y todo el proceso está dentro de una sola aplicación, es decir, no hay varias aplicaciones que se lanzan desde una shell de forma simultánea, no, es una única aplicación desde donde está todo el proceso, por eso no se pueden usar varios procesos sino que se usan hilos, exactamente pthreads.
Los pthreads son el estándar POSIX para hilos de ejecución. Además, también definen la sincronización de los mismos mediante mutex y variables de condición (que, por cierto, nunca he entendido su funcionamiento; yo uso semáforos).
Para facilitar la mantenibilidad del software, he implementado toda la funcionalidad mediante orientación a objetos, incluidos los hilos, semáforos, mutex, etc., por lo que para iniciar un hilo, se haría de la siguiente forma:
class MyThread : public Thread {
public:
MyThread() : Thread {}
virtual ~MyThread() {}
virtual void* run(void* arg) {
cout << “MyThread::run()” << endl;
}
};
MyThread* thread = new MyThread();
thread->start();
thread->wait();
delete thread;
Dentro de la función start() se crea un pthread y se inicia mediante la función pthread_create(...). En la función wait() se espera por el hilo hasta que finalice su ejecución mediante la función pthread_join(...).
La clase Thread cuenta también con funciones para parar un hilo, reiniciarlo, etc. También se puede pasar como parámetro la función a ejecutar o, incluso, una clase que herede de la clase ThreadFunction que tiene la función run() para que así esta implementación sea bastante polivalente.
Hasta aquí todo bien. La verdad es que funciona de forma bastante estable, creo que es sencillo de utilizar (al menos es lo que pretendo) y bastante mantenible ya que cualquier modificación afecta sólo a la clase y apenas hay que tocar el resto de código.
Pero, a partir de aquí, en cuanto se empieza a hacer un uso intensivo, es cuando comienzan los pequeños problemas.
Resulta que la función pthread_create() no sólo crea un nuevo pthread sino que también lo inicia, es decir, no hay ninguna función que sea pthread_start(), sino que para iniciar (y parar) un hilo hay que crearlo (y destruirlo). Además, los desarrolladores de los pthreads no están por la labor de hacer estas funciones.
El problema surge cuando se crean muchos hilos e inmediatamente después se espera por ellos, por ejemplo, de la siguiente forma:
#define limit 100
MyThread* threads[limit];
for(int i = 0; i < limit; i++) {
threads[i] = new MyThread();
threads[i]->start();
threads[i]->wait();
}
El problema está en hay veces que se crea un hilo dando como resultado un pthread_t válido (el identificador del hilo), pero no da tiempo a iniciarlo antes de que se ejecute el pthread_join(), por lo tanto se está haciendo una espera sobre un hilo creado pero no iniciado. En teoría pthread_join() debería devolver un valor del tipo EINVAL o similar pero no. En la práctica lo que se produce es un fallo de segmentación, es decir, corrupción de memoria.
Para reproducir esta funcionalidad, he hecho un pequeño programa en C++ que, ejecutado varias veces, produce este comportamiento incorrecto. En este caso no he conseguido corrupción de memoria, pero tampoco es está completo.
La solución que se me ocurre es usar un semáforo que sincronice la creación y la iniciación del hilo con la espera del mismo. No es una solución demasiado elegante pero creo que funcionará.
La verdad es que el API de POSIX respecto a hilos no me parece demasiado buena, pero creo que no tengo nada que hacer. Es en estos momentos es cuando hecho de menos el API de hilos de BeOS/Haiku
.
Actualización 25/03/2001: En esta entrada, extrañamente, es donde más spam entra (aproximadamente un 90 % de todo el que recibe el blog) así que he cerrado los comentarios.