Download ESTUDIO COMPARATIVO DEL SOPORTE MULTIHILO DE JAVA
Document related concepts
no text concepts found
Transcript
ESTUDIO COMPARATIVO DEL SOPORTE MULTIHILO DE JAVA VERSUS PTHREADS JORGE A. CASTELLANOS D. Departamento de Computación Universidad de Carabobo FACYT Valencia, Estado Carabobo, Venezuela email: [email protected] RESUMEN En este trabajo, se propone el estudio de dos de las interfaces de programación paralela de mayor uso y difusión, como son, Pthreads de “C” y Java (multi-threading), con el propósito principal de hacer más accesible al estudiante de la Licenciatura en Computación de la FACYT, el tema de la programación multihilos, que ha sido tratado tradicionalmente cómo difı́cil, proclive a errores y algo misterioso. Para la realización del presente estudio se seleccionó el sistema operativo Linux debido a su Licencia GNU (de dominio público), este hecho lo hace muy accesible a la comunidad docente y estudiantil de la FACYT. Este sistema, además tiene entre sus haberes: su compatibilidad con sistemas operativos tipo Unix y dispone de una gran cantidad de herramientas de programación y aplicaciones. PALABRAS CLAVE Programación multihilo, Pthreads, Java, Mutex, Monitor 1. Introducción Aunque se debe reconocer que la programación multihilos, de por sı́ es compleja, debido a los factores adicionales que debe tomar en cuenta el programador y también a los nuevos problemas que aparecen (ı́nter-bloqueos y condiciones de carrera)[1], es importante disponer de una evaluación inicial de las facilidades e inconvenientes que presentan los soportes de programación multihilos de más amplia difusión en la actualidad como los son Pthreads y Java. Esta evaluación le permitirá al programador, antes de iniciar el desarrollo de una aplicación multihilos, decidir cual de estos soportes favorece más a sus intereses, ó por otra parte decidir el buscar otra alternativa diferente de “C” y Java para dar solución a su problema. Si bien, todos los códigos que se recopilaron y desarrollaron para el presente trabajo, se probaron usando Linux sobre máquinas Pentium 4 con un solo procesador, se podrán montar y evaluar con relativa facilidad usando otros sistemas operativos o tipos de máquina (por ejemplo, multiprocesador), aunque la evaluación de su comportamiento puede ser el objeto de un proyecto futuro. En la primera parte se presenta la comparación de los dos soportes multihilos estudiados que toma en consideración las caracterı́sticas comunes y no comunes de los dos soportes multihilo bajo consideración. En la segunda parte del estudio se presenta un ejemplo sencillo que además de ilustrar el uso de las primitivas de sincronización, sirve de referencia al lector para interpretar los tópicos abordados en las comparaciones y en las conclusiones. Es de notar que este artı́culo representa un resumen de un trabajo extenso elaborado por el mismo autor[3], donde se implementó un conjunto de programas multihilo que permitieron evaluar diversos aspectos de la programación multihilo, como los tiempos de ejecución (caso programa del agente viajero); aún cuando solamente se presentan los códigos para uno de los programas realizados, algunas conclusiones hacen referencia a otros programas que no se presentaron en este artı́culo por limitaciones de espacio. 2. 2.1. Comparación Java vs. Pthreads Principales diferencias La interfaz de Java es orientada a objetos mientras que Pthreads está orientada a funciones. Esto hace que la interfaz de Java sea más fácil de entender. Un hilo se conceptualiza en Java como un objeto ejecutándose sobre si mismo[12]. Los hilos Pthreads ofrecen muchas más caracterı́sticas que Java. Por ejemplo Java ofrece sólo un método para destruir un hilo mientras que Pthreads ofrece varias funciones para el mismo propósito. El poder los hilos de Java es su simplicidad, pocas alternativas, facilidad de uso y confort. Pthreads le da al programador un control más preciso sobre los hilos, mientras que Java le da simplicidad y extensibilidad. Hay un sentimiento que los hilos de Pthreads son más difı́ciles de entender y programar con ellos que los hilos de Java. 2.2. Diferencias Especı́ficas Creación y arranque de hilos: En Java un hilo se crea instanciando un objeto thread. Los hilos Pthreads se crean llamando la función pthread create con las funciones especı́ficas que van a ser ejecutadas por el hilo. Los parámetros del nuevo hilo Pthreads están limitados a uno, salvo que se use como parámetro una estructura. Mientras que en Java virtualmente no hay un lı́mite para el número de parámetros que tendrá el método run(). Cuando se crea un hilo con Pthreads, el nuevo hilo arranca automáticamente, mientras que en Java se debe llamar explı́citamente al método start(). Es decir, los hilos de Java se pueden crear para un uso posterior, mientras que los hilos Pthreads deben crearse donde se necesitan para empezar su ejecución. Uniendo hilos: La unión de hilos es similar en Java y Pthreads. Ambos crean una dependencia entre los dos hilos, donde un hilo espera a que el otro finalice su trabajo. Cuando un hilo termina su trabajo en Pthreads, su resultado está automáticamente disponible como estado de realización (completion status). En Java para obtener el resultado del trabajo de un hilo que termina hay que usar una variable de clase, y almacenar el resultado en una variable de clase. En resumen, esto resulta algo más complicado en Java que en Pthreads. Deteniendo un hilo: Java tiene solo un método implementado(stop() para detener un hilo (sólo en la versión JDK 1.0). Este método puede ser llamado desde dentro de este hilo o desde otros objetos. Para llamar éste método desde otros objetos, es necesario tener una referencia de los objetos hilo para llamar stop(). En la versiones posteriores a la 1.1 (Java 2), no está implementado el método stop() porque puede dar lugar a fallos graves del sistema[12]. En contraste, el estándar Pthreads define diferentes funciones para detener un hilo. La función pthread exit() puede llamarse desde el hilo mismo que se quiere detener. Las funciones pthread kill() y pthread cancel() se usan para terminar un hilo desde otros hilos[2]. Identificador del hilo: Los identificadores de hilos (ID) están definidos en forma muy diferente en Java y en Pthreads. Un identificador de hilo en Java es una cadena (String), mientras que en Pthreads es semejante a una estructura de datos opaca sobre la cual sólo puede operar un hilo de control. En Pthreads, al contrario de Java, no es posible establecer un identificador, solo se puede recuperar y comparar. En Pthreads los identificadores de hilos se establecen automáticamente en el momento de crear el nuevo hilo. Es más fácil comparar dos hilos en Java; en Pthreads se necesita usar una función (pthread equal(id1, id2)) especialmente definida para este fin. Atributos de los hilos: Los atributos de los hilos son una de las mayores ventajas de los hilos Pthreads sobre los hilos de Java. Los atributos no están disponibles en Java. Los atributos se usan para varios propósitos. Por ejemplo la función pthreads attr setschedpolicy() puede establecer polı́ticas de planificación de hilos. En este sentido los hilos Pthreads son más controlables que los hilos de Java. Los hilos Pthreads tienen un atributo de alcance de los argumentos que se puede definir sobre múltiples hilos, mientras que en Java no existe este atributo. Grupos de hilos: En Java, a diferencia de Pthreads, cada hilo, desde el momento de su creación pertenece a un grupo de hilos. esta caracterı́stica puede ser ventajosa para programas donde se necesita trabajar con gran cantidad de hilos, ya que los hilos se pueden hacer pertenecer a diferentes grupos y de esta forma las operaciones sobre los hilos se realizan a través del grupo al cual pertenecen. En Java también es posible la definición de grupos de grupos de hilos[11]. Niveles de Prioridad: Los hilos Pthreads pueden tener al menos 32 prioridades diferentes, dependiendo de la implementación utilizada. En Java solo hay 10 prioridades de biblioteca definidas. Polı́tica de Planificación La polı́tica de planificación determina como interactúan los hilos de igual prioridad. Pthreads permite definir la polı́tica de planificación, dependiendo del sistema operativo y de la implementación particular de Pthreads sobre el sistema operativo en particular. En Java no se tiene acceso a la polı́tica de planificación, la interacción entre múltiples hilos en Java depende sobre qué sistema operativo está corriendo la máquina virtual de Java[8]. 3. Ejemplo de programación Este ejemplo de programación multihilo que denominaremos “Suma Paralela” es muy sencillo y su carácter es netamente académico. El objeto principal es ilustrar las técnicas fundamentales de programación multihilos. El código del programa se tomó de [4] y se adaptó para este trabajo, aun cuando su versión original es para Modula-3. El programa recibe como parámetro el número de componentes de un vector (N), cuyas componentes (v[i]) se generarán en forma aleatoria. El programa por su parte calculará en paralelo la suma de todas las componentes del vector (v[i]), por supuesto usando múltiples hilos. Es decir, el programa principal después de generar el vector, creará tantos hilos como componentes definidas del vector. Cada uno de los hilos recibe como parámetro una componente del vector que sumará a un acumulador común s. Cuando todos los hilos hayan terminado su trabajo (sumar al acumulador el parámetro recibido), el programa principal imprime el resultado. Para implementar cada una de las versiones se tomaron en consideración las facilidades que provee cada interfaz, es decir, cuando se hace la implementación con Pthreads se usa el objeto mutex[13] mientras que para la implementación con Java se prefiere usar monitores[6] para resolver los problemas de sincronización[14]. En la figura 1 se observa una salida tı́pica obtenida al ejecutar el programa de suma paralela. Las versiones implementadas producen una salida por pantalla equivalente a la mostrada. jcasteld@cu-d01:˜/Ascenso/Pthreads\$ ./run Se sumarán 10 números: 85 40 79 80 92 20 34 77 28 56 Hilo 1: sumando 85, suma 85 Hilo 2: sumando 40, suma 125 Hilo 3: sumando 79, suma 204 Hilo 4: sumando 80, suma 284 Hilo 5: sumando 92, suma 376 Hilo 6: sumando 20, suma 396 Hilo 7: sumando 34, suma 430 Hilo 8: sumando 77, suma 507 Hilo 9: sumando 28, suma 535 Hilo 10: sumando 56, suma 591 El total es 591, calls = 10 Figura 1. Salida para el programa de suma. 3.1. Implementación con Pthreads Para implementar este programa usando Pthreads se realizó una versión que ilustra el uso del objeto mutex. Pero, porqué usar un objeto de sincronización para un programa que resuelve un problema tan simple como: ¿ sumar “N” componentes de un vector ? Por supuesto que sumar “N” componentes de un vector en forma secuencial no presenta ninguna dificultad, ni amerita el uso de objetos de sincronización. Los inconvenientes se presentan cuando se propone obtener una solución mediante una versión paralela (multihilos). El primer problema se genera cuando definimos una variable s que va a ser compartida por N hilos que se ejecutan en paralelo. Es decir, se debe garantizar el acceso exclusivo[14] a la varible s, o sea, sólo un hilo puede acceder en forma simultánea la variable s. El segundo problema que ilustra este ejemplo es la necesidad de crear un mecanismo de sincronización que permita al programa principal imprimir el resultado cuando todos los N hilos hayan finalizado. Es por esto que necesitamos hacer uso de los objetos de sincronización que proporciona la interfaz de programación paralela. Para esta situación particular Pthreads nos ofrece el objeto mutex. En sentido estricto no se tendrán N hilos, sino N instancias de un mismo hilo. El código de este hilo se muestra en la figura 2. La variable global m del tipo pthread mutex t, se usa para garantizar el acceso exclusivo al acumulador s, que es una variable compartida por los N hilos. Cuando un hilo obtiene el mutex m mediante la función pthread mutex lock, este hilo puede modificar el acumulador s y al mismo tiempo también evita que cualquier otro hilo obtenga el mutex hasta que el hilo actual (que posee el mutex), haya liberado el mutex m a través de la ejecución de la función pthread mutex unlock. void * sum(void * arg) { int * p; p = (int *)arg; pthread_mutex_lock(&m); s = s + *p; pthread_mutex_unlock(&m); } Figura 2. Hilo que efectúa la suma. La región que se define entre la ejecución de las funciones pthread mutex lock y pthread mutex unlock se denomina “región crı́tica”. En programación multihilos con Pthreads se debe tener especial cuidado en aparear correctamente estas dos funciones, para evitar caer en uno de los errores más frecuentes[4]. Esto es, debe prestarse especial cuidado en ejecutar primero la función pthread mutex lock (al inicio de la región crı́tica) y después la función pthread mutex unlock (al final de la región crı́tica). Este error es difı́cil de detectar, de allı́ que usualmente se agrega un sangrado adicional al código contenido en la región crı́tica como se muestra en la figura 2. Según se habı́a comentado, el segundo problema que debemos resolver en este ejemplo es imprimir el resultado cuando todos los hilos hayan terminado su trabajo (sumar su parámetro al acumulador s). El programa principal necesita que cada uno de los hilos le informe cuando terminó su trabajo. Una posible solución es que cada hilo (cuando termine) incremente una variable global (que se llamará calls) y además señale al programa principal a través de una variable done (del tipo pthread cond t ) después de modificar la variable global calls. La nueva versión que incorpora estas mejoras puede verse en la figura 3. void * sum(void * arg) { int * p; p = (int *)arg; pthread_mutex_lock(&m); s = s + *p; pthread_mutex_unlock(&m); pthread_mutex_lock(&mm); calls ++; pthread_cond_signal(&done); pthread_mutex_unlock(&mm); } Figura 3. Código mejorado del hilo sum En el código mejorado (figura 3) se observa el uso de un nuevo mutex mm que se usa para garantizar el acceso exclusivo de la variable global calls. Para proteger la variable calls se usa otro mutex para evitar caer en el problema de “serialización” que incrementa los tiempos de ejecución al crear cuellos de botella adicionales en detrimento de la concurrencia (o paralelismo). En este caso se debe notar que no es necesario bloquear el acceso a la variable calls, mientras se está accediendo la variable s. De esta forma se permite a un hilo modificar la variable calls, mientras que otro hilo puede estar modificando la variable s. Cuando se necesita mejorar el rendimiento, se recomienda asignar a cada elemento o dato compartido un mutex diferente. Si bien, se conoce el código que ejecuta cada uno de los hilos, se revisará a continuación el código del programa principal main (ver figura 4) que recibe la información de los hilos para comprender el mecanismo de sincronización que le permite al programa main determinar cuando todos los hilos han terminado para luego imprimir el resultado de la suma de las componentes del vector v. main (int argc, char *argv[]){ /* Inicializa vector de N componentes */ /* Crea N hilos */ pthread_mutex_lock(&mm); while (calls != N) pthread_cond_wait(&done, &mm); pthread_mutex_unlock(&mm); printf("El total es %d\n", s); } Figura 4. Código del programa principal. Antes de proceder a llamar a la función pthread mutex lock, el programa main genera el vector v con N componentes y crea los N hilos (que efectúan las sumas). Técnicamente este segmento de programa puede reemplazarse por una serie de llamadas a la función join, pero se prefiere usar este mecanismo porque además de ser más eficiente, ya que no consume tiempo de procesador por espera ocupada (como es el caso de la función join), permite ilustrar el mecanismo estándar que se recomienda utilizar cada vez que un hilo debe esperar por una variable de condición. Cuando se trabaja con variables de condición (ver figura 3), el primer paso consiste en adquirir el mutex (mm) asociado a la variable compartida calls dentro del código del hilo. Este mutex (mm) se asocia tanto a la variable calls como a la condición done. El segundo paso es llamar a pthread cond wait dentro de un lazo while (ver figura 4), esto es porque el valor que devuelve pthread cond wait no indica con certeza el valor de su predicado, es decir, se debe re-evaluar la condición una vez que se retorna de ella; en nuestro caso debemos re-evaluar el valor de N para estar seguros que el hilo se reasumió porque N cambió su valor. El lazo while y el manejo de cualquier variable asociada a la evaluación de la condición (en nuestro caso N) deben estar ubicados dentro de la región crı́tica definida por el mismo mutex (mm) que además es un parámetro de la función pthread cond wait. Al evaluar como falsa la condición del while es porque todos los hilos han terminado y según lo previsto es el momento de imprimir el resultado y terminar la ejecución del programa main. 3.2. Suma paralela usando Java A diferencia de Pthreads, el lenguaje de programación Java ofrece un mecanismo de alto nivel para el manejo del problema de exclusión mutua denominado “monitor”, que presenta la ventaja de ser menos proclive a errores; además, como el monitor está implementado en el lenguaje de programación Java, facilita al programador la tarea de definir regiones crı́ticas para exclusión mutua de datos y/o funciones. Aún cuando el problema que se pretende resolver usando monitores es relativamente simple, tenemos que el código en Java se torna algo complejo; posiblemente se deba al hecho que Java es una lenguaje que “aparentemente” no está formulado para la solución de problemas pequeños. A pesar que la solución es algo más complicada por la cantidad de lineas de código, a diferencia del lenguaje “C”, el compilador de Java garantiza que el programa no se ejecutará hasta tanto no estén resueltos “casi” todos los problemas que involucren inconsistencias en las definición de las clases que conforman el programa. Si bien, el problema posiblemente se pueda plantear usando menos clases, la solución se presenta usando cuatro clases, sacrificando cantidad de código por claridad en el planteamiento y tratando de asemejar, en lo posible, la solución con monitores a la solución ya planteada con mutex (Pthreads), para facilitar al lector el seguimiento en la forma como se presenta la solución del problema. La primera clase propuesta se denomina Acumulador (ver figura 5) y dentro de ella se definen tres métodos del tipo synchronized, es decir que se procesan en forma exclusiva por la máquina virtual de Java. Por ejemplo, cuando se ejecuta el primero de ellos, los otros dos restantes no se ejecutan hasta que el primero termina su ejecución. El primer método, sumar, suma al acumulador s el número entero que se le pasa como parámetro. El segundo método, icalls, incrementa en 1 el valor de la variable calls. El tercer método, verificar, comprueba si el valor que se le pasa como parámetro es igual a la variable de clase calls; si son iguales regresa true, sino devuelve false. La segunda clase, denominada Hilo, como lo indica su nombre (ver figura 6), contiene el código que ejecutará cada uno de los hilos responsables de sumarle al acumulador una componente del vector. Esta clase hace uso de los métodos definidos en la clase Acumulador ya que se le pasa como uno de sus parámetros un objeto de clase Acumulador. Al hacer uso de los métodos tipo synchronized de la clase Acumulador, se garantiza en for- class Acumulador{ int s, calls; Acumulador() { s = 0; calls = 0; } synchronized void sumar(int num){ s = s + num; } synchronized void icalls(){ calls++; } synchronized boolean verificar(int d) { if (d == calls) return true; return false; } } Figura 5. Clase Acumulador. ma implı́cita la exclusión mutua en cada una de las operaciones, es decir, sumar al acumulador (a.sumar(numero)) e incrementar la variable calls (a.icalls()). Nótese que justo antes de terminar el código del hilo se usa la función notify() (ver figura 6) para avisarle a otro hilo (en este caso Principal) que se ha realizado la suma y se ha incrementado el valor de la variable de clase calls. class Hilo extends Thread{ Acumulador a; int numero; Hilo(Acumulador acc, String id, int f){ super(id); // Nombre para la super clase a = acc; numero = f; } public void run(){ try{ a.sumar(numero); a.icalls(); notify(); }catch(Exception e){} } } Figura 6. Clase Hilo. Según ya se anticipó, la clase Principal (ver figura 7) se torna en el hilo responsable de imprimir el resultado de la suma, después de esperar que los otros N hilos hayan finalizado su trabajo (sumar al acumulador un componente del vector). Inicialmente el hilo Principal se suspende cuando ejecuta la función wait(), después de determinar que aún el contenido de la variable calls es diferente de N. Cuando alguno de los hilos sumadores ejecuta la función class Principal extends Thread{ Acumulador a; int Done; Principal(Acumulador acc, int d, String id){ super(id); a = acc; Done = d; } public void run(){ while(!a.verificar(Done)){ try{ wait(); }catch(Exception e){} } System.out.println("El total es " + a.s); } } Según se aprecia en este ejemplo, una de las ventajas del código en Java es que es más coherente al momento de ser interpretado, a diferencia del código en lenguaje “C”, que a pesar de ser mucho más compacto es más difı́cil de leer, comprender y depurar. También influye el hecho que la sintaxis de las funciones de la biblioteca Pthreads no se presenta amigable al programador novel que se inicia en el campo de la programación multihilo. Figura 7. Clase Principal. 4. notify()(ver figura 6), el hilo (Principal p) “despierta” y re-evalúa la condición de finalización, es decir si calls es diferente de N. Cuando la condición se evalúa como falsa, entonces se imprime el valor acumulado en la variable de clase s. class SumaParalela{ public static void main(String args[]){ // Inicialización de variables Acumulador acc = new Acumulador(); // Inicializa vector de N componentes Principal p = new Principal(acc, N,"Hilo Principal"); p.start(); for(i=0; i<N; i++){ new Hilo(acc, " Hilo " + (i+1), v[i]).start(); } } } Figura 8. Clase SumaParalela. La cuarta y última clase (ver figura 8) denominada SumaParalela, es la encargada de generar (aleatoriamente) el vector de N componentes, además de crear los objetos de clase Acumulador, Hilo y Principal, responsables de efectuar la suma paralela. Mediante un lazo for se crean N objetos de clase Hilo; cada uno de ellos será responsable de sumar una componente del vector al acumulador s. Nótese que el código de la clase Principal puede estar incluido en esta clase (SumaParalela) y de esta forma pudiera evitarse la creación de un hilo adicional. Conclusiones Una vez culminado el presente trabajo, se puede concluir que las dos interfaces estudiadas son apropiadas, siempre y cuando se utilicen para obtener su máximo provecho. La interfaz Pthreads de “C”, nos permite obtener muy buenos tiempos de ejecución pero con un alto costo de desarrollo; el programador debe conocer el problema con mucho detalle y debe invertir mucho tiempo en la revisión del código multihilo, ya que el compilador da muy poca ayuda en la búsqueda de errores. Java por su parte permite obtener una rápida solución, pero con un alto consumo de recursos de cómputo. Si se quiere mejorar la primera solución, se debe gastar gran cantidad de tiempo para su optimización. Java maneja mejor los problemas con alta complejidad, mientras que “C” se hace más proclive a errores cuando aumenta la complejidad. En resumen, tomando las palabras de [5], la interfaz de Java se puede comparar con aquel carro automático, en el cual se necesita saber poco para poder conducirlo. En contraste, la interfaz Pthreads de “C” luce como aquel carro sincrónico que permite al conductor experto, sacar el mayor provecho del vehı́culo cuando llegan las cuestas pronunciadas u obtener la mejor arrancada. La interfaz Pthreads es más flexible, permite obtener un mejor desempeño, pero su utilización resulta compleja. El alto nivel y la flexibilidad reducida, permite a Java tener ventaja cuando se desea intentar llevar el problema a otra plataforma u otra arquitectura, ya que la máquina virtual de Java se encarga de resolver el problema. Por su parte la interfaz Pthreads, al ser de más bajo nivel, requiere que el programador haga modificaciones en el código y/o la compilación para mantener un buen desempeño de la aplicación al cambiar de plataforma o sistema operativo. Como conclusiones particulares se deben resaltar las siguientes: Los tiempos de ejecución para los programas elaborados en Pthreads y Java (caso agente viajero)[?], permitieron constatar que “C” no fue tan rápido (3 o más veces) como se proclama en varios sitios de la Internet[7], [15], [10]. Por su lado, Java, hasta el momento, no parece ser más rapido que “C”, aunque en las nuevas versiones de Java se prometen mejoras en los tiempos de ejecución de programas multihilos[9]. Cuando se usa Pthreads es más fácil sincronizar utilizando semáforos[3], pero con el uso de los mutex se pueden obtener mejores tiempos de ejecución. Esta condición se verificó cuando se implementó el programa del agente viajero usando semáforos en lugar de mutex[3] y se midió que el tiempo de ejecución con semáforos era mayor (1.01 veces) que en las versiones implementadas con semáforos. Cuando se elaboran programas multihilos, se deben repetir las pruebas infinidad de veces para prever cualquiera condición de carrera que pudiera alterar el resultado final bajo condiciones excepcionales. Estas condiciones excepcionales se descubren después de múltiples corridas del programa con diferentes juegos de datos de entrada. Esto se constató en las primeras versiones del programa de la suma paralela, las cuales “aparentemente” funcionaban bien en las primeras corridas y mostraban errores después de realizar varias pruebas. Referencias [1] Mordechai (Moti) Ben-Ari. Principles of Concurrent and Distributed Programming. Prentice-Hall International, 1990. [2] Peter Chapin. Pthread Tutorial. 2002. También disponible como http://www.ecet.vtc.edu/ ∼pchapin/ pthreadTutorial.pdf. [3] Jorge A. Castellanos D. Estudio comparativo de la interfaz Pthreads del lenguaje de programación “C” y el soporte para programación multihilo de Java. Universidad de Carabobo. Facultad Experimental de Ciencias y Tecnologı́a. Departamento de Computación. Trabajo de Ascenso, 2003. [4] René Dorta F. Implementación de la interfaz para programación paralela de Modula3 bajo el sistema operativo Mach. Universidad de Carabobo. Escuela de Ingenierı́a Eléctrica. Trabajo de Ascenso, 1998. [5] Edward Fowlkes and Takashi Shida. Multithreaded programming. 1996. http://userpages.umbc.edu/∼schmitt/331F96 /tshida1/thread.html. [6] A. Silberschatz; J. Peterson; P. Galvin. Sistemas Operativos - Conceptos Fundamentales. Tercera Edición. Addison-Wesley Iberoamericana, 1994. [7] Eric Galyon. C++ vs Java Performance. Colorado State University, Computer Science Deparment, 1998. También disponible como http://www.cs.colostate.edu/ ∼ cs154/PerfComp/. [8] Ruben Pinilla; Marisa Gil. Jvm: plataform independent vs. performance dependent. Operating Systems Review ACM press, 37(2):44–55, April 2003. [9] JAVAOLYMPUS. Java Performance. 2003. También disponible como http://www.javaolympus.com/java/ PerformanceDirectory.html. [10] Richard P. Martin. C and C++ are being replaced for many applications, but they would never become completely obsolete. Rutgers University - Computer Science Deparment, 2003. También disponible como http://www.cs.rutgers.edu/ rmartin/teaching/ spring03/cs553/papers01/04.pdf. [11] Patrick Naughton; Michael Morrison. The Java Handbook. Osborne/McGraw-Hill, 1996. [12] Herbert Shildt. Java2 Manual de Referencia. 4ta. Edición. McGraw-Hill de España, 2001. [13] William Stallings. Sistemas Operativos Segunda edición. Prentice Hall, Madrid, 1997. [14] Andrew S. Tanenbaum. Sistemas Operativos Modernos. Primera Edición. Prentice Hall Hispanoamericana S. A., 1993. [15] Alavoor Vasudevan. C++ Programming HOW-TO. Rootshell Security - Unix Systems security resources, 2000. También disponible como http://beatbox.suidzer0.org/ docs/c++programming/C++ProgrammingHOWTO.html#toc1.