Evitar las pérdida de memoria en la programación thread de POSIX

Consejos para detectar y evitar las pérdida de memoria thread de POSIX

La programación de los threads de POSIX (pthread) define un conjunto estándar de tipos, funciones y constantes del lenguaje de programación C,—y pthreads proporciona una herramienta poderosa para la administración de threads. A fin de utilizar pthreads al máximo, tendrá que evitar los errores comunes. Un error común es olvidar unir los threads que se puede unir, lo que puede crear pérdidas de la memoria y originar un trabajo extra. En este artículo orientado a las sugerencias, conozca lo básico de los threads de POSIX, vea cómo identificar y detectar la pérdida de memoria de los threads y a recibir consejos sólidos para evitarla.

Wei Dong Xie, IBM Systems Director Product Engineer, IBM

Author photo of Wei Dong XieDurante los tres últimos años, Wei Dong ha trabajado como Ingeniero de Productos para IBM Systems Director con la responsabilidad de resolver los problemas informados por los clientes. Antes de unirse a IBM, Wei Dong hizo una pasantía de 10 meses en Intel como desarrollador de Linux. Se graduó en la Universidad de Nanjing, China, con una maestría.



06-01-2011

Introducción a los threads de POSIX

La razón principal de utilizar los threads es aumentar la performance del programa. Los threads pueden ser creados y administrados con menos gastos generales del sistema operativo y menos recursos en el sistema. Todos los threads en un proceso comparten el mismo espacio de direcciones, lo que hace que las comunicaciones entre los threads sean más eficientes y más sencillas de implementar que la comunicación entre los procesos. Por ejemplo, si un thread está esperando que se complete una llamada de entrada/salida del sistema, los otros pueden estar trabajando en las tareas intensivas de la CPU. Con los threads, las tareas importantes pueden ser programadas para que tengan prioridad sobre las tareas de menor prioridad e incluso que las puedan interrumpir. Las tareas poco frecuentes y esporádicas pueden ser intercaladas en forma regular entre las tareas programadas, creando de este modo una programación más flexible. Y, por último, los pthreads son ideales para la programación en paralelo en las máquinas con múltiples CPUs.

Y la razón principal para utilizar los threads de POSIX, o los pthreads, es más simple aún: Como parte de la interfaz estandarizada de programación de los threads del lenguaje C son sumamente portátiles.

La programación de los threads de POSIX brinda muchos beneficios, pero si usted no tiene en claro algunas de las normas básicas corre el riesgo de escribir un código difícil de depurar y causar fugas de la memoria. Comencemos por revisar los threads de POSIX que pueden serthreads que se pueden unir o threads desunidos.

Threads que se pueden unir

Si usted quiere producir un nuevo thread y necesita saber cómo es terminado, entonces necesita un thread que se puede unir. Para estos threads, el sistema asigna un almacenamiento privado para guardar el estado de terminación de los threads. El estado es actualizado después de que el thread termina. Para recuperar el estado de la terminación del thread, llame a pthread_join(pthread_t thread, void** value_ptr).

El sistema asigna almacenamiento subyacente para cada thread, incluyendo la pila, la ID del thread, el estado de terminación del thread y así sucesivamente. Este almacenamiento subyacente permanecerá en el espacio del proceso (y no será reciclado) hasta que el thread haya terminado y haya sido unido por otros threads.

Threads desunidos

La mayor parte del tiempo, usted crea un thread, le asigna alguna tarea y luego continúa procesando otros asuntos. En estos casos, usted no se preocupa de cómo los threads terminan, y un thread desunido sería una buena elección.

Para los threads desunidos, el sistema recicla sus recursos subyacentes en forma automática después de que el thread termina.


Reconocer las fugas

Si usted crea un thread que se puede unir pero se olvida de unirlo, sus recursos o su memoria privada se mantienen siempre en el espacio del proceso y nunca son reclamadas. Siempre una los threads que se pueden unir, de no hacerlo, usted corre el riesgo de tener pérdidas serias de memoria.

Por ejemplo, un thread en Red Hat Enterprise Linux (RHEL4) necesita una pila de 10MB, lo que significa que se pierde al menos 10MB de memoria si usted no lo ha unido. Supongamos que usted diseña un programa en la modalidad de administrador-empleado (manager-worker) para procesar las solicitudes entrantes. Se necesitan entonces crear cada vez más threads de empleado, desarrollar tareas individuales y luego terminar. Si son threads que se pueden unir y usted no ha llamado a pthread_join() para unirlos, cada thread producido perderá una cantidad considerable de memoria (al menos 10MB por pila) después de su terminación. El tamaño de la memoria perdida aumenta en forma continua a medida que más y más threads de empleado sean creados y terminados sin ser unidos. Además, el proceso no podrá crear nuevos threads, ya que no quedará memoria disponible para crearlos.

El listado 1 muestra la pérdida grave de memoria ocasionada si usted olvida unir los hilos que se pueden unir. Puede utilizar también este código para comprobar la cantidad máxima de grupos de threads que pueden coexistir en un espacio de proceso.

Listado 1. Crear una fuga de memoria
#include<stdio.h>
#include<pthread.h>
void run() {
   pthread_exit(0);
}

int main () {
   pthread_t thread;
   int rc;
   long count = 0;
   while(1) {
      if(rc = pthread_create(&thread, 0, run, 0) ) {
         printf("ERROR, rc is %d, so far %ld threads created\n", rc, count);
         perror("Fail:");
         return -1;
      }
      count++;
   }
   return 0;
}

En el listado, pthread_create() es llamado para crear un nuevo thread con un atributo de thread por omisión. De esta forma, el nuevo creado se puede unir. Se crean nuevos threads que se pueden unir sin cesar hasta que la falla ocurre. A continuación el código de error y la causa de la falla se imprimen.

Al compilar el código del Listado 1 en Red Hat Enterprise Linux Server release 5.4 con este comando: [root@server ~]# cc -lpthread thread.c -o thread, se obtienen los resultados que se muestran en el Listado 2:

Listado 2. Resultados de la fuga de memoria
[root@server ~]# ./thread
ERROR, rc es 12, hasta el momento se crearon 304 threads
Falla: No puede asignar la memoria

Después de que el código haya creado 304 threads, ya no puede crear más. El código de error es 12, lo que significa que no tiene más memoria.

Como se muestra en el listado 1 y 2, los threads que se pueden unir son producidos, pero nunca unidos, por lo tanto cada thread que se puede unir terminado aún ocupa el espacio de proceso perdiendo la memoria de proceso.

Un thread de POSIX en RHEL tiene una pila privada con un tamaño de 10MB. En otras palabras, el sistema asigna al menos 10MB de almacenamiento privado por cada pthread. En nuestro ejemplo, 304 threads fueron producidos antes de que el proceso se detuviera; estos threads ocupan 304*10MB de memoria, alrededor de 3GB. El tamaño de la memoria virtual para un proceso es de 4GB con una cuarta parte del espacio de proceso reservada para el kernel de Linux . Sume eso y obtendrá un espacio de memoria de 3GB para el espacio del usuario. De ese modo la memoria de 3GB es consumida por threads muertos. Ésta es una fuga de memoria grave. Y es fácil ver cómo sucedió tan rápidamente.

Usted puede arreglar la fuga agregando el código para llamar pthread_join(), que une cada hilo que se puede unir.


Detectar las fugas

Al igual que con otras fugas de memoria, el problema no puede ser evidente cuando el proceso se ha iniciado. Así que ésta es una manera de detectar dichos problemas sin necesidad de acceder al código fuente:

  1. Cuente la cantidad de pilas de threads en el proceso. Esto incluye la cantidad de threads activos en ejecución y terminados.
  2. Cuente la cantidad de threads en ejecución activos en el proceso.
  3. Compare ambos. Si la cantidad de pilas de threads existentes es mayor que la de threads activos en ejecución, y la dispersión de estos dos números sigue aumentando a medida que el programa sigue funcionando, entonces la memoria tiene fugas.

Y lo más probable es que dicha fuga de memoria sea causada por no poder unir los threads que se pueden unir.

Utilice el pmap para contar las pilas de threads

En un proceso de ejecución, la cantidad de pilas de threads es igual a la cantidad de de grupos de threads en el proceso. Los grupos de threads constan de threads activos en ejecución y de threads inactivos que se pueden unir.

pmap es una herramienta de Linux utilizada para informar sobre la memoria del procesos. Combine los siguientes comandos para obtener la cantidad de pilas de threads:

[root@server ~]# pmap PID | grep 10240 | wc -l

(10240KB es el tamaño de la pila por omisión en Red Hat Enterprise Linux Server release 5.4.)

Utilice /proc/PID/task para contar los threads activos

Cada vez que un thread es creado y está en funcionamiento, una entrada se completa en /proc/PID/task. Cuando el thread termina, ya sea que se pueda unir o que sea individual, la entrada es eliminada de /proc/PID/task. De este modo la cantidad de threads activos se puede obtener mediante la ejecución de:

[root@server ~]# ls /proc/PID/task | wc -l.

Comparar los resultados

Controlar el resultado de pmap PID | grep 10240 | wc -l y compararlo con el resultado de ls /proc/PID/task | wc -l. Si la cantidad de pilas de threads es superior a la cantidad de threads activos, y su dispersión continúa creciendo a medida que el programa funciona, usted puede llegar a la conclusión de que el problema de fuga existe.


Prevenir las fugas

Los threads que se pueden unir deberían ser unidos durante la programación. Si va a crear threads que se pueden unir en su programa, no se olvide de llamar a pthread_join(pthread_t, void**) para reciclar el almacenamiento privado asignado al thread. De lo contrario, se producirán serias fugas de memoria.

Después de la programación y durante el período de prueba, usted puede usar pmap y /proc/PID/task para detectar si dichas fugas existen. Si fuera así, controle el código fuente para ver si todos los threads que se pueden unir se han unido.

Y eso es todo. Un poco de prevención le ahorrará trabajo posterior y fugas de memoria molestas.

Recursos

Aprender

Obtener los productos y tecnologías

  • Evaluate IBM products en la forma que más le convenga: Descargue una prueba del producto, pruébela en línea, utilice un producto en un entorno en nube, o dedíquele unas pocas horas a SOA Sandbox conocer cómo implementar Service Oriented Architecture en forma eficiente.

Comentar

  • Involúcrese en My developerWorks community. Conéctese con otros usuarios de developerWorks mientras explora los blogs, foros, grupos y wikis impulsados por los desarrolladores.

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Linux
ArticleID=606148
ArticleTitle=Evitar las pérdida de memoria en la programación thread de POSIX
publish-date=01062011