8.2. Gestión de "threads" en los "callback"

Como se ha explicado en el anterior capítulo, el mecanismo de "callback" requiere que se implemente una función que llamamos "thread id resolver". Esta función asigna un identificador numérico a cada "thread" que está en ejecución.

Esta función ha de reutilizar identificadores comenzando desde 0, es decir, que si un "thread" muere, el próximo que se creé debe tener el identificador del antiguo "thread". Así si en un determinado momento hay 8 threads en ejecución todos sus identificadores estarán entre 0 y 7.

Respetar esta restricción es importante, ya que se asociará una pila de ejecución a cada "thread", y estas pilas estarán almacenadas en una tabla de como mucho max_threads (ver parámetro de configuración) entradas. Si asignamos un identificador distinto a cada "thread" que se cree podemos agotar pronto las entradas de dicha tabla y/o consumir más memoria de la necesaria.

La función que identifica un "thread" no recibe ningún parámetro y sólo devuelve un entero positivo a partir de 0.

El "thread id resolver" se configura, habitualmente, desde el runtime con las funciones _pdi_ebe_getThreadIdResolver() y _pdi_ebe_setThreadIdResolver(). La primera obtiene un puntero al "thread id resolver" actual, mientras que la segunda permite asignar uno nuevo.

Mostramos un ejemplo de runtime que instala un "thread id resolver" para la librería pthread:


/* Runtime de ejemplo que instala un "thread id resolver" para pthreads */
#include<config.h>
#include<backend.h>
#include<log.h>
#include<pdiconfig.h>
#include<threadid.h>
#include<pthread.h>

#define FALSE 0
#define TRUE (1==1)

/* Esta es la tabla que contendrá la relación de threads en ejecución */
pthread_t *thconv = NULL;

/* Usaremos un mutex para hacer los accesos a la tabla de forma atómica */
pthread_mutex_t mutex_thconv = PTHREAD_MUTEX_INITIALIZER;

/* Esta función será la encarga de darle un identificador al thread */
static int idres(void)
{
  int i;
  int fz;
  pthread_t pt;

  if(thconv)
  {
    /* Buscamos el thread */
    pthread_mutex_lock(&mutex_thconv);
    pt = pthread_self();
    fz = PDICFG.max_threads + 1;
    for(i = 0; i < PDICFG.max_threads; i++)
    {
      /* si lo encontramos lo devolvemos */
      if(thconv[i] == pt)
      {
        pthread_mutex_unlock(&mutex_thconv);
        return i;
      }
      /* nos apuntamos esta posición, si está libre */
      if(fz > PDICFG.max_threads && !thconv[i])
        fz = i;
    }

    /* no hemos encontrado el thread, lo damos de alta */
    thconv[fz] = pt;
    pthread_mutex_unlock(&mutex_thconv);

    return fz;
  } else
    return 0;
}

/* este wrapper se encargará de interceptar la muerte de un */
/* thread y así darlo de baja en la tabla 'thconv'          */
int pthread_exit_wrapper(void *ret)
{
  int id;

  if(thconv)
  {
    id = idres();
    _pdi_log(THIS, "mato a %d\n", id);
    thconv[id] = 0;
  }

  pthread_exit(ret);
}

/* funciones de inicialización y finalización */
int PDI_BE_FUNC_INIT(void)
{
  _pdi_log(THIS, "Inicializando backend '" __FILE__ "'.\n");

  if(PDICFG.max_threads > 0)
  {
    if((thconv = malloc(sizeof(int) * PDICFG.max_threads)) == NULL)
    {
      _pdi_error(THIS, "No hubo memoria para 'thconv'.");
      return FALSE;
    }

    memset(thconv, 0, sizeof(int) * PDICFG.max_threads);

    _pdi_ebe_setThreadIdResolver(idres);
  } else {
    _pdi_log(THIS, "En este programa no se usan threads.");
  }

  return TRUE;
}

void PDI_BE_FUNC_FINI(void)
{
  _pdi_log(THIS, "Cerrando mi backend.\n");

  if(thconv)
  {
    free(thconv);
    thconv = NULL;
    _pdi_ebe_setThreadIdResolver(NULL);
  }
}
    

Como podemos ver este runtime, sólo inicializarse instala, si hay instrumentación con "threads", un "thread id resolver" e inicializa una tabla de conversión. Esta tabla de conversión será ampliada cada vez que se ejecute idres() sobre un nuevo "thread".

La tabla de conversión mantiene una biyección entre un identificador pthread y el identificador numérico que le asignamos. Se dan de alta los nuevos "threads" de tal modo que siempre se les asignará el identificador más bajo posible.

Para mantener sólo los "threads" vivos en la tabla se instala una redefinición sobre la función pthread_exit() que va dando de baja los "threads" que terminan. El fichero de comandos del runtime sería el siguiente:


; Fichero de comandos runtime
#backend RUNTIME runtime.so
#object  PTHREADS /lib/libpthread.so

#commands
D PTHREADS pthread_exit RUNTIME pthread_exit_wrapper
    

Es posible que no se hayan contemplado casos de la librería de pthread en este ejemplo, pero tampoco pretende ser un runtime exhaustivo y real. Simplemente se ilustra como se implementaría un pequeño sistema de obtención de identificadores de "thread".