Esta es una vista previa del archivo. Inicie sesión para ver el archivo original
Academia/.DS_Store __MACOSX/Academia/._.DS_Store Academia/Practica2/Enunciado Práctica 2 20_21 v2.pdf Enunciado Práctica Evaluable 2 Programación Concurrente 3º Grado en Ingeniería de Computadores 1 Enunciado Práctica Evaluable 2 Almacén de pedidos online Objetivo Que el alumno ponga en práctica los conocimientos teóricos y prácticos adquiridos en la Parte II (Tema 5 en adelante) de la asignatura de Programación Concurrente. Normativa, fecha y mecanismo de entrega La práctica podrá realizarse de forma individual o por parejas. La fecha límite para la entrega de la práctica será el día indicado en la actividad correspondiente en el Aula Virtual, a través del cual se realizará la entrega de la práctica. Se deberá entregar un .zip cuyo contenido será el proyecto Eclipse en el que se ha realizado la práctica. El nombre del fichero .zip tiene que ser igual al identificador del alumno en la URJC (la parte antes de la @ del correo electrónico del alumno en la URJC). En caso de que la práctica haya sido realizada por dos alumnos, se incluirán ambos nombres separados por “-” y ambos alumnos deben entregar la práctica de manera independiente. Además del código fuente, se deberá elaborar una breve memoria explicativa del mismo en formato PDF. La memoria deberá guardarse en la carpeta src/main/resources y tendrá el nombre “memoria.pdf”. Deberá incluir como mínimo, las sincronizaciones y las estructuras de datos usadas. Los alumnos que hayan cursado esta asignatura el curso anterior y que hubieran aprobado la práctica 2 en dicho curso no tendrán que volver a realizarla. Esta práctica será convalidada por defecto con la práctica 2 del curso anterior, asignando la nota que se obtuviera. No obstante, si el alumno desea realizar la práctica nuevamente, puede hacerla y entregarla en forma y fecha. Enunciado Una tienda de muebles a medida, llamado Ukea, decide simular en un programa informático concurrente la interacción que se produce en cada una de sus tiendas. Esta simulación les permitirá analizar de forma más precisa su funcionamiento y podrán optimizar sus procesos. Se modelarán diversos tipos de personas: • Cliente: Representa a los clientes que van a realizar pedidos en una tienda. • Empleado: Representa a todos los tipos de empleados de Ukea. Esta categoría se divide en las siguientes: o EmpCogePedidos: Empleados encargados de atender a los clientes para tomarles el pedido y, cuando ha sido dispensado, cobrarles. o EmpElaboraProductos: Existen algunos productos especiales que necesitan una preparación previa, ya que se componen a su vez de varios productos. Los empleados encargados de elaborar productos se encargan de esta labor. Enunciado Práctica Evaluable 2 Programación Concurrente 3º Grado en Ingeniería de Computadores 2 o EmpPreparaPedidos: Empleados encargados de entregar los pedidos a los clientes con todos los productos que han pedido. Lo hacen en un dispensador de pedidos. o EmpEncargado: Encargado de la tienda, que supervisa el funcionamiento de la misma. La tienda recibe pedidos las 14 horas que está abierta, no obstante, los trabajadores solo trabajan en dos turnos de 7 horas cada uno. A continuación, se describe el ciclo de vida de cada uno de los procesos: • Cliente: Un cliente podrá hacer un pedido en cualquier instante del horario de apertura. Cuando no hace un pedido realizará su vida normal (se supone que hace compras cada cierto tiempo aleatorio). • Empleados: Todos los empleados realizarán su vida normal y luego irán al trabajo. Empezarán a trabajar cuando el encargado se los indique y acabaran cuando el encargado le diga que tienen que dejar de trabajar. En ese momento se marcharán de la tienda. Realizarán su vida cotidiana y volverán a trabajar cuando el encargado les avise. • EmpCogePedidos: Este empleado atenderá pedidos de los clientes. Recogerá un pedido (cada vez) y comprobará que hay existencias en el almacén. Si es así (hay unidades de todos los productos pedidos) informará a los EmpElaboraProductos para que comiencen con la elaboración de los productos especiales (si los hay) y a los EmpPreparaPedido para que realicen su tarea (preparar el pedido y dárselo al cliente). Cuando el pedido ha sido puesto en el dispensador, avisará al cliente y procederá a cobrar el importe del pedido. • EmpElaboraProductos: Este empleado estará a la espera de que le pidan que elabore un producto especial. Cuando reciba la petición, elaborará el producto pedido (retirando los productos del almacén) y lo dejará en una zona intermedia para que sean recogido por el EmpPreparaPedido. • EmpPreparaPedido: Este empleado se encarga de recoger los productos del almacén que no necesitan una preparación especial y de esperar a que los EmpElaboraProductos elaboren los productos más complejos. Cuando el pedido está completo le entrega por un dispensador el pedido al cliente. Si no hay dispensadores libres, deberá esperar. Cuando haya dejado el pedido en el dispensador, avisará al EmpcogePedidos para que avise al cliente que su pedido está preparado y listo para recoger. • EmpEncargado: Este empleado se encargará de abrir el almacén y la tienda por la mañana y de cerrarlos por la noche. Cuando la tienda está abierta al público, revisará a intervalos regulares el trabajo de los empleados y el comportamiento de los clientes. Entre otras cosas, revisará el número de dispensadores libres y el tamaño de la cola de clientes. Para implementar el programa se ofrecen un conjunto de clases que forma la arquitectura de la aplicación. La clase principal es la clase Personal, que contiene el ciclo de vida de todos los procesos. Las clases auxiliares Pedido, Producto, ProductoEspecial se utilizan en la implementación de la clase Personal. La clase Ukea es la encargada de realizar todas las sincronizaciones y comunicaciones entre los hilos del programa que representan personas. A continuación, se muestra el código fuente de Personal: Enunciado Práctica Evaluable 2 Programación Concurrente 3º Grado en Ingeniería de Computadores 3 package ukea; public class Personal { private static final int NUM_CLIENTES = 8; private static final int NUM_EMP_COGE_PEDIDOS = 4; private static final int NUM_EMP_ELABORA_PRODUCTOS = 3; private static final int NUM_EMP_PREPARA_PEDIDO = 3; private static final int NUM_DISPENSADORES = 2; private static Ukea ukea = new Ukea(NUM_DISPENSADORES); // ---------------- Métodos procesos ----------------- public static void empCogePedidos() { …. } public static void empElaboraProductos() { …. } public static void empPreparaPedido() { … } public static void cliente() { … } public static void encargado() { } // ------------------------- Main ------------------------- public static void main(String[] args) { //Creación de hilos que realizan llamadas a los métodos correspondientes } } Enunciado Práctica Evaluable 2 Programación Concurrente 3º Grado en Ingeniería de Computadores 4 Se pide Se pide implementar completamente en Java un programa concurrente que simule el funcionamiento de una tienda Ukea. Para ello se deberá implementar completamente la clase Ukea y Personal, así como el resto de clase auxiliares. Además, si lo estimas conveniente, el código que se proporciona con este enunciado podrá ser modificado para cumplir con ciertos requisitos del enunciado. Para la implementación se deberán tener en cuenta las siguientes consideraciones: • Cualquier decisión respecto al enunciado, no especificada en el mismo, debe explicarse ade- cuadamente en la memoria. • Deberán utilizarse las clases de la API de Java más adecuadas para implementar la sincroni- zación y comunicación de la clase Ukea. Queda excluido el uso de la biblioteca SimpleConcu- rrent (empleada en la primera parte de la asignatura). • El número de empleados de cada tipo debe poder configurarse fácilmente. Por ejemplo, me- diante una serie de constantes. • Cuando el encargado notifica a sus empleados el fin de la jornada, podrá usar interrupciones. • El número de pedido es un contador que se iniciará en 1 cada vez que se ejecute el pro- grama y se incrementa por cada pedido. • El horizonte temporal en el que llegan los pedidos y la velocidad de llegada de los pedidos debe poder configurarse también mediante algún parámetro. • Se recomienda asignar un factor rectificación de tiempo, de modo que se pueda simular una ejecución de 24 horas en unos cuantos segundos. • Toda la información de lo que está sucediendo debe mostrarse por pantalla. Por ejemplo, cuando un determinado cliente hace un pedido, se mostrará los datos del cliente y del pedido. También debe mostrarse cuando el pedido es recogido, cuando se está elaborando, cuando está depositado en un determinado dispensador, etc. • NOTA: Para realizar la simulación, se supondrá lo siguiente: • Cada producto se identifica por un valor entero único y tiene un precio. • En el almacén hay 10 productos (identificados del 0 al 9) y 15 unidades de cada pro- ducto. • Los pedidos de cada cliente se generan de forma aleatoria. Como mucho, compra 5 productos en cada pedido. Los productos especiales se generan con un número de dos cifras, que significa que están compuestos por una unidad de cada producto asociado al identificador. Es decir, si el cliente pide un producto especial con el número 23, eso significa que está compuesto por un producto con id= 2 y con un producto con id=3. • Los clientes tienen 300 euros para gastar. Cuando no pueden pagar el pedido, se anula. • Si un pedido de un cliente no puede servirse (porque el cliente no pueda pagar o porque no haya suficientes unidades en el almacén) el producto se anula. Objetivo Normativa, fecha y mecanismo de entrega Enunciado Se pide __MACOSX/Academia/Practica2/._Enunciado Práctica 2 20_21 v2.pdf Academia/Tema4/Tema 4 - Concurrencia en los lenguajes.pdf Programación Concurrente Tema 3 Concurrencia en los lenguajes de programación Micael Gallego micael.gallego@urjc.es @micael_gallego mailto:micael.gallego@urjc.es 2 Concurrencia en los Lenguajes de Programación • Introducción • Concurrencia en Java • Concurrencia en C/C++ • Concurrencia en JavaScript • Conclusiones PROGRAMACIÓN CONCURRENTE 3 Introducción • Para implementar un programa informático deben seleccionarse la plataforma de desarrollo: Sistema Operativo: Windows XP, Ubuntu, Windows 7, Mac OS X, iOS, Android, RedHat, … Lenguaje de programación: C, C++, Java, Python, COBOL, ActionScript, JavaScript, Ruby… Librerías: Las librerías disponibles para cada lenguaje en cada Sistema Operativo CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 4 Sistema Operativo • Hay muchos tipos de sistemas operativos: Familias: Windows (WinXP, Win7), Unix (linux, MacOS, Android, iOS) y otros tipos como Solaris Arquitecturas HW: 32bits vs 64bits (x86 vs AMD64), RISC vs CISC (x86 vs ARM). Uso (Empotrados, PC Escritorio, Servidores): iOS, Android, Windows 7, Ubuntu, Solaris, HPUX, AIX… INTRODUCCIÓN 5 Lenguaje de programación • Hay muchos lenguajes de programación • Se diferencian entre sí por…: Sistema de tipos estático vs dinámico Compilado vs Interpretado vs Máquina Virtual Portables vs Centrado en una Arquitectura Orientados a sistemas vs Lenguajes de script Imperativo vs Declarativo Imperativo: Orientado a Objetos vs Estructurado Declarativo: Funcional vs Lógico INTRODUCCIÓN 6 Lenguaje de programación • Ejemplos Java: Tipos estáticos, portable, orientado a objetos, Máquina virtual C/C++: Tipos estáticos, compilado, sistemas, imperativo, estructurado JavaScript: Tipos dinámicos, portable, estructurado y orientado a objetos, interpretado y máquina virtual INTRODUCCIÓN 7 Librerías • Las librerías* son muy importantes para el desarrollo de aplicaciones Librerías del sistema operativo: win32 (windows), POSIX (linux), librerías Android, librerías iOS… Librerías de la plataforma de desarrollo: Java, JavaScript (navegador web), Python… Librerías de terceros: Librerías comerciales o libres, librerías portables entre sistemas operativos, librerías genéricas vs específicas INTRODUCCIÓN Deberían llamarse bibliotecas al ser una traducción de “library” 8 Plataforma de desarrollo • Para desarrollar una aplicación hay que tener en cuenta la plataforma de desarrollo Lenguaje de programación Sistema Operativo Librerías • La programación concurrente está muy influenciada por la plataforma de desarrollo elegida INTRODUCCIÓN 9 Plataformas de desarrollo • A continuación se realizará una presentación básica de la programación concurrente en las siguientes plataformas de desarrollo: • Esto permite al alumno tener una visión general de las diferentes formas de utilizar la programación concurrente al desarrollar una aplicación INTRODUCCIÓN Java C/C++ JavaScript 10 Concurrencia en los Lenguajes de Programación • Introducción • Concurrencia en Java • Concurrencia en C/C++ • Concurrencia en JavaScript • Conclusiones PROGRAMACIÓN CONCURRENTE 11 Concurrencia en Java • Java es una plataforma de desarrollo formada por Lenguaje de programación Java Máquina virtual de Java (JVM) Librería estándar (API) • La máquina virtual y la librería estándar ofrece portabilidad en sistemas operativos y plataformas hardware CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 12 Concurrencia en Java •Modelo de concurrencia Java ofrece un modelo de concurrencia de memoria compartida integrado en el lenguaje Java y en la librería estándar El soporte de concurrencia ha evolucionado mucho en Java En las últimas versiones aparecen herramientas de alto nivel en la librería estándar que permiten al desarrollador abstraerse de detalles de bajo nivel CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 13 Concurrencia en Java • La concurrencia también está integrada en el propio lenguaje de programación Está definido cómo se comparte la memoria entre diferentes hilos en el Modelo de Memoria de Java [1] (Java Memory Model) Existen palabras reservadas en el propio lenguaje para delimitar zonas bajo exclusión mutua (synchronized) y para especificar que un atributo es compartido entre hilos (volatile) CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN [1] http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html 14 Concurrencia en Java • También tiene soporte en la librería estándar Como Java es un lenguaje orientado a objetos, los hilos se representan como objetos de la clase java.lang.Thread Esta clase tiene métodos para controlar el ciclo de vida del hilo (configurar su nombre, prioridad, iniciar, esperar a que finalice, etc…) En Java los hilos se pueden crear y destruir en cualquier momento de la ejecución del programa, no tienen que seguir la rígida estructura de los programas de SimpleConcurrent CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 15 package ejemplo.gestionhilo; public class Programa { public static void main(String[] args) { new Thread(()->{ System.out.println("Soy un hilo"); }).start(); } } Comienza la ejecución del hilo, el método start() no se espera a que finalice la ejecución del hilo. El código del hilo se codifica en una expresión lambda Concurrencia en Java CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 16 Concurrencia en Java • En java se pueden usar otros modelos de concurrencia con librerías Actores: Akka: http://akka.io/ Quasar: http://www.paralleluniverse.co/quasar/ Memoria software transaccional: Multiverse: http://multiverse.codehaus.org Comunicando procesos secuenciales (CSP): JCSP: http://www.cs.kent.ac.uk/projects/ofa/jcsp/ Programación funcional: Framework Collections en Java 8: http://java.dzone.com/articles/title-devoxx-2012-java-8 RxJava: https://github.com/Netflix/RxJava CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN http://akka.io/ http://akka.io/ http://www.paralleluniverse.co/quasar/ http://www.paralleluniverse.co/quasar/ http://multiverse.codehaus.org/ http://www.cs.kent.ac.uk/projects/ofa/jcsp/ http://java.dzone.com/articles/title-devoxx-2012-java-8 https://github.com/Netflix/RxJava 17 Concurrencia en Java • En la mayoría de lenguajes de programación imperativos se usan las mismas herramientas presentes en Java • La plataforma Java se estudiará en detalle en el Tema 5 – Programación Concurrente en Java • Se desarrollarán programas concurrentes reales utilizando herramientas profesionales CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 18 Concurrencia en los Lenguajes de Programación • Introducción • Concurrencia en Java • Concurrencia en C/C++ • Concurrencia en JavaScript • Conclusiones PROGRAMACIÓN CONCURRENTE 19 Concurrencia en C/C++ • C/C++ es una familia de lenguajes de programación de bajo nivel • Están diseñados para el acceso al hardware y el rendimiento • Esto hace que se usen especialmente en: Programación de sistemas Sistemas empotrados con recursos limitados Computación de altas prestaciones CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 20 Concurrencia en C/C++ • Existen diversos estándares de C/C++ C90, C99, C++03, C++11, C++14 • Existen muchos compiladores de C/C++ con diferentes soporte de los estándares gcc: http://gcc.gnu.org/ clang: http://clang.llvm.org/ VisualC++: http://www.microsoft.com/visualstudio/ Intel C/C++ Compilers: http://software.intel.com/en-us/c-compilers/ Hay muchos más… (http://en.wikipedia.org/wiki/C99) CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN http://gcc.gnu.org/ http://gcc.gnu.org/ http://clang.llvm.org/ http://www.microsoft.com/visualstudio/ http://software.intel.com/en-us/c-compilers/ http://en.wikipedia.org/wiki/C99 21 Concurrencia en C/C++ • Existen dos enfoques para programación concurrente en C/C++ Concurrencia en el lenguaje: Extensiones del lenguaje no estándar que “paralelizan” el código automáticamente para que algunas partes sean ejecutadas en paralelo Concurrencia con librerías: Librerías no estándar para el desarrollo de programas concurrentes CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 22 Concurrencia en C/C++ • Concurrencia en el lenguaje Existen extensiones de algunos compiladores Amplían C/C++ con nuevas palabras reservadas Usando esas palabras reservadas se transforma el código para que sea ejecutado en diferentes hilos de ejecución Normalmente transforman bucles de secuencial a paralelo Más importantes: OpenMP e Intel Cilk Plus CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 23 Concurrencia en C/C++ • OpenMP Disponible en los compiladores Intel C/C++, gcc, … Permite ejecutar concurrentemente bucles, bloques, etc. Trabaja con un esquema fork / join para que un hilo principal reparta el trabajo en hilos y espere al resultado de todos ellos http://openmp.org/ CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN http://openmp.org/ 24 Concurrencia en C/C++ • Ejemplo OpenMP CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN double ProductoPunto(double* a, double* b, int size) { double c = 0; #pragma omp parallel for reduction(+:c) for (int i = 0; i < size; ++i) { c += a[i]*b[i]; } return c; } Con este comando se permite paralelizar la ejecución de las iteraciones del buble y luego “reducir” los valores de cada hilo con una operación de suma 25 Concurrencia en C/C++ • Intel Cilk Plus Disponible en los compiladores Intel C/C++ y gcc Usando sólo tres nuevas palabras clave se pueden paralelizar operaciones sobre arrays http://www.cilkplus.org/ CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN http://www.cilkplus.org/ 26 Concurrencia en C/C++ • Ejemplo Intel Cilk Plus CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN int fib(int n) { if (n < 2) return n; int x = cilk_spawn fib(n-1); int y = fib(n-2); cilk_sync; return x + y; } Con este comando se indica que fib(n-1) se puede ejecutar en un nuevo hilo en paralelo con el hilo principal que ejecutará fib(n-2) Con este comando se indica que el hilo se bloquea hasta que han terminado los otros hilos 27 Concurrencia en C/C++ • Concurrencia con Librerías La librería estándar de C++ era muy básica, y en la práctica cada sistema operativo ofrecía su propio conjunto de librerías Win32: Librería de la familia de sistemas operativos windows (95, 98, XP, 2000, Vista, 8..) POSIX, GTK, KDE…: Librerías de la familia de sistemas operativos UNIX / Linux (RedHay, Ubuntu…) CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 28 Concurrencia en C/C++ • Win32 y POSIX permiten la programación concurrente en un modelo de memoria compartida Cada librería con tipos y funciones (API) diferentes Win32 API win32 ofrece las primitivas de concurrencia. http://msdn.microsoft.com/en-us/library/ms686937(v=vs.85).aspx Otras librerías concurrentes para Visual C++: http://archive.msdn.microsoft.com/concrtextras POSIX Podemos referirnos a ellos como Native POSIX Threads Library (NPTL) o pthreads https://computing.llnl.gov/tutorials/pthreads/ CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN http://msdn.microsoft.com/en-us/library/ms686937(v=vs.85).aspx http://archive.msdn.microsoft.com/concrtextras https://computing.llnl.gov/tutorials/pthreads/ 29 Concurrencia en C/C++ • Existen algunas librerías multiplataforma para programación concurrente con memoria compartida que se pueden usar en Unix, Mac y Windows Boost También tiene otro tipo de funcionalidades http://www.boost.org/doc/libs/1_43_0/doc/html/thread.html Intel Threading Building Blocks También dispone de herramientas de alto nivel (estructuras de datos, gestión de hilos, …) http://threadingbuildingblocks.org/ CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN http://www.boost.org/doc/libs/1_43_0/doc/html/thread.html http://threadingbuildingblocks.org/ 30 Ejemplo POSIX pthreads Tema 4 - Concurrencia en los Lenguajes de Programación 30 void *thr_func(void *arg) { printf("hello from new thread\n"); pthread_exit(NULL); } int main(int argc, char **argv) { pthread_t thr[NUM_THREADS]; int i, rc; for (i = 0; i < NUM_THREADS; ++i) { rc = pthread_create (&thr[i], NULL, thr_func, NULL); if (rc) { fprintf(stderr, "error rc: %d\n", rc); return -1; } } for (i = 0; i < NUM_THREADS; ++i) { pthread_join(thr[i], NULL); } return 0; } Código que se ejecuta en el hilo de forma concurrente Creación de los hilos e inicio de la ejecución Bloqueo hasta que terminan los hilos 31 Ejemplo win32 Threads Tema 4 - Concurrencia en los Lenguajes de Programación 31 DWORD WINAPI thr_func(LPVOID args) { printf("hello from new thread\n"); return 0; } int main(int argc, char **argv) { DWORD thrIds[NUM_THREADS]; HANDLE thr[NUM_THREADS]; int i, rc; for (i = 0; i < NUM_THREADS; ++i) { thr[i] = CreateThread (0,0, thr_func,0,0,&thrIds[i]); if (!thr[i]) { fprintf(stderr, "error in threads\n”); return -1; } } for (i = 0; i < NUM_THREADS; ++i) { WaitForSingleObject(thr[i], INFINITE); CloseHandle(thr[i]); } return 0; } Código que se ejecuta en el hilo de forma concurrente Creación de los hilos e inicio de la ejecución Bloqueo hasta que terminan los hilos 32 Concurrencia en C/C++ • C++11 Define una forma estándar de crear hilos CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN #include <thread> #include <iostream> int main(){ std::thread t1([](){ std::cout << "Hello from thread " << std::endl; }); t1.join(); return 0; } https://www.classes.cs.uchicago.edu/archive/2013/spring/12300-1/labs/lab6/ https://www.classes.cs.uchicago.edu/archive/2013/spring/12300-1/labs/lab6/ https://www.classes.cs.uchicago.edu/archive/2013/spring/12300-1/labs/lab6/ 33 Concurrencia en C/C++ • Conclusiones Las plataformas mayoritarias tienen librerías para la programación concurrente de bajo nivel con memoria compartida (win32, POSIX…) El estándar C++11 ya proporciona una forma estándar de gestionar hilos CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 34 Concurrencia en C/C++ • Conclusiones Existen librerías multiplataforma que proporcionan herramientas de más alto nivel (Boost, Intel TBB…) Se pueden usar variantes del lenguaje C/C++ que ofrecen herramientas del alto nivel especialmente diseñadas para paralelización de algoritmos (OpenMP, Intel Cilk Plus…) CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 35 Concurrencia en los Lenguajes de Programación • Introducción • Concurrencia en Java • Concurrencia en C/C++ • Concurrencia en JavaScript • Conclusiones PROGRAMACIÓN CONCURRENTE 36 Concurrencia en JavaScript • JavaScript es un lenguaje que se diseñó para ejecutarse en el contexto de una página web dentro de un navegador web • Los navegadores actuales ejecutan el código JavaScript con máquinas virtuales* (V8 de Chrome, IonMonkey en Firefox…) • JavaScript es el nombre “popular” del estándar ECMAScript • Muchos de los aspectos de la plataforma de desarrollo con JavaScript se definen bajo el estándar HTML5 CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN * http://en.wikipedia.org/wiki/JavaScript_engine http://en.wikipedia.org/wiki/JavaScript_engine 37 Concurrencia en JavaScript • El estándar WebWorkers dentro de HTML5 permite usar un modelo de programación concurrente de paso de mensajes en JavaScript • A los hilos en segundo plano se les denomina WebWorkers (o simplemente workers) • Los workers no usan memoria compartida, sólo se comunican mediante mensajes entre hilos • Un worker se puede iniciar desde el script principal (main) o desde otro worker CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 38 Concurrencia en JavaScript • En JavaScript el código siempre se ejecuta en respuesta a un evento (carga de página, evento del usuario, temporizador, llamada AJAX…) • Un worker funciona de forma similar, sólo ejecuta código ante la llegada de un mensaje desde el script principal o desde otro worker • Un worker también puede enviar mensajes de vuelta • Tutorial: http://anders.janmyr.com/2013/02/web-workers.html CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN http://anders.janmyr.com/2013/02/web-workers.html 39 Ejemplo WebWorkers self.addEventListener('message', function(e) { //do somethin useful self.postMessage(e.data); }, false); Código del hilo (WebWorker) var worker = new Worker('doWork.js'); worker.addEventListener('message', function(e) { console.log('Worker said: ', e.data); }, false); // Send data to the worker. worker.postMessage('Hello World'); Script principal doWork.js Ejecución del hilo (WebWorker) al enviarle un mensaje Código de respuesta al recibir un mensaje del WebWorker Concurrencia en JavaScript CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 40 Concurrencia en JavaScript • También existen extensiones (no estándar) del lenguaje JavaScript para concurrencia Intel RiverTrail Enfocado a la implementación de algoritmos paralelos Disponible como plugin de Firefox Quizás se estandarice en el futuro https://github.com/RiverTrail/RiverTrail/wiki http://www.infoq.com/news/2011/11/webgl-webcl-multi core-rivertrail CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN https://github.com/RiverTrail/RiverTrail/wiki https://github.com/RiverTrail/RiverTrail/wiki http://www.infoq.com/news/2011/11/webgl-webcl-multicore-rivertrail http://www.infoq.com/news/2011/11/webgl-webcl-multicore-rivertrail 41 Concurrencia en JavaScript • Actualmente se está popularizado el uso de JavaScript fuera del navegador • El más popular es el servidor web node.js (basado en V8 de Chrome) que utiliza librerías (módulos) • Módulos para WebWorkers en node.js https://npmjs.org/package/webworker-threads • Módulos para la gestión directa de procesos en node.js http://nodejs.org/api/child_process.html https://github.com/passcod/DynWorker CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN https://npmjs.org/package/webworker-threads http://nodejs.org/api/child_process.html https://github.com/passcod/DynWorker 42 Concurrencia en los Lenguajes de Programación • Introducción • Concurrencia en Java • Concurrencia en C/C++ • Concurrencia en JavaScript • Conclusiones PROGRAMACIÓN CONCURRENTE 43 Conclusiones • Cada plataforma de desarrollo (lenguaje, sistema operativo y librerías) tiene sus propios mecanismos de programación concurrente • Las plataformas que soportan el modelo de memoria compartida: Suelen ofrecer herramientas de bajo nivel similares: Gestión de hilos y herramientas de sincronización Habitualmente disponen de librerías de alto nivel: Estructuras de datos concurrentes, herramientas avanzadas de sincronización, otros modelos de programación concurrente…) CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 44 Conclusiones • Existen plataformas muy extendidas que tienen un soporte de concurrencia limitado o únicamente ofrecen modelos de alto nivel (JavaScript, Go, ActionScript/Flash…) • Esto se debe a que la programación concurrente es compleja y se ha restringido en ámbitos en los que se considera que no es imprescindible o se puede ofrecer un modelo más sencillo CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN 45 Conclusiones • La programación concurrente no es una disciplina nueva en informática • Pero la llegada de los procesadores multicore ha obligado a las plataformas de desarrollo a ofrecer herramientas para aprovechar (y no desperdiciar) todo esa potencia de cómputo • En los próximos años evolucionarán y se popularizarán las herramientas de concurrencia que ofrecen las plataformas CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN Slide 1 Concurrencia en los Lenguajes de Programación Introducción Sistema Operativo Lenguaje de programación Lenguaje de programación Librerías Plataforma de desarrollo Plataformas de desarrollo Concurrencia en los Lenguajes de Programación Concurrencia en Java Concurrencia en Java Concurrencia en Java Concurrencia en Java Concurrencia en Java Concurrencia en Java Concurrencia en Java Concurrencia en los Lenguajes de Programación Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Ejemplo POSIX pthreads Ejemplo win32 Threads Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en C/C++ Concurrencia en los Lenguajes de Programación Concurrencia en JavaScript Concurrencia en JavaScript Concurrencia en JavaScript Ejemplo WebWorkers Concurrencia en JavaScript Concurrencia en JavaScript Concurrencia en los Lenguajes de Programación Conclusiones Conclusiones Conclusiones __MACOSX/Academia/Tema4/._Tema 4 - Concurrencia en los lenguajes.pdf Academia/Tema3/Tema3 - Concurrencia con paso de mensajes.pdf Programación Concurrente Tema 3 Paso de Mensajes Micael Gallego micael.gallego@urjc.es @micael_gallego mailto:micael.gallego@urjc.es 2 Concurrencia con Paso de Mensajes ● Introducción ● Identificación de procesos ● Sincronización ● Canal de comunicación ● Conclusiones PROGRAMACIÓN CONCURRENTE 3 ● Las técnicas usadas para la construcción de programas concurrentes se dividen en dos grandes modelos: ● Memoria compartida: Los procesos pueden acceder a una memoria compartida para comunicarse y sincronizarse. ● Paso de mensajes: Los procesos sólo se pueden comunicar y sincronizar con el envío de mensajes de uno a otro. Introducción CONCURRENCIA CON PASO DE MENSAJES 4 ● ¿Cuándo usar paso de mensajes? ● Tiene que usarse obligatoriamente en sistemas multiprocesador poco acoplados (sistemas distribuidos) ● Puede usarse también en sistemas muy acoplados: ● El programa será más escalable (podrá ser usado en un cluster en el futuro) ● Cuando el modelo de concurrencia sea más adecuado para el programa concreto ● Cuestión de gustos. Introducción CONCURRENCIA CON PASO DE MENSAJES 5 Introducción •Modelos híbridos Es habitual que un sistema informático distribuido combine ambos modelos. Modelo de paso de mensajes para comunicación entre nodos. Modelo de memoria compartida para comunicación entre procesos dentro del mismo nodo. Pero cada vez se usan más modelos puros basado en paso de mensajes en ambos niveles CONCURRENCIA CON PASO DE MENSAJES 6 • Primitivas básicas en un modelo de paso de mensajes: Send: Envía información (mensaje) a otro proceso Receive: Recibe información (mensaje) de otro proceso Introducción CONCURRENCIA CON PASO DE MENSAJES Emisor ReceptorCanal Mensaje 7 • Estas primitivas pueden ser: Explícitas: Cuando se usan directamente por el programador Implícitas: Cuando el modelo las oculta dentro de otras primitivas de más alto nivel Introducción CONCURRENCIA CON PASO DE MENSAJES 8 • Existen muchos tipos concretos de modelos de paso de mensajes dependiendo de: Identificación de procesos al enviar o recibir (nombrado, direccionamiento, denominación…) Sincronización: Envío y recepción bloqueantes (síncronas) o no bloqueantes (asíncronas) Características del canal: Capacidad, tipo de datos, etc… Introducción CONCURRENCIA CON PASO DE MENSAJES 9 Concurrencia con Paso de Mensajes • Introducción • Identificación de procesos • Sincronización • Canal de comunicación • Conclusiones PROGRAMACIÓN CONCURRENTE 10 • Identificación de procesos La forma en la que el proceso emisor indica a qué proceso receptor va dirigido el mensaje y viceversa Operaciones: Identificación de procesos CONCURRENCIA CON PASO DE MENSAJES void send(Id receiverId, Message message) Message receive(Id senderId) 11 • Comunicación directa simétrica: Tanto el proceso emisor como el proceso receptor indican el id del otro proceso. Proceso A ejecuta: send(“B”, Message) Proceso B ejecuta: m = receive(“A”) Desventajas: La identificación explícita de el receptor y el emisor puede hacer el sistema poco flexible a futuras ampliaciones Identificación de procesos CONCURRENCIA CON PASO DE MENSAJES 12 • Comunicación directa asimétrica: Normalmente el proceso emisor indica el id del receptor, pero no al contrario Proceso A ejecuta: send(“B”, Message) Proceso B ejecuta: m = receive() Ventajas: Es más flexible que el modelo simétrico Es el modelo usado en arquitectura cliente / servidor en sistemas distribuidos Habitualmente el receptor puede conocer qué emisor concreto le ha enviado cada mensaje Identificación de procesos CONCURRENCIA CON PASO DE MENSAJES 13 • Comunicación directa vs comunicación indirecta Comunicación directa: Los procesos identifican a otros procesos Comunicación indirecta: Los procesos emisores envían los mensajes a almacenes intermedios y los receptores los recogen de esos almacenes intermedios Identificación de procesos CONCURRENCIA CON PASO DE MENSAJES 14 • Comunicación indirecta La comunicación se realiza con almacenes intermedios Esos almacenes se denominan buzones o colas Identificación de procesos CONCURRENCIA CON PASO DE MENSAJES void send(Id queueId, Message message) Message receive(Id queueId) 15 • Comunicación indirecta Dependiendo de las restricciones en las colas y los mensajes existen diversos modelos: Varios procesos pueden recibir mensajes de una misma cola o cada cola puede ser exclusiva por proceso receptor Un mensaje enviado a una cola puede ser recibido por un único proceso (unicast) o por todos los procesos (multicast) Un mismo proceso puede enviar mensajes a varias colas La creación de colas puede ser estática o dinámica (en tiempo de ejecución) Identificación de procesos CONCURRENCIA CON PASO DE MENSAJES 16 Concurrencia con Paso de Mensajes • Introducción • Identificación de procesos • Sincronización • Canal de comunicación • Conclusiones PROGRAMACIÓN CONCURRENTE 17 • Tipos de comunicaciones en función de la sincronización: Comunicación síncrona: Emisor y receptor coinciden en el tiempo en el momento de la comunicación (ejemplo: teléfono) Comunicación asíncrona: Emisor y receptor tienen que coincidir en el tiempo para llevar a cabo la comunicación (ejemplo: mail) Sincronización CONCURRENCIA CON PASO DE MENSAJES 18 • Bloqueo de envío y recepción Comunicación síncrona: El emisor queda bloqueado en la operación de envío hasta que el receptor ejecute la operación de recepción El receptor queda bloqueado en la operación de recepción Sincronización CONCURRENCIA CON PASO DE MENSAJES Cita simple (rendez-vous) 19 • Bloqueo de envío y recepción Comunicación síncrona (caso especial): El emisor queda bloqueado en la operación de envío hasta que el receptor ejecute la operación de recepción, procese el mensaje y envíe un mensaje de vuelta, que será recibido por el emisor. Sincronización CONCURRENCIA CON PASO DE MENSAJES Cita extendida (extended rendezvous) 20 • Bloqueo de envío y recepción Comunicación síncrona (tiempo de espera): En algunos sistemas, las operaciones de envío y recepción bloqueantes disponen de versiones en las que se puede especificar un timeout. Sincronización CONCURRENCIA CON PASO DE MENSAJES 21 • Bloqueo de envío y recepción Comunicación asíncrona: El emisor no queda bloqueado en la operación de envío, los mensajes se guardan en un buffer hasta que el receptor los recoge El receptor puede ejecutar la operación de recepción sin bloquearse Sincronización CONCURRENCIA CON PASO DE MENSAJES 22 • Bloqueo de envío y recepción Comunicación asíncrona (casos especiales): El emisor podría quedarse bloqueado en la operación de envío si el buffer se llena de mensajes pendientes de recibir. El receptor podría querer bloquearse en la operación de recepción si no hay mensajes Sincronización CONCURRENCIA CON PASO DE MENSAJES 23 Concurrencia con Paso de Mensajes • Introducción • Identificación de procesos • Sincronización • Canal de comunicación • Conclusiones PROGRAMACIÓN CONCURRENTE 24 • Características del canal de comunicación Flujo de datos: bidireccional o unidireccional. La comunicación asíncrona suele ser unidireccional y la síncrona bidireccional. Capacidad del canal: Relevante en comunicación asíncrona (limitada o infinita dependiendo de los recursos HW) Canal de comunicación CONCURRENCIA CON PASO DE MENSAJES 25 • Características del canal de comunicación Tipo y tamaño de los mensajes: Algunos sistemas pueden poner restricciones de todos los mensajes del mismo tipo o del mismo tamaño. Ordenación y fiabilidad del envío: Los enlaces de red pueden ser menos fiables que los canales internos en una máquina Canal de comunicación CONCURRENCIA CON PASO DE MENSAJES 26 Concurrencia con Paso de Mensajes • Introducción • Identificación de procesos • Sincronización • Canal de comunicación • Conclusiones PROGRAMACIÓN CONCURRENTE 27 • El modelo de paso de mensajes es interesante porque permite construir programas escalables (desde un nodo a un cluster) • Existen una gran variedad de detalles en este modelo, y cada tecnología de desarrollo implementa modelos ligeramente diferentes Conclusiones CONCURRENCIA CON PASO DE MENSAJES Slide 1 Concurrencia con Paso de Mensajes Introducción Introducción Introducción Introducción Introducción Introducción Concurrencia con Paso de Mensajes Identificación de procesos Identificación de procesos Identificación de procesos Identificación de procesos Identificación de procesos Identificación de procesos Concurrencia con Paso de Mensajes Sincronización Sincronización Sincronización Sincronización Sincronización Sincronización Concurrencia con Paso de Mensajes Canal de comunicación Canal de comunicación Concurrencia con Paso de Mensajes Conclusiones __MACOSX/Academia/Tema3/._Tema3 - Concurrencia con paso de mensajes.pdf Academia/Tema2/Tema 2. Memoria compartida.pdf Programación Concurrente Tema 2 Memoria Compartida Micael Gallego micael.gallego@urjc.es @micael_gallego mailto:micael.gallego@urjc.es 2 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 3 • SimpleConcurrent es una librería Java diseñada para la enseñanza de los conceptos básicos a la programación concurrente • Soporta los modelos de concurrencia: Modelo de memoria compartida: Hilos y cerrojos Paso de mensajes: Actores • Se puede usar junto con cualquier otra característica básica de Java (Clases, métodos, arrays, etc…) SimpleConcurrent MEMORIA COMPARTIDA 4 • Se ha diseñado para que sea lo más simple posible y que los programas sean muy compactos • Se ha evitado el paradigma orientado a objetos para hacer los programas sencillos • No se debe usar nunca en un programa real porque no está optimizada ni diseñada para ello • Se puede descargar de github como fuente o binario http://www.github.com/codeurjc/simpleconcurrent • Todos los ejemplos y ejercicios de este tema están en github https://github.com/codeurjc/concurrencia-tema2 SimpleConcurrent MEMORIA COMPARTIDA http://www.github.com/codeurjc/simpleconcurrent https://github.com/codeurjc/concurrencia-tema2 5 • Para usar la librería vamos a usar Maven Es una herramienta para gestionar proyectos Java Se descarga automáticamente las librerías (dependencias) de Internet Se puede usar en cualquier entorno de desarrollo actualizado (Eclipse Neon, Netbeans, IntelliJ…) SimpleConcurrent MEMORIA COMPARTIDA 6 • Cómo crear un proyecto Maven en Eclipse New project > Maven > Maven Project Marcar “Create simple project (skip archetype…)” Insertar los datos del proyecto Group Id: es.urjc.pc Artifact Id: ejercicios Finish SimpleConcurrent MEMORIA COMPARTIDA 7 • Cómo crear un proyecto Maven en Eclipse SimpleConcurrent MEMORIA COMPARTIDA Nombre del proyecto Marcar 8 • El nuevo proyecto tiene la siguiente estructura src/main/java: Código de la aplicación src/test/java: Código de los tests pom.xml: Fichero de descripción del proyecto (nombre, dependencias, configuraciones, etc…) SimpleConcurrent MEMORIA COMPARTIDA 9 • pom.xml: Configuración del proyecto SimpleConcurrent MEMORIA COMPARTIDA 10 • pom.xml: Configuración del proyecto Poner la vista de código fuente SimpleConcurrent MEMORIA COMPARTIDA 11 • pom.xml: Configuración del proyecto Todas las opciones del proyecto se configuran en el fichero pom.xml Configuración de Java 8 Ficheros compatibles linux y windows (UTF-8): <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> SimpleConcurrent MEMORIA COMPARTIDA 12 • pom.xml: Configuración del proyecto Librerías (dependencias): Se indica el groupId, artifactId y versión de cada librería que use el proyecto Las librerías se descargan automáticamente Librería SimpleConcurrent versión 0.6 SimpleConcurrent MEMORIA COMPARTIDA <dependencies> <dependency> <groupId>com.github.codeurjc</groupId> <artifactId>simpleconcurrent</artifactId> <version>0.6</version> </dependency> </dependencies> 13 • pom.xml: Configuración del proyecto Repositorios de librerías: La mayoría de las librerías se descargan del repositorio por defecto (Maven Central) Otras librerías están en su propio repositorio Repositorio de SimpleConcurrent: SimpleConcurrent MEMORIA COMPARTIDA <repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories> 14 SimpleConcurrent MEMORIA COMPARTIDA <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>es.urjc.pc</groupId> <artifactId>ejercicios</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.github.codeurjc</groupId> <artifactId>simpleconcurrent</artifactId> <version>0.6</version> </dependency> </dependencies> <repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories> </project> Librería SimpleConcurrent versión 0.5 Java 8 Repositorio de la librería Datos del proyecto pom.xml para usar SimpleConcurrent 15 • Cuidado! Algunos cambios en el fichero pom.xml no se reflejan el eclipse de forma automática • Cuando se hace un cambio y eclipse no se actualiza con esos cambios, se tiene que actualizar manualmente Botón derecho proyecto > Maven > Update Project… SimpleConcurrent MEMORIA COMPARTIDA 16 SimpleConcurrent •Memoria Compartida en SimpleConcurrent Modelo de Hilos y cerrojos La implementación de un programa concurrente con memoria compartida se divide en dos partes: 1) Implementar el código que podrá ser ejecutado concurrentemente (programa secuencial) 2) Iniciar la ejecución de ese código (crear el proceso) MEMORIA COMPARTIDA 17 • Se importan los miembros estáticos de la clase SimpleConcurrent • De esta forma, los métodos estáticos de SimpleConcurrent se pueden usar directamente, sin indicar el nombre de la clase Programa Concurrente SimpleConcurrent package ejemplo; import static es.urjc.etsii.code. concurrency.SimpleConcurrent.*; public class Ejemplo1 { public static void repeat(String text) { for(int i=0; i<5; i++){println(text);} } public static void printText() { println("B1"); println("B2"); println("B3"); } public static void main(String[] args) { createThread("repeat","XXXXX"); createThread("repeat","-----"); createThread("printText"); startThreadsAndWait(); } } Download this code https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/Ejemplo1.java 18 • Se escriben los códigos secuenciales como métodos estáticos de la clase principal • Como cualquier método estático, pueden tener parámetros • Usarán el método println(…) para imprimir por pantalla Programa Concurrente package ejemplo; import static es.urjc.etsii.code. concurrency.SimpleConcurrent.*; public class Ejemplo1 { public static void repeat(String text) { for(int i=0; i<5; i++){println(text);} } public static void printText() { println("B1"); println("B2"); println("B3"); } public static void main(String[] args) { createThread("repeat","XXXXX"); createThread("repeat","-----"); createThread("printText"); startThreadsAndWait(); } } SimpleConcurrent Download this code https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/Ejemplo1.java 19 • En el método main se crea un nuevo hilo con el método createThread(…) • Se indica el nombre del método que será ejecutado en ese nuevo hilo • Se pueden pasar parámetros • Es posible crear varios hilos que ejecutan el mismo método, con distintos parámetros • El método startThreadsAndWait () crea los hilos y se bloquea hasta que han terminado su ejecución Programa Concurrente package ejemplo; import static es.urjc.etsii.code. concurrency.SimpleConcurrent.*; public class Ejemplo1 { public static void repeat(String text) { for(int i=0; i<5; i++){println(text);} } public static void printText() { println("B1"); println("B2"); println("B3"); } public static void main(String[] args) { createThread("repeat","XXXXX"); createThread("repeat","-----"); createThread("printText"); startThreadsAndWait(); } } SimpleConcurrent Download this code https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://gist.github.com/micaelgallego/6575162 https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/Ejemplo1.java 20 • Al ejecutar el programa varias veces se obtienen las siguientes salidas por pantalla • Como se puede ver, se obtienen diferentes resultados en diferentes ejecuciones ----- XXXXX B1 ----- XXXXX ----- B2 ----- XXXXX B3 XXXXX ----- XXXXX Programa Concurrente B1 ----- XXXXX XXXXX B2 ----- XXXXX ----- B3 XXXXX ----- XXXXX ----- ----- XXXXX B1 XXXXX B2 XXXXX ----- XXXXX B3 ----- ----- XXXXX ----- B1 B2 ----- XXXXX B3 ----- ----- XXXXX ----- XXXXX ----- XXXXX XXXXX SimpleConcurrent MEMORIA COMPARTIDA 21 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 22 Intercalación de instrucciones: Indeterminismo • El orden de las instrucciones En un programa secuencial, todas las instrucciones están ordenadas Está definido el orden en el que se van ejecutando las instrucciones y la forma en la que van cambiando los valores de las variables En la programación concurrente, diferentes ejecuciones del mismo programa pueden ejecutar las sentencias en orden diferente MEMORIA COMPARTIDA 23 El orden de las instrucciones INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO package ejemplo; import static es.urjc.etsii.code. concurrency.SimpleConcurrent.*; public class MaxMin { static volatile double n1, n2, min, max; public static void min(){ min = n1 < n2? n1 : n2; } public static void max(){ max = n1 > n2? n1 : n2; } public static void main(String[] args) { n1=3; n2=5; // I0 min(); // I1 max(); // I2 println("m:"+min+" M:"+max); // I3 } } I0 I1 I2 I3 La Relación de Precedencia (->) entre las instrucciones define una relación de orden I0 -> I1 -> I2 -> I3 Programación Secuencial Orden Total Diagrama de Precedencia Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/MaxMin.java 24 El orden de las instrucciones • Existe determinismo • Al ejecutar el programa con los mismos datos de entrada se obtienen los mismos resultados • Hay veces que no es necesario que una sentencia sea ejecutada antes que otra, se podrían ejecutar en cualquier orden ¿Cómo lo podríamos especificar en el código? INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO Programación Secuencial Orden Total I0 I1 I2 I3 Diagrama de Precedencia 25 El orden de las instrucciones INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO I0 I1 I2 I3 I0->I1, I0–>I2, I1->I3,I2->I3 I1 || I2 Programación Concurrente Orden Parcial Diagrama de Precedencia package ejemplo; import static es.urjc.etsii.code. concurrency.SimpleConcurrent.*; public class MaxMinCon { static volatile double n1, n2, min, max; public static void min(){ min = n1 < n2? n1 : n2; } public static void max(){ max = n1 > n2? n1 : n2; } public static void main(String[] args) { n1 =3; n2 =5; // I0 createThread("min"); // I1 createThread("max"); // I2 startThreadsAndWait(); println("m:"+min+" M:"+max); // I3 } } Download the code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/MaxMinCon.java 26 El orden de las instrucciones • No existe determinismo • No se restringe el orden de ejecución de I1 e I2. Podrían ejecutarse en cualquier ordena o de forma concurrentemente INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO I0 I1 I2 I3 I1 || I2 Diagrama de Precedencia Programación Concurrente Orden Parcial 27 Instrucciones atómicas INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 1ª Abstracción de la Programación Concurrente Se considera que cada proceso se ejecuta en su propio procesador 2ª Abstracción de la Programación Concurrente Se ignoran las velocidades relativas de cada proceso, lo que posibilita considerar sólo las secuencias de instrucciones que se ejecutan 28 Instrucciones atómicas • La 1ª y 2ª abstracción permiten abstraernos de detalles como el número de procesadores y su velocidad • Nos permiten centrarnos en las diferentes posibles secuencias de instrucciones que ejecuta cada proceso • Pero… ¿Exactamente qué instrucciones ejecuta un proceso? INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 29 Instrucciones atómicas • Una instrucción atómica es aquella cuya ejecución es indivisible • Independientemente del número de procesadores, una instrucción atómica de un proceso se ejecuta completamente sin interferencias • Durante la ejecución de una sentencia atómica, otros procesos no pueden interferir en su ejecución INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 30 Instrucciones atómicas • Las instrucciones atómicas se usan mucho aplicaciones empresariales • Ejemplo: Reserva de vuelo con escala Un viajero quiere ir de Madrid a Los Ángeles Tiene que hacer escala en New York Se debe reservar el billete Madrid-New York y también de New York-Los Ángeles La reserva no se puede quedar a medias, reservando sólo un trayecto O se reservan ambos trayectos o no se reserva ninguno INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 31 Instrucciones atómicas • En programación concurrente, es muy importante conocer las instrucciones atómicas que ejecuta el procesador • Esas instrucciones atómicas serán las que se ejecuten completamente sin interferencias de otros procesos • Para desarrollar aplicaciones concurrentes en un lenguaje de programación, hay que conocer qué sentencias del lenguaje se corresponden a sentencias atómicas • Las sentencias no atómicas ejecutadas por un proceso, podrán verse interferidas por la ejecución de otros procesos INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 32 Instrucciones atómicas INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO x = x+1 LOAD R,x ADD R,#1 STR R,x Sentencia Java Instrucciones Atómicas Corresponde a las instrucciones atómicas • Incrementar una variable en 1 no es una sentencia atómica en Java • Carga la variable x en el registro R del procesador • Suma 1 al registro R del procesador • Guarda el valor del registro R en la variable x 33 Intercalación (Interleaving) • La 1ª y 2ª abstracción nos permiten pensar en la secuencia de instrucciones que ejecuta cada proceso en su procesador • Pero es bastante complicado pensar en la ejecución en paralelo de múltiples secuencias de instrucciones (una secuencia por cada proceso) • Para que sea más sencillo estudiar el comportamiento de un programa concurrente, se considera que todas las sentencias de todos los procesos se intercalan en una única secuencia INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 34 Intercalación (Interleaving) • No hay solapamientos • La ejecución de dos instrucciones atómicas en paralelo tiene los mismos resultados que una después de otra (de forma secuencial) INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 3ª Abstracción de la Programación Concurrente Se considera que las secuencias de ejecución de las acciones atómicas de todos los procesos se intercalan en una única secuencia 35 Intercalación (Interleaving) • Hay que estudiar lo que ocurre en todas las posibles intercalaciones de instrucciones atómicas (y son muchas) • Así comprenderemos todas las posibles ejecuciones del programa (y comprobaremos si son correctas) INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 36 Intercalación (Interleaving) •Multiprogramación Realmente las instrucciones se ejecutan de forma intercalada porque sólo hay un procesador •Multiproceso Si dos instrucciones compiten por un mismo recurso, el hardware se encarga de que se ejecuten de forma secuencial Si dos instrucciones no compiten, son independientes, el resultado es el mismo en paralelo que de forma secuencial INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 37 Intercalación (Interleaving) • Todas las abstracciones se pueden resumir en una sola INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO Abstracción de la Programación Concurrente Es el estudio de las secuencias de ejecución intercalada de las instrucciones atómicas de los procesos secuenciales Principles of Concurrent and Distributed Programming, 2nd ed. 2006 Addison-Wesley. Ben-Ari. 38 Indeterminismo • Al ejecutar un programa concurrente, se ejecutarán las sentencias con una intercalación determinada y se obtendrá un resultado • Puesto que todas las intercalaciones de instrucciones atómicas son posibles, un mismo programa puede obtener resultados diferentes en diferentes ejecuciones • Cuando un mismo programa obtiene resultados diferentes dependiendo de la ejecución concreta, se dice que es indeterminista INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 39 Indeterminismo INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO public class IncDec { static volatile double x; public static void inc(){ x = x + 1; } public static void dec(){ x = x - 1; } public static void main(String[] args){ x = 0; createThread("inc"); createThread("dec"); startThreadsAndWait(); println("x:"+x); } } LOAD R,x ADD R,#1 STR R,x Instrucciones atómicas del tipo de proceso inc LOAD R2,x SUB R2,#1 STR R2,x Instrucciones atómicas del tipo de proceso dec Download the code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/IncDec.java 40 Indeterminismo • Una posible intercalación de instrucciones INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO inc dec x R R2 1 LOAD R2,x 0 0 2 LOAD R,x 0 0 0 3 SUB R2,#1 0 0 -1 4 ADD R,#1 0 1 -1 5 STR R2,x -1 1 -1 6 STR R,x 1 1 -1 Resultado Final: 1 LOAD R,x ADD R,#1 STR R,x inc LOAD R2,x SUB R2,#1 STR R2,x dec 41 Indeterminismo • Otra posible intercalación de instrucciones INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO inc dec x R R2 1 LOAD R2,x 0 0 2 LOAD R,x 0 0 0 3 ADD R,#1 0 1 0 4 SUB R2,#1 0 1 -1 5 STR R,x 1 1 -1 6 STR R2,x -1 1 -1 Resultado Final: -1 LOAD R,x ADD R,#1 STR R,x inc LOAD R2,x SUB R2,#1 STR R2,x dec 42 Indeterminismo • Otra más… INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO LOAD R,x ADD R,#1 STR R,x inc LOAD R2,x SUB R2,#1 STR R2,x dec inc dec x R R2 1 LOAD R2,x 0 0 2 SUB R2,#1 0 -1 3 STR R2,x -1 -1 4 LOAD R,x -1 -1 -1 5 ADD R,#1 -1 0 -1 6 STR R,x 0 0 -1 Resultado Final: 0 Como se deben considerar todas las posibles intercalaciones, también hay que considerar que un proceso se ejecute completamente antes que el otro 43 Indeterminismo • Se han encontrado tres intercalaciones posibles de las instrucciones atómicas de un programa concurrente en las que se obtiene un resultado diferente • Hay 20 posibles intercalaciones En sólo 2 de ellas se obtiene un 0 En 9 intercalaciones se obtiene un 1 En 9 intercalaciones se obtiene un -1 INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO 44 Indeterminismo INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMOpublic class IncDecFor { static volatile double x; public static void inc(){ for (int i = 0; i < 10000000; i++){ x = x + 1; } } public static void dec(){ for (int i = 0; i < 10000000; i++) { x = x - 1; } } public static void main(String[] args){ x = 0; createThread("inc"); createThread("dec"); startThreadsAndWait(); println("x:"+x); } } • Los compiladores y máquinas virtuales pueden hacer ciertas optimizaciones al intercalar las instrucciones de los procesos • Para observar los efectos de la intercalación de instrucciones en el ejemplo anterior se pueden ejecutar múltiples incrementos y decrementos en cada proceso Download the code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/IncDecFor.java 45 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 46 Variables Compartidas •Modelo de memoria compartida Los procesos pueden acceder a una memoria común • Existen variables compartidas que varios procesos pueden leer y escribir • Un valor escrito por un proceso puede ser leído por otro proceso MEMORIA COMPARTIDA ¿Qué ocurre si dos procesos leen o escriben de forma simultánea en la misma variable? 47 • La abstracción de la programación concurrente nos indica que todas las instrucciones atómicas de cada proceso se intercalan en una única secuencia • Con SimpleConcurrent, la lectura y la escritura de atributos compartidos de tipo simple (boolean, int, char…) son instrucciones atómicas en Java Variables Compartidas MEMORIA COMPARTIDA 48 • Lectura simultáneas Ambos procesos leen el valor de la variable No interfieren entre sí • Escrituras simultáneas El resultado de la escritura simultánea sobre una variable compartida será el valor escrito por uno u otro proceso Nunca una mezcla de las dos escrituras Variables Compartidas MEMORIA COMPARTIDA 49 • Lectura y Escritura simultáneas Si un proceso lee sobre una variable compartida y simultáneamente otro escribe sobre ella, el resultado de la lectura será: O el valor de la variable antes de la escritura O el valor de la variable después de la escritura Nunca un valor intermedio Variables Compartidas MEMORIA COMPARTIDA 50 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 51 Memoria Compartida • Sincronización con Espera Activa Relaciones entre procesos Sincronización Condicional Exclusión Mutua PROGRAMACIÓN CONCURRENTE 52 • Relaciones entre procesos concurrentes Independientes Son gestionados por el Sistema No nos tenemos que preocupar como programadores. Interactúan entre sí Compiten por los recursos o se comunican entre sí para obtener un resultado conjunto Relaciones entre procesos SINCRONIZACIÓN CON ESPERA ACTIVA 53 • Interacción Competencia: Varios procesos deben compartir recursos comunes del sistema (procesador, memoria, disco, impresoras,…) por lo que compiten entre ellos para conseguirlo Cooperación: Varios procesos deben trabajar sobre distintas partes de un problema para resolverlo conjuntamente Relaciones entre procesos SINCRONIZACIÓN CON ESPERA ACTIVA 54 • Dos procesos que interactúan entre sí lo hacen a través de las siguientes actividades Comunicación: Es el intercambio de información entre procesos. Dos procesos cooperan entre sí intercambian información de algún modo Sincronización: La sincronización impone restricciones a la ejecución de las sentencias de los procesos (espera) Sincronización condicional Exclusión mutua Relaciones entre procesos SINCRONIZACIÓN CON ESPERA ACTIVA 55 • Sincronización Condicional Uno o más procesos no pueden continuar su ejecución (tienen que esperarse) a que se cumpla cierta condición Otro proceso es el que establece esa condición Un proceso servidor web está esperando a que otro proceso navegador web realice una petición mediante http Relaciones entre procesos SINCRONIZACIÓN CON ESPERA ACTIVA 56 • Exclusión Mutua Varios procesos compiten por un recurso común de acceso exclusivo Sólo uno de los procesos puede estar accediendo al recurso a la vez Si varios procesos quieren usar el recurso, tienen que esperarse a que quede libre Los procesos se excluyen mutuamente Sólo un proceso puede acceder a la vez a un recurso como un lector de huellas digitales Relaciones entre procesos SINCRONIZACIÓN CON ESPERA ACTIVA 57 Relaciones entre procesos Competencia Cooperación Actividades entre procesos Sincronización Comunicación Sincronización Condicional Exclusión Mutua Se lleva a cabo mediante A veces necesita Tipos Relaciones entre procesos SINCRONIZACIÓN CON ESPERA ACTIVA 58 • Comunicación en el modelo de memoria compartida Se utilizan las variables o atributos compartidos para compartir información Un proceso escribe un valor en la variable y otro proceso lee ese valor Relaciones entre procesos SINCRONIZACIÓN CON ESPERA ACTIVA 59 • Sincronización en el modelo de memoria compartida Espera activa: Utiliza únicamente variables compartidas para sincronizarse Herramientas de sincronización: Cuando se usan, además de variables compartidas, herramientas de sincronización entre procesos (Semáforos, monitores, etc…) Relaciones entre procesos SINCRONIZACIÓN CON ESPERA ACTIVA 60 Memoria Compartida • Sincronización con Espera Activa Relaciones entre procesos Sincronización Condicional Exclusión Mutua PROGRAMACIÓN CONCURRENTE 61 Sincronización Condicional • La Sincronización Condicional se produce cuando un proceso debe esperar a que se cumpla una cierta condición para proseguir su ejecución • Esta condición sólo puede ser activada por otro proceso SINCRONIZACIÓN CON ESPERA ACTIVA 61 PA1 PA2 PB1 PB2 Diagrama de Precedencia Proceso A Proceso B 62 Sincronización Condicional • Si los procesos muestran por pantalla* el nombre de la acción que han realizado, el resultado de ejecutar el programa concurrente del diagrama podría ser cualquiera de los siguientes: SINCRONIZACIÓN CON ESPERA ACTIVA PA1 PA2 PB1 PB2 Proceso A Proceso B PA1 PA2 PB1 PB2 PA1 PB1 PA2 PB2 PB1 PA1 PA2 PB2 Salidas Posibles: Salidas No Posibles: PB1 PB2 PA1 PA2 * La escritura en pantalla con println es una instrucción atómica en SimpleConcurrent 63 Sincronización Condicional BLP (06-07) 63 public class SincCond { static volatile boolean continuar; public static void a() { print("PA1 "); continuar = true; print("PA2 "); } public static void b() { print("PB1 "); while (!continuar); print("PB2 "); } public static void main(String[] args){ continuar = false; createThread("a"); createThread("b"); startThreadsAndWait(); } } Se declara un atributo compartido continuar El proceso a la pondrá a true cuando haya ejecutado PA1 y continuará ejecutando PA2 El proceso b ejecutará PB1 y comprobará repetidamente en un bucle el valor de continuar Saldrá del bucle cuando valga true y continuará ejecutando PB2 Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/SincCond.java 64 Sincronización Condicional a b continuar 1 print("PA1 "); false 2 print("PB1 "); false 3 continuar = true; true 4 print("PA2 "); true 5 while (!continuar); true 6 print("PB2 "); true SINCRONIZACIÓN CON ESPERA ACTIVA PA1 PB1 PA2 PB2Salida: Posible intercalación de instrucciones 65 Sincronización Condicional a b continuar 1 print("PB1 "); false 2 while (!continuar); false 3 while (!continuar); false 4 while (!continuar); false 5 print("PA1 "); false 6 continuar = true; true 7 print("PA2 "); true 8 while (!continuar); true 9 print("PB2 "); true SINCRONIZACIÓN CON ESPERA ACTIVA PB1 PA1 PA2 PB2Salida: Posible intercalación de instrucciones 66 Sincronización Condicional a b continuar 1 print("PA1 "); false 2 continuar = true; true 3 print("PA2 "); true 4 print("PB1 "); true 5 while (!continuar); true 6 print("PB2 "); true SINCRONIZACIÓN CON ESPERA ACTIVA PA1 PA2 PB1 PB2Salida: Posible intercalación de instrucciones 67 Ejercicio 1 – Productor Consumidor • Se desea implementar un programa concurrente con un proceso que produce información (productor) y otro proceso que hace uso de esa información (consumidor) • El proceso productor genera un número aleatorio y termina • El proceso consumidor muestra por pantalla el número generado y termina SINCRONIZACIÓN CONDICIONAL 68 Ejercicio 2 – Productor Consumidor N • Se desea ampliar el programa anterior de forma que el proceso productor genere 5 números (consecutivos) • El proceso consumidor consumirá esos 5 números • No se puede quedar ningún producto sin consumir • No se puede consumir dos veces el mismo producto SINCRONIZACIÓN CONDICIONAL 69 Ejercicio 3 – Cliente Servidor 1 Petición • Programa formado por un proceso servidor y otro proceso cliente • El proceso cliente hace una petición al proceso servidor (con un número entero) y espera su respuesta. Cuando la recibe, la procesa. • El proceso servidor no hace nada hasta que recibe una petición, momento en el que suma 1 y devuelve el valor. • El proceso cliente muestra un número por pantalla, se lo envía al servidor y cuando le llega la respuesta, la muestra por pantalla SINCRONIZACIÓN CONDICIONAL 70 Ejercicio 4 – Cliente Servidor N Peticiones • Se desea ampliar el programa anterior de forma que el proceso cliente esté constantemente haciendo peticiones y el proceso servidor atendiendo a las mismas SINCRONIZACIÓN CONDICIONAL 71 Ejercicio 4B – N Clientes Servidor N Peticiones • Se desea ampliar el programa anterior de forma que existan varios clientes que acceden al mismo servidor • Para facilitar la depuración, cada proceso cliente debe empezar los números consecutivos en un valor diferente (de 10 en 10) SINCRONIZACIÓN CONDICIONAL 72 Memoria Compartida • Sincronización con Espera Activa Relaciones entre procesos Sincronización Condicional Exclusión Mutua PROGRAMACIÓN CONCURRENTE 73 Exclusión Mutua • ¿Qué es la Exclusión Mutua? Es un tipo de sincronización entre procesos que se tiene cuando varios procesos compiten por un recurso común de acceso exclusivo Sólo uno de los procesos puede estar accediendo al recurso a la vez y los demás tienen que esperar Cuando un proceso libera el recurso, otro proceso que estuviera esperando podrá acceder a él SINCRONIZACIÓN CON ESPERA ACTIVA 74 Exclusión Mutua • ¿Qué es el Problema de la Exclusión Mutua? Es un problema histórico, definido por los primeros investigadores en Programación Concurrente El problema se utilizó para investigar la mejor forma de implementar la sincronización de tipo exclusión mutua usando únicamente variables compartidas (espera activa) SINCRONIZACIÓN CON ESPERA ACTIVA 75 Exclusión Mutua • El problema de la Exclusión Mutua Se tienen dos o más procesos concurrentes, que ejecutan indefinidamente una secuencia de instrucciones dividida en dos secciones Sección bajo exclusión mutua Sección sin exclusión mutua SINCRONIZACIÓN CON ESPERA ACTIVA public static void p1() { while(true) { // Sección bajo EM printlnI("P1_EM1"); printlnI("P1_EM2"); // Sección sin EM printlnI("P1_1"); printlnI("P1_2"); } } El método printlnI imprime en una columna diferente la información de cada proceso. Esto permite diferenciar de forma muy sencilla qué instrucciones ejecuta cada proceso. Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutua.java 76 Exclusión Mutua • Sección bajo Exclusión Mutua (Sección crítica) Secuencia de instrucciones que acceden a un recurso compartido de acceso exclusivo Puesto que el recurso es de acceso exclusivo y solo un proceso puede acceder a él al mismo tiempo, cada proceso debe ejecutar las instrucciones de la sección bajo EM sin que haya intercalación de instrucciones de la sección bajo EM de otros procesos Se pueden intercalar instrucciones que no hagan uso del recurso, es decir, instrucciones de la sección sin EM de otros procesos SINCRONIZACIÓN CON ESPERA ACTIVA 77 Exclusión Mutua • Sección sin Exclusión Mutua Secuencia de instrucciones que pueden ser ejecutadas concurrentemente por todos los procesos Sus instrucciones se pueden intercalar con las instrucciones de la sección bajo EM de otros procesos SINCRONIZACIÓN CON ESPERA ACTIVA 78 Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA 78 P1_EM1 P1_EM2 P1_1 P2_EM1 P1_2 P2_EM2 P2_1 P1_EM1 P2_2 P1_EM2 P1_1 P2_EM1 P2_EM2 P2_1 P1_2 P1_EM1 ... Ejecución de ejemplo con dos procesos ejecutando instrucciones de la sección bajo EM y a continuación instrucciones de la sección sin EM No se intercalan sentencias de las secciones bajo EM de los dos procesos Sí se intercalan instrucciones de las secciones sin EM 79 Exclusión Mutua • Implementación de la Exclusión Mutua con espera activa No es fácil de implementar Veremos dos intentos de implementación pero que no funcionan correctamente El estudio de estos intentos nos permitirá entender los errores habituales de los programas concurrentes (y más adelante veremos cómo evitarlos) SINCRONIZACIÓN CON ESPERA ACTIVA 80 Primer intento • Se puede usar una variable booleana que indique si hay algún proceso ejecutando las secciones que deben estar bajo exclusión mutua (para que los demás se esperen) • Un proceso, antes de ejecutar las instrucciones bajo exclusión mutua, comprueba si ya hay otro proceso mirando esa variable • Si hay otro proceso, espera hasta que termine • Si no hay ningún proceso en, pone a true la variable y empieza a ejecutar las instrucciones • Cuando termina, vuelve a poner a false la variable SINCRONIZACIÓN CON ESPERA ACTIVA 8181 static volatile boolean ocupado; for (int i = 0; i < 5; i++) { while (ocupado); ocupado = true; // Sección bajo EM printlnI("P1_EM1"); printlnI("P1_EM2"); ocupado = false; // Sección sin EM printlnI("P1_1"); printlnI("P1_2"); } for (int i = 0; i < 5; i++) { while (ocupado); ocupado = true; // Sección bajo EM printlnI("P2_EM1"); printlnI("P2_EM2"); ocupado = false; // Sección sin EM printlnI("P2_1"); printlnI("P2_2"); } Primer intento EXCLUSIÓN MUTUA Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaIntento1.java 82 p1 p2 ocupado 1 while (ocupado); false 2 ocupado = true; true 3 while (ocupado); true 4 printlnI("P1_EM1"); true 5 while (ocupado); true 6 printlnI("P1_EM2"); true 7 while (ocupado); true 8 ocupado = false; false 9 while (ocupado); false 10 printlnI("P1_1"); false 11 ocupado = true; true 12 printlnI("P2_EM1"); true 13 printlnI("P1_2"); true 14 while (ocupado); true 15 printlnI("P2_EM2"); true 16 while (ocupado); true 17 ocupado = false; false 18 while (ocupado); false 19 … 83 • La intercalación que hemos visto se comporta correctamente • El problema es que existen intercalaciones que no funcionan correctamente • Los dos procesos pueden ejecutar las instrucciones que deberían estar bajo exclusión muta de forma intercalada Primer intento EXCLUSIÓN MUTUA 84 p1 p2 ocupado 1 while (ocupado); false 2 while (ocupado); false 3 ocupado = true; true 4 ocupado = true; true 5 printlnI("P1_EM1"); true 6 printlnI("P2_EM1"); true 7 printlnI("P1_EM2"); true 8 printlnI("P2_EM2"); true 9 … Primer intento EXCLUSIÓN MUTUA • Intercalación problemática Se permite que dos procesos ejecuten las sentencias de la EM de forma intercalada 85 • El problema del intento anterior es que los dos procesos miran y después entran, pudiendo entrar los dos a la vez • En el segundo intento antes de comprobar si hay alguien dentro, el proceso pide ejecutar instrucciones bajo exclusión mutua • Si alguien lo ha pedido ya, el proceso se espera Segundo intento EXCLUSIÓN MUTUA 8686 static volatile boolean p1p; static volatile boolean p2p; for (int i = 0; i < 5; i++) { p1p = true; while (p2p); // Sección bajo EM printlnI("P1_EM1"); printlnI("P1_EM2"); p1p = false; // Sección sin EM printlnI("P1_1"); printlnI("P1_2"); } for (int i = 0; i < 5; i++) { p2p = true; while (p1p); // Sección bajo EM printlnI("P2_EM1"); printlnI("P2_EM2"); p2p = false; // Sección sin EM printlnI("P2_1"); printlnI("P2_2"); } Segundo intento EXCLUSIÓN MUTUA Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaIntento2.java 87 p1 p2 p1p p2p 1 p1p = true; true false 2 while (p2p); true false 3 p2p = true; true true 4 while (p1p); true true 5 printlnI("P1_EM1"); true true 6 while (p1p); true true 7 printlnI("P1_EM2"); true true 8 p1p = false; false true 9 while (p1p); false true 10 printlnI("P1_1"); false true 11 printlnI("P1_2"); false true 12 printlnI("P2_EM1"); false true 13 p1p = true; true true 14 while (p2p); true true 15 printlnI("P2_EM2"); true true 16 p2p = false; true false 17 while (p2p); true false 18 printlnI("P1_EM1 "); true false 19 … 88 Segundo intento • Intercalación problemática Si los dos procesos anotan su petición a la vez, se quedan esperando para siempre (interbloqueo) EXCLUSIÓN MUTUA 88 p1 p2 c.p1p c.p2p 1 p1p = true; true false 2 p2p = true; true true 3 while (p2p); true true 4 while (p1p); true true 5 while (p1p); true true 6 while (p1p); true true 7 while (p2p); true true 89 Exclusión Mutua • Los problemas que hemos visto son típicos al empezar a programar de forma concurrente • Hay que tener muy presentes todas las posibles intercalaciones de las instrucciones • Por muy baja sea la probabilidad de que algo malo ocurra, puede ocurrir en cualquier momento • Ejecutar el programa varias veces sin problemas no es garantía de que no tenga problemas SINCRONIZACIÓN CON ESPERA ACTIVA 90 Exclusión Mutua • Existen varios algoritmos para implementar sincronización de Exclusión Mutua con espera activa El algoritmo de Dekker: se utiliza para dos procesos El algoritmo de Lamport: es un algoritmo genérico diseñado para N procesos • Estos algoritmos son bastante complejos y no los vamos a estudiar • Información sobre ellos: SINCRONIZACIÓN CON ESPERA ACTIVA Ben-Ari, M. Principles of Concurrent and Distributed Programming. 2nd Ed. Prentice Hall, 2.006. 91 • SimpleConcurrent implementa la sincronización de Exclusión Mutua: Para entrar en la sección bajo EM: Para salir de la sección bajo EM: enterMutex(); exitMutex(); Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA 92 public static void p1() { for (int i = 0; i < 5; i++) { // Sección bajo EM enterMutex(); printlnI("P1_EM1"); printlnI("P1_EM2"); exitMutex(); // Sección sin EM printlnI("P1_1"); printlnI("P1_2"); } } public static void p2() { for (int i = 0; i < 5; i++) { // Sección bajo EM enterMutex(); printlnI("P2_EM1"); printlnI("P2_EM2"); exitMutex(); // Sección sin EM printlnI("P2_1"); printlnI("P2_2"); } } Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaEA.java 93 • Programa que muestra El problema de la Exclusión Mutua de forma textual al ejecutarse Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA public class ExcMutuaEA2 { public static void p() { for (int i=0; i<5; i++) { enterMutex(); printlnI("********"); printlnI("********"); printlnI("********"); exitMutex(); printlnI("--------"); printlnI("--------"); printlnI("--------"); printlnI("--------"); printlnI("--------"); } } public static void main(String[] args){ createThreads(4,"p"); startThreadsAndWait(); } } Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaEA2.java 94 Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA . ******** ******** ******** ******** -------- -------- ******** -------- ******** -------- -------- ******** -------- -------- ******** -------- ******** -------- ******** -------- -------- ******** -------- ******** -------- ******** -------- -------- -------- ******** -------- ******** -------- -------- -------- ******** • Salida por pantalla de una de las posibles ejecuciones del programa • Se puede observar como los asteriscos nunca se intercalan porque corresponden a la sección crítica (están bajo exclusión mutua) 95 • Normalmente la sección bajo exclusión mutua se usa para acceder de forma exclusiva a una variable compartida • De esa forma no aparecen interferencias entre varios hilos accediendo a la misma vez • Una misma variable compartida se puede usar en varios partes del programa Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA 96 • Variable x bajo exclusión mutua con dos secciones diferentes (incremento y decremento) • El resultado de este programa siempre será correcto (cero) • No se pueden producir errores al contar porque no se pueden intercalar de forma anómala instrucciones que usan la variable x public class IncDecEM { static volatile double x; public static void inc() { for (int i = 0; i < 10000000; i++){ enterMutex(); x = x + 1; exitMutex(); } } public static void dec() { for (int i = 0; i < 10000000; i++){ enterMutex(); x = x - 1; exitMutex(); } } } Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/IncDecEM.java 97 • Varios recursos diferentes pueden estar bajo exclusión mutua en un mismo programa • Las secciones críticas de cada recurso son completamente independientes entre sí. Incluso pueden intercalarse sus instrucciones • Las secciones críticas de diferentes recursos pueden anidarse si es necesario • Se debe indicar el nombre del recurso como un parámetro de enterMutex(…) y exitMutex(…) Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA 98 • Las variables x e y están bajo exclusión mutua con dos secciones independientes • Se pueden intercalar instrucciones que usan variables diferentes • No se pueden intercalar instrucciones que usen la misma variable • El resultado final en cada variable es el correcto (cero) Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVApublic class IncDecEM2 { static volatile double x, y; public static void incX() { enterMutex("x"); x = x + 1; exitMutex("x"); } public static void decX() { enterMutex("x"); x = x - 1; exitMutex("x"); } public static void incY() { enterMutex("y"); y = y + 1; exitMutex("y"); } public static void decY() { enterMutex("y"); y = y - 1; exitMutex("y"); } } Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/IncDecEM2.java 99 Ejercicio 5 - Museo • Existen 3 personas en el mundo, 1 museo, y sólo cabe una persona dentro del museo • Las personas realizan cuatro acciones dentro del museo Cuando entran al museo saludan: “hola!” Cuando ven el museo se sorprenden: “qué bonito!” y “alucinante!” Cuando se van del museo se despiden: “adiós” • Cuando salen del museo se van a dar un “paseo” • Después del paseo, les ha gustado tanto que vuelven a entrar EXCLUSIÓN MUTUA 100 Ejercicio 5 - Museo • Se pide: Tipos de procesos Número de procesos del programa concurrente y de qué tipo son Escribir lo que hace cada proceso Identificar los recursos compartidos Identificar la sección o secciones críticas Escribir el programa completo EXCLUSIÓN MUTUA 101 Ejercicio 6 – Museo con infinitas personas • Considerar que caben infinitas personas dentro del museo • Cada persona al entrar tiene que saludar diciendo cuántas personas hay en el museo: “hola, somos 3” • Al despedirse tiene que decir el número de personas que quedan tras irse: “adiós a los 2” EXCLUSIÓN MUTUA 102 Ejercicio 7 – Museo con regalo • Para incentivar las visitas, cuando una persona entre en el museo estando vacío, será obsequiado con un regalo • Las personas, después de saludar, dicen si les han dado un regalo (“Tengo regalo”) o si no (“No tengo regalo”) • Las personas deben permitir que otras personas hablen entre el saludo y el comentario sobre el regalo EXCLUSIÓN MUTUA 103 • Una instrucción atómica es aquella que se ejecuta como una unidad indivisible • El lenguaje de programación y el hardware definen las instrucciones atómicas en las que se divide cada sentencia x = x+1 LOAD R,x ADD R,#1 STR R,x Instrucciones Atómicas Sentencia alto nivel Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA 104 • Supongamos que dos procesos quieren usar una variable común para contar las acciones realizadas • Si dos procesos quieren incrementar la misma variable existen intercalaciones de las instrucciones atómicas que producen errores en la cuenta • Para el desarrollador sería muy interesante que el incremento de una variable fuese una instrucción atómica Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA 105 •Tipos de instrucciones atómicas De grano fino Ofrecidas por el lenguaje de programación y el hardware al desarrollador Habitualmente se corresponden con las instrucciones máquina del procesador De grano grueso Conjunto de sentencias que ejecuta un proceso sin interferencias de otros procesos Los lenguajes de programación y el sistema hardware disponen de mecanismos para hacer que un grupo de sentencias se ejecuten como una instrucción atómica de grano grueso Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA 106 • Una sección bajo Exclusión Mutua es una instrucción atómica de grano grueso Indivisible Ningún otro proceso puede interferir en el uso del recurso compartido Si dentro de la sección crítica se incrementa una variable, ningún otro proceso podrá interferir y no se producirán errores en la cuenta Divisible Pero es divisible en el sentido de que se pueden intercalar instrucciones de la sección no crítica de otros procesos Exclusión Mutua SINCRONIZACIÓN CON ESPERA ACTIVA 107 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de Corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 108 Propiedades de Corrección • La programación concurrente es tan compleja que se han definido un conjunto de propiedades de corrección que todo programa concurrente debe cumplir para considerarse correcto • Estas propiedades deben cumplirse en cualquier posible intercalación de las instrucciones atómicas MEMORIA COMPARTIDA 109 Propiedades de Corrección • Exclusión Mutua Hay que poner correctamente bajo exclusión mutua los recursos compartidos De no ser así, se pueden producir interferencias indeseadas entre los procesos que tengan como consecuencia la corrupción de los datos compartidos Cuando esto ocurre se dice que se ha producido una “condición de carrera” (race condition) MEMORIA COMPARTIDA 110 Propiedades de Corrección • Ausencia de Interbloqueos Dos procesos se bloquean mutuamente cuando se están esperando el uno al otro y no pueden continuar su ejecución mientras esperan Interbloqueo activo (livelock): Cuando los procesos ejecutan instrucciones que no producen un avance real del programa, sólo sirven para sincronizarse entre sí Interbloqueo pasivo (deadlock) Cuando todos los procesos están bloqueados esperando indefinidamente Este interbloqueo sólo se produce con herramientas de sincronización (se verán más adelante) MEMORIA COMPARTIDA 111 Propiedades de Corrección • Ausencia de Retrasos Innecesarios Los procesos deben progresar en su ejecución si no hay otro proceso compitiendo con él para entrar en las secciones bajo exclusión mutua Generalmente las primitivas concurrentes garantizan que esto no ocurra. El desarrollador no tiene que preocuparse de ello. MEMORIA COMPARTIDA 112 Propiedades de Corrección • Ausencia de Inanición (starvation) Todo proceso que quiera acceder a un recurso compartido deberá poder hacerlo (en algún momento) Generalmente las primitivas concurrentes garantizan que esto no ocurra. El desarrollador no tiene que preocuparse de ello. MEMORIA COMPARTIDA 113 Propiedades de Corrección • Tipos de propiedades de corrección: De seguridad (safety): Si alguna de estas propiedades se incumple en alguna ocasión, el programa se comportará de forma errónea De Vida (liveness): Si alguna de estas propiedades se incumple en “alguna” ocasión, el programa se comportara de forma correcta pero será más lento de lo que podría ser y desaprovechará los recursos MEMORIA COMPARTIDA 114 Propiedades de Corrección • Propiedades de seguridad (safety) Exclusión Mutua Ausencia de Interbloqueo pasivo • Propiedades de Vida (liveness) Ausencia de Retrasos innecesarios Ausencia de inanición (starvation) Ausencia de interbloqueo activo (livelock) MEMORIA COMPARTIDA 115 Propiedades de Corrección • Justicia en el acceso a recursos compartidos (fariness): Espera FIFO: Si un proceso quiere acceder a un recurso, lo hará antes de que lo haga otro proceso que lo solicite después que él (cola del pan) Espera lineal: Si un proceso quiere acceder a un recurso, lo hará antes de que otro proceso lo haga más de una vez (se cuela una vez, pero no dos) Aleatorio: No se considera justa, pero es la más eficiente (se usa cuando no se necesita justicia) MEMORIA COMPARTIDA 116 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 117 Memoria Compartida • Espera Activa vs Herramientas de sincronización Problemas de la Espera Activa Herramientas de sincronización Implementación de la Multiprogramación Algoritmos no bloqueantes PROGRAMACIÓN CONCURRENTE 118 Problemas de la Espera Activa • Se han estudiado técnicas básicas de sincronización de procesos en el modelo de Memoria Compartida con hilos y cerrojos usando únicamente variables compartidas para la sincronización • Se denomina Espera Activa porque los procesos están ejecutando instrucciones (están activos) incluso cuando tienen que esperar para poder continuar su ejecución • También se la conoce como Busy waiting o spinning o polling (aunque este último término es más usado en entrada/salida) ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN while (!continuar); 119 Problemas de la Espera Activa • La Espera Activa presenta los siguientes problemas Multiprogramación Los procesos que están esperando están malgastando el procesador que podría usarse por otros procesos que realmente estén realizando un trabajo útil Multiproceso Un procesador ejecutando instrucciones consume energía y por tanto disipa calor Si las instrucciones no son útiles, el procesador podría estar en reposo ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN La Espera Activa es muy ineficiente y NO se debe usar en programas concurrentes 120 Problemas de la Espera Activa • Para solucionar los problemas de la espera activa, se desarrollaron herramientas de sincronización de procesos en los procesadores, lenguajes de programación y librerías • Con estas herramientas, cuando un proceso no puede continuar ejecutando las sentencias se bloquea y deja de ejecutar sentencias hasta que otro proceso lo desbloquea cuando se cumplen las condiciones para que siga ejecutando • Esto permite aprovechar de forma mucho mas adecuada los recursos (capacidad de cómputo, energía, …) ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 121 Memoria Compartida • Espera Activa vs Herramientas de sincronización Problemas de la Espera Activa Herramientas de sincronización Implementación de la Multiprogramación Algoritmos no bloqueantes PROGRAMACIÓN CONCURRENTE 122 Herramientas de sincronización • Puesto que la espera activa en general es ineficiente, se han diseñado herramientas que permiten sincronizar procesos de forma más eficiente • Existen algunas herramientas de bajo nivel de abstracción y otras de mayor nivel de abstracción dependiendo de las necesidades del desarrollador • En general lo mejor es usar las herramientas de mayor nivel que permiten implementar los requisitos ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 123 Herramientas de sincronización • En la programación funcional y en la programación orientada a objetos, la gran mayoría de los lenguajes de programación implementan los mismos conceptos Funcional: Funciones, listas, patrones… Orientación a Objetos: Clases, objetos, métodos, atributos… • En la programación concurrente, incluso en lenguajes que implementan el mismo modelo, pueden existir diferencias sustanciales entre las herramientas que ofrecen • Algunas herramientas básicas suelen estar disponibles en todos los lenguajes ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 124 Herramientas de sincronización • Herramientas de sincronización de procesos en el modelo de Memoria Compartida Semáforos Monitores Regiones Críticas Regiones Críticas Condicionales Sucesos Buzones Recursos ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 125 Memoria Compartida • Espera Activa vs Herramientas de sincronización Problemas de la Espera Activa Herramientas de sincronización Implementación de la Multiprogramación Algoritmos no bloqueantes PROGRAMACIÓN CONCURRENTE 126 Implementación de la Multiprogramación • Cuando se ejecuta un programa concurrente en multiprogramación, sólo un proceso puede estar ejecutándose en el procesador a la vez • Para coordinar a todos los procesos que comparten el procesador Planificación (Scheduling) Despacho (Dispatching) ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 127 Implementación de la Multiprogramación • Planificación (Scheduling) Política que determina la asignación, en cada instante, de los procesos a los procesadores disponibles FIFO: El primero que llegó Lineal: No puede volver el mismo proceso hasta que no hayan ejecutado los demás Prioridades: El más prioritario Aleatoria ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 128 • Despacho (Dispatching) Configuración del procesador para que ejecute el proceso que ha determinado la planificación El proceso debe ejecutarse con las mismas condiciones en las que abandonó el procesador Para ello el sistema dispone de un Descriptor de Proceso (Process Control Block) por cada proceso que alberga toda la información necesaria Identificador del proceso (Process ID, PID) Estado Entorno, Memoria, … Implementación de la Multiprogramación ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 129 • Para implementar las Herramientas de sincronización, un proceso en un sistema en multiprogramación puede estar en los siguientes estados Bloqueado Preparado Ejecución Implementación de la Multiprogramación ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 130 • Un proceso estará en el estado bloqueado debido a diversos motivos: Está esperando por una operación de entrada/salida y no puede continuar su ejecución hasta que finalice la operación Ha ejecutado una instrucción de tipo sleep() Usa una herramienta de sincronización (por ejemplo un semáforo) y está esperando a que se cumpla cierta condición para volver a estar preparado Bloqueado Preparado Ejecución Implementación de la Multiprogramación ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 131 Memoria Compartida • Espera Activa vs Herramientas de sincronización Problemas de la Espera Activa Herramientas de sincronización Implementación de la Multiprogramación Algoritmos no bloqueantes PROGRAMACIÓN CONCURRENTE 132 Algoritmos no bloqueantes • Cada vez que un proceso A intenta acceder a un recurso bajo exclusión mutua y ya hay otro proceso usando el recurso, el proceso A se tiene que bloquear • Cuando hay muchos procesos intentando acceder al mismo recurso, se dice que tiene mucha competencia (contended) • Cuando eso ocurre, ese recurso compartido se convierte en un cuello de botella e impide que el programa pueda aprovechar todos los recursos (escalar) ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 133 Algoritmos no bloqueantes • Cuando muchos procesos intentan acceder a recursos compartidos bajo exclusión mutua se pueden producir demasiados bloqueos y desbloqueos de procesos y esto también puede ser demasiado ineficiente • Para evitarlo se reduce al máximo el tamaño de la sección bajo exclusión mutua, para reducir la probabilidad de que la sección bajo EM esté ocupada ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 134 Algoritmos no bloqueantes • Algoritmos no bloqueantes Otra técnica para evitar los bloqueos consiste en usar algoritmos que no ponen los recursos bajo EM Para evitar condiciones de carrera al acceder a los recursos compartidos, usan instrucciones atómicas hardware de lectura y escritura de variables (Comparse and Set) en bucles de espera activa A estos algoritmos se les llama: non-blocking algorithms y lock-free algorithms ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 135 Algoritmos no bloqueantes • Estos algoritmos se consideran avanzados y no se van a estudiar en clase •Muchas herramientas concurrentes de Java están implementadas internamente usando esta técnica avanzada ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN 136 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 137 Introducción a los semáforos • Dijkstra introdujo la primera herramienta de sincronización de procesos en 1968 y la denominó Semáforo • Es una herramienta de sincronización de procesos de bajo nivel que permite implementar de forma sencilla la sincronización condicional y la exclusión mutua MEMORIA COMPARTIDA Dijkstra (1930-2002) 138 Introducción a los semáforos • Un semáforo es una clase • El estado interno está formado por un contador de permisos y un conjunto de procesos bloqueados • Las operaciones permiten bloquear y desbloquear procesos dependiendo del número de permisos MEMORIA COMPARTIDA 139 Introducción a los semáforos • En la librería SimpleConcurrent los semáforos se representan como objetos de la clase SimpleSemaphore • Métodos públicos public SimpleSemaphore(int permits) Constructor que inicializa el número de permisos iniciales del semáforo con el valor indicado void acquire() y void release() Métodos invocados por los procesos para sincronizarse entre sí Se ejecutan de forma atómica Su comportamiento depende del número de permisos MEMORIA COMPARTIDA 140 Introducción a los semáforos • void acquire() El proceso que invoca este método intenta “adquirir” un permiso del semáforo. Si lo consigue, continúa la ejecución. Si no, queda bloqueado Internamente el semáforo tiene un contador de permisos (permits). Si el número de permisos del semáforo es mayor que cero (permits>0), se decrementa una unidad el número de permisos y el proceso continúa su ejecución Si el número de permisos del semáforo es cero (permits=0), el proceso suspende su ejecución, pasa al estado bloqueado y se añade al conjunto de procesos bloqueados en el semáforo No es posible que un semáforo tenga un número negativo de permisos MEMORIA COMPARTIDA permits = permits - 1 141 Introducción a los semáforos • void release(); El proceso que invoca este método “libera” un permiso previamente adquirido. Si otro proceso estaba esperando un permiso, lo consigue en la misma operación y se desbloquea. El hilo que ejecuta este método nunca queda bloqueado, siempre continúa la ejecución Si no existen procesos bloqueados en el semáforo, se incrementa el número de permisos. Si existen procesos bloqueados en el semáforo, se desbloquea aleatoriamente a uno cualquiera. MEMORIA COMPARTIDA permits = permits + 1 142 Introducción a los semáforos • Se puede pensar en un semáforo como una caja llena de bolas El número de permisos representa el número de bolas acquire() El proceso que ejecuta acquire() tiene que coger una bola de la caja Si hay una o más bolas, coge una y continúa Si no hay bolas, se bloquea hasta que estén disponibles release() Echa una nueva bola a la caja Si algún proceso estaba esperando bola, la coge y se desbloquea Si no había ningún proceso esperando, la bola se queda en la caja MEMORIA COMPARTIDA 143 Introducción a los semáforos MEMORIA COMPARTIDA Operación que ejecuta un proceso Antes Después Permisos Procesos Bloqueados Permisos Procesos Bloqueados acquire() 3 Ninguno 2 Ninguno acquire() 0 P1 0 P1,P2 release() 1 Ninguno 2 Ninguno release() 0 Ninguno 1 Ninguno release() 0 P1,P3 0 P1* * Podría haberse desbloqueado cualquiera de los procesos 144 Introducción a los semáforos • Diferentes nombres para las operaciones acquire y release Las operaciones de gestión del semáforo reciben diferentes nombres dependiendo del sistema operativo, lenguaje de programación y/o librería. MEMORIA COMPARTIDA acquire release Descripción P V Los nombres que Dijkstra propuso originalmente a las operaciones en idioma holandés. V proviene de verhogen (incrementar) y P proviene de portmanteau prolaag (intentar reducir) Down Up ALGOL 68, el kernel de Linux kernel y algunos libros de texto Wait Signal PascalFC y algunos libros de texto Pend Post Procure Vacate Procure significa obtener y vacate desocupar 145 Introducción a los semáforos • Tipos de Semáforos Según el número de permisos Semáforos Binarios: Como máximo sólo pueden gestionar un permiso Semáforos Generales: Pueden gestionar cualquier número de permisos Según la política de desbloqueo de procesos FIFO (First In, First Out): Los procesos se desbloquean en orden de llegada Aleatorio: Los procesos se desbloquean aleatoriamente MEMORIA COMPARTIDA Los semáforos SimpleSemaphore son generales aleatorios 146 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 147 Sincronización Condicional • Se produce cuando un proceso debe esperar a que se cumpla una cierta condición para proseguir su ejecución • Esta condición sólo puede ser activada por otro proceso SINCRONIZACIÓN CON SEMÁFOROS PA1 PA2 PB1 PB2 Diagrama de Precedencia Proceso A Proceso B 148 public class SincCond { static volatile boolean continuar; public static void a() { print("PA1 "); continuar = true; print("PA2 "); } public static void b() { print("PB1 "); while (!continuar); print("PB2 "); } public static void main(String[] args){ continuar = false; createThread("a"); createThread("b"); startThreadsAndWait(); } } Sincronización Condicional • Implementación con Espera Activa PA1 PA2 PB1 PB2 Proceso A Proceso B 149 Sincronización Condicional public class SincCondSem { static SimpleSemaphore continuar; public static void a() { print("PA1 "); continuar.release(); print("PA2 "); } public static void b() { print("PB1 "); continuar.acquire(); print("PB2 "); } public static void main(String[] args){ continuar = new SimpleSemaphore(0); createThread("a"); createThread("b"); startThreadsAndWait(); } } • Implementación con Semáforos PA1 PA2 PB1 PB2 Proceso A Proceso B Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/SincCondSem.java 150 150 Semáforos (Eficiente)Espera Activa (Ineficiente) public class SincCond { static volatile boolean continuar; public static void a() { print("PA1 "); continuar = true; print("PA2 "); } public static void b() { print("PB1 "); while (!continuar); print("PB2 "); } public static void main(String[] args){ continuar = false; createThread("a"); createThread("b"); startThreadsAndWait(); } } public class SincCondSem { static SimpleSemaphore continuar; public static void a() { print("PA1 "); continuar.release(); print("PA2 "); } public static void b() { print("PB1 "); continuar.acquire(); print("PB2 "); } public static void main(String[] args){ continuar = new SimpleSemaphore(0); createThread("a"); createThread("b"); startThreadsAndWait(); } } 151 Sincronización Condicional • Comportamiento de los procesos con herramientas de sincronización (bloqueantes) Un proceso se bloquea a sí mismo Un proceso se bloquea a sí mismo si no puede proseguir su ejecución Un proceso nunca bloquea a otro proceso Un proceso desbloquea a otro Un proceso nunca se desbloquea a sí mismo Un proceso desbloquea a otro proceso cuando ese otro proceso puede proseguir su ejecución SINCRONIZACIÓN CON SEMÁFOROS 152 Exclusión Mutua • El problema de la Exclusión Mutua Se tienen dos o más procesos concurrentes, que ejecutan indefinidamente una secuencia de instrucciones dividida en dos secciones: sección bajo Exclusión Mutua y la sección sin EM SINCRONIZACIÓN CON SEMÁFOROS while(true) { // Sección bajo EM printlnI("P1_EM1"); printlnI("P1_EM2"); // Sección sin EM printlnI("P1_1"); printlnI("P1_2"); } 153 Exclusión Mutua • La exclusión mutua con espera activa con SimpleConcurrent se implementa usando enterMutex() y exitMutex() • Implementada con semáforos es mucho más eficiente 153 public class ExcMutuaSem { public static void p() { for (int i = 0; i < 5; i++) { // Sección bajo EM enterMutex(); printlnI("P1_EM1"); printlnI("P1_EM2"); exitMutex(); // Sección sin EM printlnI("P1_1"); printlnI("P2_2"); } } public static void main(String[] args) { createThreads(2,"p"); startThreadsAndWait(); } } 154 Exclusión Mutua • Para entrar en la sección bajo EM hay que coger una bola de la caja, y dejarla al salir para que la pueda coger el próximo proceso que quiera entrar • Cuando el semáforo tiene 1 permiso (permits=1), la sección crítica está libre • Cuando el semáforo no tiene permisos (permits=0), la sección crítica está ocupada por un proceso 154 public class ExcMutuaSem { private static SimpleSemaphore em; public static void p() { for (int i = 0; i < 5; i++) { // Sección bajo EM em.acquire(); printlnI("P1_EM1"); printlnI("P1_EM2 "); em.release(); // Sección sin EM printlnI("P1_1"); printlnI("P1_2"); } } public static void main(String[] args) { em = new SimpleSemaphore(1); createThreads(2,"p"); startThreadsAndWait(); } } Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaSem.java 155 Ejercicio 13A • Una línea del metro está formada por varios tramos de vía que son recorridos secuencialmente en un único sentido por diferentes trenes • Cada tren está representado por un hilo que realiza las siguientes operaciones: SINCRONIZACIÓN CON SEMÁFOROS public static void tren(int numTren) { sleepRandom(500); recorrerTramoA(numTren); sleepRandom(500); recorrerTramoB(numTren); sleepRandom(500); recorrerTramoC(numTren); } 156 Ejercicio 13A SINCRONIZACIÓN CON SEMÁFOROS public class Metro { private static final int NUM_TRENES = 5; public static void tren(int numTren) { ... } private static void recorrerTramoA(int numTren) { printlnI("Entra TA T" + numTren); sleepRandom(500); printlnI("Sale TA T" + numTren); } public static void main(String args[]) { for (int i = 0; i < NUM_TRENES; i++) { createThread("tren", i); } startThreadsAndWait(); } } 157 Ejercicio 13A • El funcionamiento actual es: Varios trenes pueden estar en el mismo tramo a la vez Unos trenes pueden adelantar a otros • Se quiere el siguiente funcionamiento: Cada tramo sólo pueda estar ocupado por un tren en cada instante Los trenes nunca pueden adelantarse unos a otros • Para ello únicamente hay que añadir herramientas de sincronización de procesos (semáforos) SINCRONIZACIÓN CON SEMÁFOROS 158 Ejercicio 13B • Se quiere hacer el código genérico para que el número de tramos se pueda especificar con una constante SINCRONIZACIÓN CON SEMÁFOROS 159 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 160 Sincronización Avanzada • Todo programa concurrente se puede implementar con sincronizaciones condicionales y exclusiones mutuas • En algunas ocasiones este tipo de sincronizaciones son muy básicas, de muy bajo nivel • Existen otras formas de sincronización más avanzadas, de más alto nivel MEMORIA COMPARTIDA 161 Sincronización Avanzada • Veremos varios ejemplos de sincronizaciones avanzadas: Exclusión Mutua Generalizada: En la zona bajo exclusión mutua puede haber varios procesos (no sólo 1) Comunicación con Buffer: Varios procesos se comunican entre sí usando una estructura de datos intermedia para no bloquearse. MEMORIA COMPARTIDA 162 Exclusión Mutua Generalizada • Tipos de Exclusión Mutua generalizada: Exclusión Mutua con varios procesos: Sin ninguna restricción salvo el número de ellos Lectores – Escritores: Pueden ejecutar instrucciones múltiples procesos lectores o un único proceso escritor (se verá como ejercicio) Específicos del problema: Cada problema puede tener sus propias restricciones SINCRONIZACIÓN AVANZADA 163 Exclusión Mutua Generalizada • Exclusión Mutua con varios procesos: Se tiene cuando más de un proceso puede ejecutar la sección bajo exclusión mutua Si el número de procesos es K, se implementa con un semáforo con un valor inicial de K SINCRONIZACIÓN AVANZADA 164 Exclusión Mutua con varios proc public class ExcMutuaGenSem { private static SimpleSemaphore em; public static void p() { for (int i = 0; i < 5; i++) { // Sección bajo EM em.acquire(); printlnI("P_EM1"); printlnI("P_EM2"); em.release(); // Sección sin EM printlnI("P_1"); printlnI("P_2"); } } public static void main(String[] args) { em = new SimpleSemaphore(3); createThreads(5,"p"); startThreadsAndWait(); } } Para entrar en la sección bajo EM hay que coger una bola de la caja, y dejarla al salir para que la pueda coger otro proceso El contador del semáforo indica los huecos libres de la sección crítica 165 Comunicación con Buffer • En los ejercicios de productores y consumidores los procesos se comunicaban con una variable de tipo primitivo • Para adaptar las velocidades diferentes de los productores y consumidores y que no haya esperas innecesarias se utilizan buffers para almacenar temporalmente información • Un buffer permite que se pueda ir insertando información aunque no esté preparado el proceso encargado de consumirla • Los buffers se usan mucho en informática: Sistemas operativos: teclado, red, escritura en disco, etc. Aplicaciones distribuidas: Comunicación entre diferentes nodos de la red SINCRONIZACIÓN AVANZADA 166 Productores Consumidores • Para estudiar la comunicación con buffers implementaremos el “Problema de los Productores y los Consumidores” • Procesos Los Productores son procesos que generan datos Los Consumidores son procesos que consumen los datos en el orden en que se generan COMUNICACIÓN CON BUFFER 167 Productores Consumidores • Restricciones Cada productor genera un único dato cada vez Un consumidor consume un dato generado por el productor cada vez Todos los productos se consumen Si hay varios consumidores, ningún producto se consume dos veces COMUNICACIÓN CON BUFFER 168 Productores Consumidores • Comunicación Se utilizará un array o una lista para almacenar temporalmente los datos producidos antes de ser consumidos • Sincronización Los consumidores deben de bloquearse cuando no tengan datos que consumir (array vacío) Los productores deben bloquearse cuando no puedan insertar más datos en el buffer (array lleno) COMUNICACIÓN CON BUFFER 169 Productores Consumidores 169 public class ProdConsBufferMal { //Atributos y métodos del buffer ... //Hilos y main public static void productor() { for(int i=0; i<20; i++){ sleepRandom(500); insertarBuffer(i); } } public static void consumidor() { for(int i=0; i<20; i++){ String dato = sacarBuffer(); sleepRandom(500); print(dato); } } public static void main(String[] args) { createThreads(5, "productor"); createThreads(3, "consumidor"); startThreadsAndWait(); } } Esquema de la aplicación Hay 5 hilos productores que generan 20 productos y finalizan Hay 3 hilos consumidores que consumen 20 productos y finalizan Download this code https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9 https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9 https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9 https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9 https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9 170 Productores Consumidores class ProdConsBufferMal { //Atributos y métodos del buffer int[] datos = new int[10]; int posInser, posSacar = 0; public static void insertarBuffer(int dato) { datos[posInser] = dato; posInser = (posInser+1) % 10; } public static int sacarBuffer() { int dato = datos[posSacar]; posSacar = (posSacar+1) % 10; return dato; } //Hilos y main ... } Implementación del buffer con un array circular * * * * posInser posSacar 0 1 2 3 4 56 7 Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ProdConsBufferMal.java 171 Productores Consumidores • El programa no es correcto porque falta incorporar los puntos de sincronización Sincronización Condicional Un productor se bloqueará antes de insertar un dato si el buffer está lleno Un consumidor se bloqueará antes de leer un dato si el buffer está vacío Exclusión Mutua Las variables de control del buffer deben estar bajo exclusión mutua COMUNICACIÓN CON BUFFER 172 Productores Consumidores 172 172 public class ProdConsBuffer { //Atributos y métodos del buffer SimpleSemaphore nHuecos = new SimpleSemaphore(10); SimpleSemaphore nProductos = new SimpleSemaphore(0); SimpleSemaphore emPosInser = new SimpleSemaphore(1); SimpleSemaphore emPosSacar = new SimpleSemaphore(1); int[] datos = new int[10]; int posInser, posSacar = 0; public static void insertarBuffer(int dato) { nHuecos.acquire(); emPosInser.acquire(); datos[posInser] = dato; posInser = (posInser+1) % 10; emPosInser.release(); nProductos.release(); } public static int sacarBuffer() { nProductos.acquire(); emPosSacar.acquire(); int dato = datos[posSacar]; posSacar = (posSacar+1) % 10; emPosSacar.release(); nHuecos.release(); return dato; } //Hilos y main... } Cada contador se protege bajo su propio semáforo de exclusión mutua Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ProdConsBuffer.java 173 public class ProdConsBuffer { //Atributos y métodos del buffer SimpleSemaphore nHuecos = new SimpleSemaphore(10); SimpleSemaphore nProductos = new SimpleSemaphore(0); SimpleSemaphore emPosInser = new SimpleSemaphore(1); SimpleSemaphore emPosSacar = new SimpleSemaphore(1); int[] datos = new int[10]; int posInser, posSacar = 0; public static void insertarBuffer(int dato) { nHuecos.acquire(); emPosInser.acquire(); datos[posInser] = dato; posInser = (posInser+1) % 10; emPosInser.release(); nProductos.release(); } public static int sacarBuffer() { nProductos.acquire(); emPosSacar.acquire(); int dato = datos[posSacar]; posSacar = (posSacar+1) % 10; emPosSacar.release(); nHuecos.release(); return dato; } //Hilos y main... } El proceso que quiere sacar queda bloqueado si no hay productos Hay tantos permisos como productos Productores Consumidores 173Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ProdConsBuffer.java 174 public class ProdConsBuffer { //Atributos y métodos del buffer SimpleSemaphore nHuecos = new SimpleSemaphore(10); SimpleSemaphore nProductos = new SimpleSemaphore(0); SimpleSemaphore emPosInser = new SimpleSemaphore(1); SimpleSemaphore emPosSacar = new SimpleSemaphore(1); int[] datos = new int[10]; int posInser, posSacar = 0; public static void insertarBuffer(int dato) { nHuecos.acquire(); emPosInser.acquire(); datos[posInser] = dato; posInser = (posInser+1) % 10; emPosInser.release(); nProductos.release(); } public static int sacarBuffer() { nProductos.acquire(); emPosSacar.acquire(); int dato = datos[posSacar]; posSacar = (posSacar+1) % 10; emPosSacar.release(); nHuecos.release(); return dato; } //Hilos y main... } Productores Consumidores El proceso que quiere insertar queda bloqueado si no hay huecos Hay tantos permisos como huecos 174Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ProdConsBuffer.java 175 Memoria Compartida • SimpleConcurrent • Intercalación de instrucciones: Indeterminismo • Variables Compartidas • Sincronización con Espera Activa • Propiedades de corrección • Espera Activa vs Herramientas de sincronización • Introducción a los Semáforos • Sincronización con Semáforos • Sincronización avanzada • Conclusiones PROGRAMACIÓN CONCURRENTE 176 Conclusiones • Espera activa vs Herramientas de sincronización La espera activa es una técnica que no debe usarse nunca para desarrollar programas concurrentes porque es ineficiente Deben usarse herramientas de sincronización bloqueantes para aprovechar mejor los recursos de cómputo. En general es mejor usar las herramientas de alto nivel que proporcionan las tecnologías de desarrollo (lenguajes + librerías) que pueden estar implementadas con algoritmos no bloqueantes MEMORIA COMPARTIDA 177 Conclusiones • Ventajas de los Semáforos Son muy fáciles de entender Permiten sincronizar procesos de forma sencilla y eficiente Están presentes en la mayoría de las tencologías de desarrollo (lenguajes+librerías) MEMORIA COMPARTIDA 178 Conclusiones • Desventajas de los Semáforos Son primitivas de muy bajo nivel Omitir una llamada a release() puede provocar interbloqueo Omitir una llamada a acquire() puede provocar condiciones de carreara (por no estar bajo exclusión mutua) No estructuran el código del programa, lo que hace que los códigos sean difíciles de mantener y de eliminar los errores que se produzcan MEMORIA COMPARTIDA 179 Conclusiones • Para solventar los problemas de los semáforos, existen otras herramientas de sincronización de procesos con mayor nivel de abstracción Monitores (presentes en Java) Regiones Críticas Regiones Críticas Condicionales Sucesos Buzones Recursos MEMORIA COMPARTIDA 180 Conclusiones • Programación concurrente con memoria compartida: Difícil de implementar programas concurrentes y difícil de corregir errores Condiciones de carrera: Por no poner bajo EM los recursos compartidos. Interbloqueos: Cuando dos procesos se bloquean esperando que el otro los desbloquee. Aprovechamiento de recursos: Cuando el programa no es lo suficientemente concurrente y no aprovecha todos los procesadores disponibles o emplea demasiado tiempo sincronizando los procesos. MEMORIA COMPARTIDA 181 Conclusiones • Popularización de otros modelos de programación concurrente: Paso de mensajes: Actores: Librerías para Java (Akka, Vert.x, Reactor) CSP: Lenguaje de programación Go! Modelos asíncronos (sin bloqueo explícito) : JavaScript, Vert.x, Akka… MEMORIA COMPARTIDA 182 14. Sincronización de Barrera • La Sincronización de Barrera (Barrier) es una sincronización condicional en la que los procesos tienen que esperar a que el resto de procesos lleguen al mismo punto para poder continuar su ejecución • Vamos a estudiar este tipo de sincronización con los siguientes requisitos Programa con N procesos Cada proceso escribe la letra A y luego la B Los procesos tienen que esperar que todos hayan escrito la letra A antes de escribir la B EJERCICIOS 183 Sincronización de Barrera public class Ejer14_SincBarrera_Mal1 { private static final int NPROCESOS = 3; private static volatile int nProcesos; private static SimpleSemaphore sb; public static void proceso() { print("A"); nProcesos++; if (nProcesos < NPROCESOS) { sb.acquire(); } else { for (int i=0; i<NPROCESOS - 1; i++) { sb.release(); } } print("B"); } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); createThreads(NPROCESOS, "proceso"); startThreadsAndWait(); } } 1ª Aproximación Incorrecta Puede provocar interbloqueo (deadlock) porque nProcesos no está bajo exclusión mutua y se pueden producir errores al contar Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarrera_Mal1.java 184 private static final int NPROCESOS = 3; private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore emNProcesos; public static void proceso() { print("A"); emNProcesos.acquire(); nProcesos++; emNProcesos.release(); if (nProcesos < NPROCESOS) { sb.acquire(); } else { for (int i=0; i<NPROCESOS - 1; i++) { sb.release(); } } print("B"); } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThreads(NPROCESOS, "proceso"); startThreadsAndWait(); } } 2ª Aproximación Incorrecta Si se deja la consulta del contador fuera de la Exclusión Mutua, puede ocurrir que dos procesos hagan release() Sincronización de Barrera Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarrera_Mal2.java 185 public class Ejer14_SincBarrera { private static final int NPROCESOS = 3; private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore emNProcesos; public static void proceso() { print("A"); emNProcesos.acquire(); nProcesos++; if (nProcesos < NPROCESOS) { emNProcesos.release(); sb.acquire(); } else { emNProcesos.release(); for (int i=0; i< NPROCESOS-1; i++) { sb.release(); } } print("B"); } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThreads(NPROCESOS, "proceso"); startThreadsAndWait(); } } Solución Correcta Si el proceso no es el último, libera la EM y se bloquea Si es el último, sale de la EM y desbloquea a los demás procesos Sincronización de Barrera Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarrera.java 186 15. Sincronización de Barrera Cíclica • Se pide implementar un programa concurrente con las siguientes características: El programa tendrá 4 procesos que escriben una única letra indefinidamente. Un proceso escribirá la letra A, otra la B, otra la C y otro la D. Cada proceso escribe su letra y espera a que los demás hayan escrito también su letra para poder continuar. Uno de los procesos tiene que escribir un guión – después de que todos hayan escrito su letra y antes de que empiecen a escribirlas de nuevo. • La salida por pantalla del programa sería: ACDB-BACD-DCBA-ABCD-BCAD … EJERCICIOS 187 public static void procesoA() { while(true){ print("A"); sincronizacion(); } } public static void sincronizacion(){ emNProcesos.acquire(); nProcesos++; if (nProcesos < 4) { emNProcesos.release(); sb.acquire(); } else { println("-"); nProcesos = 0; emNProcesos.release(); for (int i = 0; i < 3; i++) { //sleepRandom(500); Condición carrera sb.release(); } } } Sincronización de Barrera Cíclica 1ª Aproximación Incorrecta Un proceso puede ser desbloqueado, mostrar su letra, volver a entrar en sincronizacion() ejectuar sb.acquire(), ser desbloqueado de nuevo (con un permiso que no estaba destinado para él) y volver a escribir su letra. Todo ello antes de que todos lo procesos de la primera iteración se hayan desbloqueado Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarreraCicl_Mal.java 188 public static void procesoA() { while(true){ print("A"); sincronizacion(); } } public static void sincronizacion(){ emNProcesos.acquire(); nProcesos++; if (nProcesos < 4) { emNProcesos.release(); //sleepRandom(500); Condición carrera sb.acquire(); } else { println("-"); nProcesos = 0; for (int i = 0; i < 3; i++) { sb.release(); } emNProcesos.release(); } } Sincronización de Barrera Cíclica 2ª Aproximación Incorrecta Se protege la entrada de los procesos a la segunda iteración hasta que todos los de la primera han sido desbloqueados. El problema es que no podemos garantizar que han llegado a bloquearse en “sb” antes de liberar la exclusión mutua. Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarreraCicl_Mal2.java 189 public static void procesoA() { while(true){ print("A"); sincronizacion(); } } public static void sincronizacion() { emNProcesos.acquire(); nProcesos++; if (nProcesos < 4) { emNProcesos.release(); sleepRandom(500); sb.acquire(); desbloqueo.release(); } else { println("-"); nProcesos = 0; sb.release(3); desbloqueo.acquire(3); emNProcesos.release(); } } Sincronización de Barrera Cíclica Solución Obligando a que los procesos desbloqueados señalizen que les ha dado tiempo a bloquearse y desbloquearse. De esta forma se garantiza que un proceso no se adelanta a los demás El nuevo semáforo desbloqueo se inicializa a 0 porque es de sincronización condicional Download this code https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarreraCicl.java 190 16. Descarga de Ficheros • Se desea ampliar el ejercicio de descarga de ficheros para descargar 10 ficheros. • Cuando los procesos hayan terminado de descargar un fichero, se esperen a que el último proceso muestre por pantalla el fichero para comenzar a descargar un nuevo fichero EJERCICIOS 191 17. Filósofos Comilones • 5 Filósofos que piensan libremente y comen en una mesa con 5 platos • Cada plato está asignado a un filósofo • Los platos se reponen continuamente • Hay 5 tenedores, uno entre cada par de platos adyacentes EJERCICIOS F3 F2 F1F5 F4 T4 T3 T2 T1 T5 192 • Procesos (Filósofos) El filósofo inicialmente piensa Se sienta delante de su plato y toma de uno en uno los tenedores situados a ambos lados de su plato Come Cuando finaliza, deja los dos tenedores en su posición original Todo filósofo que come, en algún momento se harta y termina 17. Filósofos Comilones EJERCICIOS public static void filosofo(int numFilosofo) { while (true) { printlnI("Pensar"); // Obtener tenedores printlnI("Comer"); // Liberar tenedores } } 193 17. Filósofos Comilones • Restricciones Un filósofo sólo puede comer cuando tenga los dos tenedores Los tenedores se cogen y se dejan de uno en uno Dos filósofos no pueden tener el mismo tenedor simultáneamente (EM de acceso al tenedor) Si varios filósofos tratan de comer al mismo tiempo, uno de ellos debe conseguirlo (Ausencia Interbloqueo) Si un filósofo desea comer y tiene competencia, en algún momento lo deberá poder hacer (Ausencia de Inanición) En ausencia de competencia, un filósofo que quiera comer deberá hacerlo sin retrasos innecesarios (Ausencia retrasos innecesarios) EJERCICIOS 194 19. Lectores Escritores • Acceso Combinado (Concurrente- Exclusivo) a variables compartidas • Dos tipos de procesos que comparten una misma Base de Datos: los Lectores y los Escritores • Los Lectores pueden leer registros de la BD • Los Escritores pueden leer y escribir los registros de la BD EJERCICIOS B.D Escritores Lectores Leer Escribir Leer 195 19. Lectores Escritores • Restricciones Cualquier número de lectores puede acceder a la vez a la BD, si no hay escritores accediendo El acceso a la BD de los escritores es exclusivo. Mientras haya algún lector leyendo, ningún escritor puede acceder a la BD, pero otros lectores sí. Se puede tener varios escritores trabajando, aunque estos se deberán sincronizar para que la escritura se lleve a cabo de uno en uno Se da prioridad a los escritores. Ningún lector puede acceder a la BD cuando haya escritores que desean hacerlo (Inanición de Lectores) EJERCICIOS 196 19. Lectores Escritores • Procesos EJERCICIOS public static void lector() { while(true){ inicioLectura(); println("Leer datos"); finLectura(); println("Procesar datos"); } } public static void escritor() { while (true) { println("Generar datos"); inicioEscritura(); println("Escribir datos"); finEscritura(); } } Slide 1 Concurrencia con Memoria Compartida SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent SimpleConcurrent Concurrencia con Memoria Compartida Intercalación de instrucciones: Indeterminismo El orden de las instrucciones El orden de las instrucciones El orden de las instrucciones El orden de las instrucciones Instrucciones atómicas Instrucciones atómicas Instrucciones atómicas Instrucciones atómicas Instrucciones atómicas Instrucciones atómicas Intercalación (Interleaving) Intercalación (Interleaving) Intercalación (Interleaving) Intercalación (Interleaving) Intercalación (Interleaving) Indeterminismo Indeterminismo Indeterminismo Indeterminismo Indeterminismo Indeterminismo Indeterminismo Concurrencia con Memoria Compartida Variables Compartidas Variables Compartidas Variables Compartidas Variables Compartidas Concurrencia con Memoria Compartida Concurrencia con Memoria Compartida Relaciones entre procesos Relaciones entre procesos Relaciones entre procesos Relaciones entre procesos Relaciones entre procesos Relaciones entre procesos Relaciones entre procesos Relaciones entre procesos Concurrencia con Memoria Compartida Sincronización Condicional Sincronización Condicional Sincronización Condicional Sincronización Condicional Sincronización Condicional Sincronización Condicional Ejercicio 1 – Productor Consumidor Ejercicio 2 – Productor Consumidor N Ejercicio 3 – Cliente Servidor 1 Petición Ejercicio 4 – Cliente Servidor N Peticiones Slide 71 Concurrencia con Memoria Compartida Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Primer intento Primer intento Slide 82 Primer intento Primer intento Segundo intento Segundo intento Slide 87 Segundo intento Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Ejercicio 5 - Museo Ejercicio 5 - Museo Ejercicio 6 – Museo con infinitas personas Ejercicio 7 – Museo con regalo Exclusión Mutua Exclusión Mutua Exclusión Mutua Exclusión Mutua Concurrencia con Memoria Compartida Propiedades de Corrección Propiedades de Corrección Propiedades de Corrección Propiedades de Corrección Propiedades de Corrección Propiedades de Corrección Propiedades de Corrección Propiedades de Corrección Concurrencia con Memoria Compartida Concurrencia con Memoria Compartida Problemas de la Espera Activa Problemas de la Espera Activa Problemas de la Espera Activa Concurrencia con Memoria Compartida Herramientas de sincronización Herramientas de sincronización Herramientas de sincronización Concurrencia con Memoria Compartida Implementación de la Multiprogramación Implementación de la Multiprogramación Implementación de la Multiprogramación Implementación de la Multiprogramación Implementación de la Multiprogramación Concurrencia con Memoria Compartida Algoritmos no bloqueantes Algoritmos no bloqueantes Algoritmos no bloqueantes Algoritmos no bloqueantes Concurrencia con Memoria Compartida Introducción a los semáforos Introducción a los semáforos Introducción a los semáforos Introducción a los semáforos Introducción a los semáforos Introducción a los semáforos Introducción a los semáforos Introducción a los semáforos Introducción a los semáforos Concurrencia con Memoria Compartida Sincronización Condicional Sincronización Condicional Sincronización Condicional Slide 150 Sincronización Condicional Exclusión Mutua Exclusión Mutua Exclusión Mutua Ejercicio 13A Ejercicio 13 Ejercicio 13A Ejercicio 13B Concurrencia con Memoria Compartida Sincronización Avanzada Sincronización Avanzada Exclusión Mutua Generalizada Exclusión Mutua Generalizada Exclusión Mutua con varios proc Comunicación con Buffer Productores Consumidores Productores Consumidores Productores Consumidores Productores Consumidores Productores Consumidores Productores Consumidores Productores Consumidores Productores Consumidores Productores Consumidores Concurrencia con Memoria Compartida Conclusiones Conclusiones Conclusiones Conclusiones Conclusiones Conclusiones 14. Sincronización de Barrera Sincronización de Barrera Sincronización de Barrera Slide 185 15. Sincronización de Barrera Cíclica Slide 187 Slide 188 Slide 189 16. Descarga de Ficheros 17. Filósofos Comilones 17. Filósofos Comilones 17. Filósofos Comilones 19. Lectores Escritores 19. Lectores Escritores 19. Lectores Escritores __MACOSX/Academia/Tema2/._Tema 2. Memoria compartida.pdf Academia/Tema2/Tema 2.1. Ejercicios.pdf Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 1 Ejercicios Tema 2. Parte 1 Concurrencia con Memoria Compartida Soluciones Los ejercicios de esta hoja sirven para que los alumnos puedan ejercitar sus conocimientos en el tema de la sincronización con memoria compartida. Ejercicio 1. Productor Consumidor Se desea implementar un programa concurrente con un proceso que produce información (productor) y otro proceso que hace uso de esa información (consumidor). El proceso productor genera un número aleatorio y termina. El proceso consumidor muestra por pantalla el número generado y termina. Ejercicio 2. Productor Consumidor Infinito Se desea ampliar el programa del Ejercicio 1 de forma que el proceso productor esté constantemente produciendo números consecutivos. El proceso consumidor estará constantemente consumiendo los productos. No se puede quedar ningún producto sin consumir. No se puede consumir dos veces el mismo producto. Ejercicio 3. Cliente Servidor 1 Petición Programa formado por un proceso servidor y otro proceso cliente. El proceso cliente hace una petición al proceso servidor (en forma de número aleatorio) y espera su respuesta, cuando la recibe, la procesa. El proceso servidor no hace nada hasta que recibe una petición, momento en el que suma 1 al número enviado en la petición y contesta con ese valor. El proceso cliente procesa la respuesta mostrándola por pantalla. Ejercicio 4. Cliente Servidor N Peticiones Se desea ampliar el programa anterior de forma que el proceso cliente esté constantemente haciendo peticiones y el proceso servidor atendiendo a las mismas. Ejercicio 5. Museo Existen 3 personas en el mundo, 1 museo, y sólo cabe una persona dentro del museo. Las personas realizan cuatro acciones dentro del museo: Cuando entran al museo saludan: “hola!” Cuando ven el museo se sorprenden: “qué bonito!” y “alucinante!” Cuando se van del museo se despiden: “adios” Cuando salen del museo se van a dar un “paseo” Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 2 Después del paseo, les ha gustado tanto que vuelven a entrar. Se pide: Número de métodos diferentes que ejecutarán los procesos Número de procesos del programa concurrente y de qué tipo son Escribir lo que hace cada proceso Identificar los recursos compartidos Identificar la sección o secciones bajo exclusión mutua Escribir el programa completo Ejercicio 6. Museo con infinitas personas Considerar que caben infinitas personas dentro del museo. Cada persona al entrar tiene que saludar diciendo cuántas personas hay en el museo: “hola, somos 3”. Al despedirse tiene que decir el número de personas que quedan tras irse: “adiós a los 2”. Ejercicio 7. Museo con regalo Para incentivar las visitas, cuando una persona entre en el museo estando vacío, será obsequiado con un regalo. Las personas, después de saludar, dicen si les han dado un regalo (“Tengo regalo”) o si no (“No tengo regalo”). Las personas deben permitir que otras personas saluden entre su saludo y el comentario sobre el regalo. Ejercicio 8. Preguntas Cortas 8.1) Dado el programa siguiente ¿podría darse el caso de que uno o ninguno de los dos procesos finalizase? Razona la respuesta. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer8_1 { static volatile boolean fin = false; public static void proceso() { int i = 0; // I1 while (!fin) { // I2 i++; // I3 if (i == 3) { // I4 fin = true; // I5 } else { fin = false; // I6 } } } public static void main(String[] args) { createThreads(2, "proceso"); startThreadsAndWait(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 3 8.2) Utilizando la espera activa se pretende resolver el problema de sincronización condicional de dos procesos concurrentes cuyo ciclo de vida es el siguiente: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer8_2 { static volatile boolean flag = false; public static void p1(){ while(true){ print("A"); flag = true; print("B"); } } public static void p2(){ while(true){ print("C"); while(!flag); print("D"); } } public static void main(String[] args) { createThread("p1"); createThread("p2"); startThreadsAndWait(); } } a) Después de la primera sincronización ¿se sincronizarán correctamente ambos procesos? Razone la respuesta. b) Qué tipo de interacción se produce entre los procesos P1 y P2? ¿En qué estados pueden estar los procesos P1 y P2? 8.3) ¿Por qué razón el acceso (lecturas y/o escrituras) a variables globales compartidas por varios procesos debe realizarse bajo exclusión mutua en el modelo de variables compartidas? 8.4) ¿En qué consiste el problema de la exclusión mutua? ¿Cuáles son los requisitos de su solución? 8.5) Dibuje el diagrama de estados de un proceso 8.6) ¿Qué diferencia hay entre los estados de preparado y de ejecución de un proceso? 8.7) Defina en qué consisten las propiedades de seguridad de un programa concurrente. Cite dos propiedades de este tipo. 8.8) ¿Qué es un interbloqueo de procesos? 8.9) ¿Cuáles son las dos actividades principales necesarias para gestionar la ejecución de procesos concurrentes en multiprogramación? 8.10) En la gestión de procesos concurrentes, explique la diferencia entre planificación y despacho. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 4 Ejercicio 9. Downloader a) Se quiere implementar una aplicación para descargar ficheros. La aplicación debe tener la capacidad de descargar un único fichero, pero debe ser capaz de descargar varios fragmentos del fichero de manera concurrente para aprovechar de forma más eficiente la red. Para simplificar la aplicación, consideramos que un fichero se representa en memoria como un array de enteros. Internamente, la aplicación dispone de una serie de procesos que van descargando los diferentes fragmentos del fichero (posiciones del array). Los procesos están ejecutando tres acciones: primero se determina el siguiente fragmento a descargar, a continuación se descarga ese fragmento, y por último se guarda el fragmento descargado en el array que representa el fichero. La solución se puede implementar con espera activa o con semáforos. La descarga de los distintos fragmentos se simulará con el método: private static int descargaDatos(int numFragmento) { sleepRandom(1000); return numFragmento * 2; } Cuando se ha terminado de descargar completamente el fichero, se muestra por pantalla y el programa termina. Para mostrar el fichero por pantalla se utilizará el siguiente método: private static void mostrarFichero() { println("--------------------------------------------------"); print("File = ["); for (int i = 0; i < N_FRAGMENTOS; i++) { print(fichero[i] + ","); } println("]"); } A continuación se presenta un esqueleto de la aplicación de descargas: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer_9A_Downloader_Plantilla { private static final int N_FRAGMENTOS = 10; private static final int N_HILOS = 3; private static volatile int[] fichero = new int[N_FRAGMENTOS]; //Add the attributes you need private static int descargaDatos(int numFragmento) { sleepRandom(1000); return numFragmento * 2; } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 5 private static void mostrarFichero() { println("--------------------------------------------------"); print("File = ["); for (int i = 0; i < N_FRAGMENTOS; i++) { print(fichero[i] + ","); } println("]"); } public static void downloader() { // Mientras hay fragmentos que descargar... //Descargar los datos del siguiente fragmento //Almacenar los datos en el array } public static void main(String[] args) { createThreads(N_HILOS, "downloader"); startThreadsAndWait(); mostrarFichero(); } } b) En la plantilla anterior, la impresión del fichero se realiza en el hilo principal. Se pide implementar el mismo programa de descarga de ficheros pero esta vez la impresión será realizada por el último proceso que guarde un fragmento descargado en el fichero. Ejercicio 10. Sincronización Condicional Implementar un programa concurrente en Java con SimpleConcurrent que tenga los siguientes requisitos: ● El programa consta de 3 procesos. ● Cada proceso escribe por pantalla varias letras y termina. El proceso 1 debe escribir la letra ‘A’ y la letra ‘B’. El proceso 2 debe escribir la letra ‘C’, la letra ‘D’ y la letra ‘E’. El proceso 3 debe escribir la letra ‘F’ y la letra ‘G’. ● Los procesos deben sincronizarse para que se cumplan las siguientes relaciones de precedencia: ● La sincronización condicional se deberá implementar con espera activa. A B D E G C F Proc1 Proc2 2 Proc3 2 Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 6 ● Algunas de las posibles salidas de este programa son: ● CFADBGE ● CFDABGE ● CFDAGBE ● FCDAGBE ● Algunas de las posibles salidas inválidas de este programa son: ● ACFDBGE ● CDFABGE Ejercicio 11. Cliente-Servidor con Proxy (1 petición) Implementar un programa concurrente formado por un proceso Servidor, un proceso Proxy y un proceso Cliente. El proceso Cliente genera un número aleatorio, se lo envía al Proxy y espera su respuesta. Cuando la recibe, la imprime por pantalla y termina. El proceso Proxy no hace nada hasta que recibe una petición. Cuando la recibe, el Proxy suma 1 al número enviado por el cliente y hace una petición al servidor con ese número, esperando su respuesta. Cuando el proceso Servidor reciba la petición, le suma 1 y se la envía de vuelta al proxy. Cuando el proxy recibe la petición del servidor, se la reenvía al cliente. La solución deberá implementarse con espera activa. Ejercicio 12. Cliente-Servidor con Proxy (N peticiones) Implementar un programa concurrente similar al anterior con la diferencia de que el proceso Cliente hace 10 peticiones, el proceso Proxy atendiendo esas 10 peticiones y se las redirige al proceso Servidor. El proceso servidor también responde a 10 peticiones. La solución deberá implementarse con espera activa. __MACOSX/Academia/Tema2/._Tema 2.1. Ejercicios.pdf Academia/Tema2/ejemplos tema2.zip ejemplo/Ejemplo1.java ejemplo/Ejemplo1.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class Ejemplo1 { public static void repeat(String text) { for (int i = 0; i < 5; i++) { println(text); } } public static void printText() { println("B1"); println("B2"); println("B3"); } public static void main(String[] args) { createThread("repeat", "XXXXX"); createThread("repeat", "-----"); createThread("printText"); startThreadsAndWait(); } } ejemplo/ExcMutua.java ejemplo/ExcMutua.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class ExcMutua { public static void p1() { while(true) { // Sección bajo EM printlnI("P1_EM1 "); printlnI("P1_EM2 "); // Sección sin EM printlnI("P1_1 "); printlnI("P1_2 "); } } public static void main(String[] args) { createThreads(2, "p1"); startThreadsAndWait(); } } ejemplo/ExcMutuaEA.java ejemplo/ExcMutuaEA.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class ExcMutuaEA { public static void p1() { for (int i = 0; i < 5; i++) { enterMutex(); // Sección bajo EM printlnI("P1_EM1 "); printlnI("P1_EM2 "); exitMutex(); // Sección sin EM printlnI("P1_1 "); printlnI("P1_2 "); } } public static void p2() { for (int i = 0; i < 5; i++) { enterMutex(); // Sección bajo EM printlnI("P2_EM1 "); printlnI("P2_EM2 "); exitMutex(); // Sección sin EM printlnI("P2_1 "); printlnI("P2_2 "); } } public static void main(String[] args) { createThread("p1"); createThread("p2"); startThreadsAndWait(); } } ejemplo/ExcMutuaEA2.java ejemplo/ExcMutuaEA2.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class ExcMutuaEA2 { public static void p() { for (int i = 0; i < 5; i++) { enterMutex(); printlnI("********"); printlnI("********"); printlnI("********"); exitMutex(); printlnI("--------"); printlnI("--------"); printlnI("--------"); printlnI("--------"); printlnI("--------"); } } public static void main(String[] args) { createThreads(4, "p"); startThreadsAndWait(); } } ejemplo/ExcMutuaGenSem.java ejemplo/ExcMutuaGenSem.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; import es.urjc.etsii.code.concurrency.SimpleSemaphore; public class ExcMutuaGenSem { private static SimpleSemaphore em; public static void p() { for (int i = 0; i < 5; i++) { em.acquire(); printlnI("P_EM1"); printlnI("P_EM2"); em.release(); // Sección No Crítica printlnI("P_1"); printlnI("P_2"); } } public static void main(String[] args) { em = new SimpleSemaphore(3); createThreads(5,"p"); startThreadsAndWait(); } } ejemplo/ExcMutuaIntento1.java ejemplo/ExcMutuaIntento1.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class ExcMutuaIntento1 { private static volatile boolean ocupado; public static void p1() { for (int i = 0; i < 5; i++) { while (ocupado); ocupado = true; // Sección bajo EM printlnI("P1_EM1 "); printlnI("P1_EM2 "); ocupado = false; // Sección sin EM printlnI("P1_1 "); printlnI("P1_2 "); } } public static void p2() { for (int i = 0; i < 5; i++) { while (ocupado); ocupado = true; // Sección bajo EM printlnI("P1_EM1 "); printlnI("P1_EM2 "); ocupado = false; // Sección sin EM printlnI("P1_1 "); printlnI("P1_2 "); } } public static void main(String[] args) { ocupado = false; createThread("p1"); createThread("p2"); startThreadsAndWait(); } } ejemplo/ExcMutuaIntento2.java ejemplo/ExcMutuaIntento2.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class ExcMutuaIntento2 { private static volatile boolean p1p; private static volatile boolean p2p; public static void p1() { for (int i = 0; i < 5; i++) { p1p = true; while (p2p); // Sección bajo EM printlnI("P1_EM1"); printlnI("P1_EM2"); p1p = false; // Sección sin EM printlnI("P1_1"); printlnI("P1_2"); } } public static void p2() { for (int i = 0; i < 5; i++) { p2p = true; while (p1p); // Sección bajo EM printlnI("P2_EM1"); printlnI("P2_EM2"); p2p = false; // Sección sin EM printlnI("P2_1"); printlnI("P2_2"); } } public static void main(String[] args) { p1p = false; p2p = false; createThread("p1"); createThread("p2"); startThreadsAndWait(); } } ejemplo/ExcMutuaSem.java ejemplo/ExcMutuaSem.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; import es.urjc.etsii.code.concurrency.SimpleSemaphore; public class ExcMutuaSem { private static SimpleSemaphore em; public static void p() { for (int i = 0; i < 5; i++) { //Sección bajo EM em.acquire(); printlnI("P_EM1"); printlnI("P_EM2"); em.release(); // Sección sin EM printlnI("P_1"); printlnI("P_2"); } } public static void main(String[] args) { em = new SimpleSemaphore(1); createThreads(2,"p"); startThreadsAndWait(); } } ejemplo/IncDec.java ejemplo/IncDec.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class IncDec { static volatile double x; public static void inc() { x = x + 1; } public static void dec() { x = x - 1; } public static void main(String[] args) { x = 0; createThread("inc"); createThread("dec"); startThreadsAndWait(); println("x:" + x); } } ejemplo/IncDecEM.java ejemplo/IncDecEM.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class IncDecEM { static volatile double x; public static void inc() { for (int i = 0; i < 10000000; i++) { enterMutex(); x = x + 1; exitMutex(); } } public static void dec() { for (int i = 0; i < 10000000; i++) { enterMutex(); x = x - 1; exitMutex(); } } public static void main(String[] args) { x = 0; createThread("inc"); createThread("dec"); startThreadsAndWait(); println("x:" + x); } } ejemplo/IncDecEM2.java ejemplo/IncDecEM2.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class IncDecEM2 { static volatile double x; static volatile double y; public static void inc() { enterMutex("x"); x = x + 1; exitMutex("x"); } public static void dec() { enterMutex("x"); x = x - 1; exitMutex("x"); } public static void incY() { enterMutex("y"); y = y + 1; exitMutex("y"); } public static void decY() { enterMutex("y"); y = y - 1; exitMutex("y"); } public static void main(String[] args) { x = 0; y=0; createThread("inc"); createThread("dec"); createThread("incY"); createThread("decY"); startThreadsAndWait(); println("x:" + x); println("y:" + y); } } ejemplo/IncDecFor.java ejemplo/IncDecFor.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class IncDecFor { static volatile double x; public static void inc() { for (int i = 0; i < 10000000; i++) { x = x + 1; } } public static void dec() { for (int i = 0; i < 10000000; i++) { x = x - 1; } } public static void main(String[] args) { x = 0; createThread("inc"); createThread("dec"); startThreadsAndWait(); println("x:" + x); } } ejemplo/MaxMin.java ejemplo/MaxMin.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class MaxMin { static volatile double n1, n2, min, max; public static void min() { min = n1 < n2 ? n1 : n2; } public static void max() { max = n1 > n2 ? n1 : n2; } public static void main(String[] args) { n1 = 3; n2 = 5; // I0 min(); // I1 max(); // I2 println("m:" + min + " M:" + max); // I3 } } ejemplo/MaxMinCon.java ejemplo/MaxMinCon.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class MaxMinCon { static volatile double n1, n2, min, max; public static void min() { min = n1 < n2 ? n1 : n2; } public static void max() { max = n1 > n2 ? n1 : n2; } public static void main(String[] args) { n1 = 3; n2 = 5; // I0 createThread("min"); // I1 createThread("max"); // I2 startThreadsAndWait(); println("m:" + min + " M:" + max); // I3 } } ejemplo/ProdConsBuffer.java ejemplo/ProdConsBuffer.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; import es.urjc.etsii.code.concurrency.SimpleSemaphore; public class ProdConsBuffer { private static final int BUFFER_SIZE = 10; private static int[] datos = new int[BUFFER_SIZE]; private static int posInser = 0; private static int posSacar = 0; private static SimpleSemaphore nHuecos = new SimpleSemaphore(BUFFER_SIZE); private static SimpleSemaphore nProductos = new SimpleSemaphore(0); private static SimpleSemaphore emPosInser = new SimpleSemaphore(1); private static SimpleSemaphore emPosSacar = new SimpleSemaphore(1); public static void insertarBuffer(int dato) { nHuecos.acquire(); emPosInser.acquire(); datos[posInser] = dato; posInser = (posInser+1) % datos.length; emPosInser.release(); nProductos.release(); } public static int sacarBuffer() { nProductos.acquire(); emPosSacar.acquire(); int dato = datos[posSacar]; posSacar = (posSacar+1) % datos.length; emPosSacar.release(); nHuecos.release(); return dato; } public static void productor() { for (int i = 0; i < 20; i++) { sleepRandom(500); insertarBuffer(i); } } public static void consumidor() { while (true) { int data = sacarBuffer(); sleepRandom(500); print(data + " "); } } public static void main(String[] args) { createThreads(5, "productor"); createThreads(3, "consumidor"); startThreadsAndWait(); } } ejemplo/ProdConsBufferMal.java ejemplo/ProdConsBufferMal.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class ProdConsBufferMal { private static int[] datos = new int[10]; private static int posInser = 0; private static int posSacar = 0; public static void insertarBuffer(int dato) { datos[posInser] = dato; posInser = (posInser + 1) % datos.length; } public static int sacarBuffer() { int dato = datos[posSacar]; posSacar = (posSacar + 1) % datos.length; return dato; } public static void productor() { for (int i = 0; i < 20; i++) { sleepRandom(500); insertarBuffer(i); } } public static void consumidor() { while (true) { int data = sacarBuffer(); sleepRandom(500); print(data + " "); } } public static void main(String[] args) { createThreads(5, "productor"); createThreads(3, "consumidor"); startThreadsAndWait(); } } ejemplo/SincCond.java ejemplo/SincCond.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; public class SincCond { static volatile boolean continuar; public static void a() { print("PA1 "); continuar = true; print("PA2 "); } public static void b() { print("PB1 "); while (!continuar); print("PB2 "); } public static void main(String[] args) { continuar = false; createThread("a"); createThread("b"); startThreadsAndWait(); } } ejemplo/SincCondSem.java ejemplo/SincCondSem.java package ejemplo; import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*; import es.urjc.etsii.code.concurrency.SimpleSemaphore; public class SincCondSem { static SimpleSemaphore continuar; public static void a() { print("PA1 "); continuar.release(); print("PA2 "); } public static void b() { print("PB1 "); continuar.acquire(); print("PB2 "); } public static void main(String[] args) { continuar = new SimpleSemaphore(0); createThread("a"); createThread("b"); startThreadsAndWait(); } } __MACOSX/Academia/Tema2/._ejemplos tema2.zip Academia/Tema2/Tema2.1 - Ejercicios (soluciones).pdf Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 1 Ejercicios Tema 2. Parte 1 Concurrencia con Memoria Compartida Soluciones Los ejercicios de esta hoja sirven para que los alumnos puedan ejercitar sus conocimientos en el tema de la sincronización con memoria compartida. Ejercicio 1. Productor Consumidor Se desea implementar un programa concurrente con un proceso que produce información (productor) y otro proceso que hace uso de esa información (consumidor). El proceso productor genera un número aleatorio y termina. El proceso consumidor muestra por pantalla el número generado y termina. Solución: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer1_ProdCons { static volatile boolean producido; static volatile double producto; public static void productor() { producto = Math.random(); producido = true; } public static void consumidor() { while (!producido); print("Producto: "+producto); } public static void main(String[] args) { producido = false; createThread("productor"); createThread("consumidor"); startThreadsAndWait(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 2 Ejercicio 2. Productor Consumidor Infinito Se desea ampliar el programa del Ejercicio 1 de forma que el proceso productor esté constantemente produciendo números consecutivos. El proceso consumidor estará constantemente consumiendo los productos. No se puede quedar ningún producto sin consumir. No se puede consumir dos veces el mismo producto. Intento erróneo de solución: En un primer intento, puede considerarse una solución errónea como la que se muestra a continuación. Esta solución no es correcta porque existen intercalaciones problemáticas que no son evidentes. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer2_ProdConsN_Mal { static volatile boolean producido; static volatile boolean consumido; static volatile double producto; public static void productor() { double num = 0; for(int i=0; i<5; i++){ producto = num++; producido = true; sleep(50); //Simula livelock consumido = false; while(!consumido); } } public static void consumidor() { for(int i=0; i<5; i++){ while (!producido); println("Producto: " + producto); producido = false; consumido = true; } } public static void main(String[] args) { producido = false; consumido = false; createThread("productor"); createThread("consumidor"); startThreadsAndWait(); } } El programa tiene un problema de interbloqueo activo. Si se descomenta la línea de sleep(50) se puede emular que el productor se ejecuta en un procesador lento y se puede forzar el problema. Una posible intercalación de sentencias es: Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 3 Productor consumidor producto producido consumido 1 while(¡producido); false true 2 producto = num++; 0 false true 3 producido = true; 0 true true 4 while(¡producido); 0 true true 5 println("Producto:" +producto); 0 6 consumido = true; 0 true true 7 producido = false; 0 false true 8 consumido = false; 0 false false 9 while(¡producido); 0 false false 10 while(¡consumido); 0 false false Se produce un interbloqueo. A simple vista es complicado ver el problema porque se solapan dos iteraciones del bucle infinito, pero si se desenreda el bucle (copiando el cuerpo del bucle varias veces) se puede apreciar mejor el problema. La solución consiste en actualizar la variable que controla la sincronización con el otro proceso antes de darle paso. En la siguiente implementación el proceso productor pone la variable consumido a false antes de poner producido a true. El proceso consumidor pone la variable producido a false antes de poner consumido a true. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer2_ProdConsN_1 { static volatile boolean producido; static volatile boolean consumido; static volatile double producto; public static void productor() { double num = 0; for(int i=0; i<5; i++){ producto = num++; consumido = false; sleep(50); producido = true; while(!consumido); } } public static void consumidor() { for(int i=0; i<5; i++){ while (!producido); println("Producto: " + producto); producido = false; consumido = true; } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 4 public static void main(String[] args) { producido = false; consumido = false; createThread("productor"); createThread("consumidor"); startThreadsAndWait(); } } Este ejemplo también se puede implementar con una única variable. La variable produciendo, que básicamente lo que hace es dar turnos de uno a otro. Cada proceso tiene un turno, y cuando termina su turno, le cede el turno al otro proceso. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer2_ProdConsN_2 { static volatile boolean produciendo; static volatile double producto; public static void productor() { double num = 0; for(int i=0; i<5; i++){ producto = num++; sleep(500); //Simula tiempo de producción produciendo = false; while(!produciendo); } } public static void consumidor() { for(int i=0; i<5; i++){ while (produciendo); println("Producto: " + producto); sleep(500); //Simula tiempo de consumo produciendo = true; } } public static void main(String[] args) { produciendo = true; createThread("productor"); createThread("consumidor"); startThreadsAndWait(); } } El problema de las soluciones anteriores es que no aprovechan bien los recursos disponibles. Si la máquina en la que se ejecuta el programa dispone de dos o más procesadores, pese a que cada hilo estuviera ejecutando en su propio procesador, con esta sincronización no podrían realizar trabajo en paralelo. Tal y como está diseñado, cuando un proceso está produciendo, el otro no puede consumir. Es decir, el tiempo Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 5 total de ejecución de este programa es de aproximadamente 5 segundos. Por cada elemento, 0,5s para producir y 0,5 segundo para consumir. A continuación se propone una versión más eficiente, porque permite que un hilo produzca a la misma vez que otro hilo está consumiendo. Para ello, basta con crear una variable local almacén, que guarde el producto en el proceso productor hasta que el consumidor está listo para consumir ese valor. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer2_ProdConsN_3 { static volatile boolean produciendo; static volatile double producto; public static void productor() { double num = 0; for(int i=0; i<5; i++){ double almacen = num++; // Crea el producto sleep(500); //Simula tiempo de consumo while (!produciendo); // Espera para colocarlo producto = almacen; // Guardar el valor produciendo = false; // Se notifica que ya hay valor } } public static void consumidor() { for(int i=0; i<5; i++){ while (produciendo); println("Producto: " + producto); sleep(500); //Simula tiempo de consumo produciendo = true; } } public static void main(String[] args) { produciendo = true; createThread("productor"); createThread("consumidor"); startThreadsAndWait(); } } El tiempo de ejecución de esta versión es algo más de 3s, porque la producción y el consumo se realizan en paralelo (salvo en el primer elemento, lo que añade 0,5 segundos al total). Ejercicio 3. Cliente Servidor 1 Petición Programa formado por un proceso servidor y otro proceso cliente. El proceso cliente hace una petición al proceso servidor (en forma de número aleatorio) y espera su respuesta, cuando la recibe, la procesa. El proceso servidor no hace nada hasta que recibe una petición, momento en el que suma 1 al número enviado en la petición y contesta con ese valor. El proceso cliente procesa la respuesta mostrándola por pantalla. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 6 package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer3_ClienteServidor { static volatile boolean pedido; static volatile boolean respondido; static volatile double peticion; static volatile double respuesta; public static void server() { while(!pedido); respuesta = peticion + 1; respondido = true; } public static void client() { peticion = Math.random(); pedido = true; while (!respondido); print("Response: "+respuesta); } public static void main(String[] args) { pedido = false; respondido = false; createThread("client"); createThread("server"); startThreadsAndWait(); } } Ejercicio 4. Cliente Servidor N Peticiones Se desea ampliar el programa anterior de forma que el proceso cliente esté constantemente haciendo peticiones y el proceso servidor atendiendo a las mismas. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer4_ClienteServidorN { static volatile boolean pedido; static volatile boolean respondido; static volatile double peticion; static volatile double respuesta; Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 7 public static void server() { while (true) { while (!pedido); pedido = false; respuesta = peticion + 1; respondido = true; } } public static void client() { while (true) { peticion = Math.random(); pedido = true; while (!respondido); respondido = false; println("Response: " + respuesta); } } public static void main(String[] args) { pedido = false; respondido = false; createThread("client"); createThread("server"); startThreadsAndWait(); } } Ejercicio 5. Museo Existen 3 personas en el mundo, 1 museo, y sólo cabe una persona dentro del museo. Las personas realizan cuatro acciones dentro del museo: Cuando entran al museo saludan: “hola!” Cuando ven el museo se sorprenden: “qué bonito!” y “alucinante!” Cuando se van del museo se despiden: “adios” Cuando salen del museo se van a dar un “paseo” Después del paseo, les ha gustado tanto que vuelven a entrar. Se pide: Número de métodos diferentes que ejecutarán los procesos Número de procesos del programa concurrente y de qué tipo son Escribir lo que hace cada proceso Identificar los recursos compartidos Identificar la sección o secciones bajo exclusión mutua Escribir el programa completo Solución: Número de métodos diferentes que ejecutarán los procesos: un único método “persona” Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 8 Número de procesos del programa concurrente y qué método ejecutan: 3 procesos que ejecutan el método “persona” cada uno. Escribir lo que hace cada proceso: public static void persona() { while (true) { printlnI("hola!"); printlnI("qué bonito!"); printlnI("alucinante!"); printlnI("adiós"); printlnI("paseo"); } } Identificar los recursos compartidos: El museo Identificar la sección o secciones bajo exclusión mutua: La sección bajo exclusión mutua es el conjunto de instrucciones que se ejecutan dentro del museo. Escribir el programa completo: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer5_Museo { public static void persona() { while (true) { enterMutex(); printlnI("hola!"); printlnI("qué bonito!"); printlnI("alucinante!"); printlnI("adiós"); exitMutex(); printlnI("paseo"); } } public static void main(String[] args) { createThreads(3, "persona"); startThreadsAndWait(); } } En el ejemplo anterior se puede apreciar el uso del método createThreads(3,”persona”) que permite crear varios procesos ejecutando el mismo método. Ejercicio 6. Museo con infinitas personas Considerar que caben infinitas personas dentro del museo. Cada persona al entrar tiene que saludar diciendo cuántas personas hay en el museo: “hola, somos 3”. Al despedirse tiene que decir el número de personas que quedan tras irse: “adiós a los 2”. Solución: Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 9 Del tema 1 de Introducción se sabe que incrementar o decrementar una variable no es una operación atómica de grano fino en Java, por lo tanto habrá que hacer que sea una instrucción atómica de grano grueso. Es decir, el contador debe estar bajo exclusión mutua, tanto en el incremento como en el decremento. En una primera aproximación, se podría pensar que esta puede ser la solución, aunque es errónea: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer6_Museo_Mal { static volatile int personas; public static void persona() { while (true) { enterMutex(); personas++; exitMutex(); printlnI("hola, somos "+personas); printlnI("qué bonito!"); printlnI("alucinante!"); enterMutex(); personas--; exitMutex(); printlnI("adiós a los "+personas); printlnI("paseo"); } } public static void main(String[] args) { personas = 0; createThreads(3, "persona"); startThreadsAndWait(); } } Esta aproximación no es correcta porque se puede dar el caso de que una persona entre al museo e incremente la variable y no salude hasta que haya llegado más gente. En ese caso, varias personas podrían saludar al mismo número de personas en el museo. Dicho de otra manera… podría estar un rato en el museo sin saludar a nadie y eso es una falta de cortesía. La intercalación problemática: P1 P2 contador personas++; 1 personas++; 2 printlnI("hola, somos "+personas); 2 printlnI("hola, somos "+personas); 2 Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 10 La solución correcta es la siguiente: package exercises.mutex; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer6Museo2 { static volatile int personas; public static void persona() { while (true) { enterMutex("contador"); personas++; printlnI("hola, somos "+personas); exitMutex("contador"); printlnI("qué bonito!"); printlnI("alucinante!"); enterMutex("contador"); personas--; printlnI("adiós a los "+personas); exitMutex("contador"); printlnI("paseo"); } } public static void main(String[] args) { personas = 0; createThreads(3, "persona"); startThreadsAndWait(); } } Ejercicio 7. Museo con regalo Para incentivar las visitas, cuando una persona entre en el museo estando vacío, será obsequiado con un regalo. Las personas, después de saludar, dicen si les han dado un regalo (“Tengo regalo”) o si no (“No tengo regalo”). Las personas deben permitir que otras personas saluden entre su saludo y el comentario sobre el regalo. Solución: Una posible aproximación errónea es la siguiente: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer7_Museo_Mal1 { static volatile int personas; Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 11 public static void persona() { while (true) { enterMutex(); personas++; printlnI("hola, somos "+personas); if(personas == 1){ printlnI("Tengo regalo"); } else { printlnI("No tengo regalo"); } exitMutex(); printlnI("qué bonito!"); printlnI("alucinante!"); enterMutex(); personas--; printlnI("adiós a los "+personas); exitMutex(); printlnI("paseo"); } } public static void main(String[] args) { personas = 0; createThreads(3, "persona"); startThreadsAndWait(); } } El problema de esta solución es que no permite que nadie entre o salga del museo mientras la persona está diciendo si tiene o no regalo y tal y como dice el enunciado, eso se debería permitir. En cierta forma está limitando la concurrencia. Para aumentar la concurrencia, se podría considerar la siguiente aproximación errónea: Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 12 package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer7_Museo_Mal2 { static volatile int personas; public static void persona() { while (true) { enterMutex(); personas++; printlnI("hola, somos "+personas); exitMutex(); if(personas == 1){ printlnI("Tengo regalo"); } else { printlnI("No tengo regalo"); } printlnI("qué bonito!"); printlnI("alucinante!"); enterMutex(); personas--; printlnI("adiós a los "+personas); exitMutex(); printlnI("paseo"); } } public static void main(String[] args) { personas = 0; createThreads(3, "persona"); startThreadsAndWait(); } } Pero esta solución tiene un problema importante. El contador de personas está bajo exclusión mutua, por lo tanto no se pueden producir condiciones de carrera, no se pueden producir errores en la cuenta. No obstante, si se pueden producir errores de funcionamiento porque es posible que alguien que tenga regalo (por haber llegado con el museo vacío) no se lo diga a los demás si entra una persona desde que realizó su saludo. Es decir, al sacar el “if” de la sección crítica, cualquier proceso podría cambiar el valor del contador de personas y por lo tanto que el funcionamiento del programa fuera erróneo. Una solución más concurrente que la primera aproximación y sin los problemas de la segunda aproximación se puede conseguir usando la variable local: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer7_Museo_1 { static volatile int personas; Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 13 public static void persona() { while (true) { boolean regalo = false; enterMutex(); if(personas == 0){ regalo = true; } personas++; printlnI("hola, somos "+personas); exitMutex(); if(regalo){ printlnI("Tengo regalo"); } else { printlnI("No tengo regalo"); } printlnI("qué bonito!"); printlnI("alucinante!"); enterMutex(); personas--; printlnI("adiós a los "+personas); exitMutex(); printlnI("paseo"); } } public static void main(String[] args) { personas = 0; createThreads(3, "persona"); startThreadsAndWait(); } } También se puede conseguir una solución equivalente sin necesidad de utilizar la variable local: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer7_Museo_2 { static volatile int personas; Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 14 public static void persona() { while (true) { enterMutex(); personas++; printlnI("hola, somos "+personas); if(personas == 1){ exitMutex(); printlnI("Tengo regalo"); } else { exitMutex(); printlnI("No tengo regalo"); } printlnI("qué bonito!"); printlnI("alucinante!"); enterMutex(); personas--; printlnI("adiós a los "+personas); exitMutex(); printlnI("paseo"); } } public static void main(String[] args) { personas = 0; createThreads(3, "persona"); startThreadsAndWait(); } } Como se puede ver en la solución, se puede dar el caso de que la entrada en una sección bajo exclusión mutua se ejecute en un sentencia y que su correspondiente salida se ejecute en la parte then o en la parte else de una setencia if. Este mecanismo es necesario si se quieren ejecutar acciones distintas al salir de la exclusión mutua sobre una variable que se está consultando en la condición del if. Ejercicio 8. Preguntas Cortas 8.1) Dado el programa siguiente ¿podría darse el caso de que uno o ninguno de los dos procesos finalizase? Razona la respuesta. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 15 package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer8_1 { static volatile boolean fin = false; public static void proceso() { int i = 0; // I1 while (!fin) { // I2 i++; // I3 if (i == 3) { // I4 fin = true; // I5 } else { fin = false; // I6 } } } public static void main(String[] args) { createThreads(2, "proceso"); startThreadsAndWait(); } } Solución: En el código existe una variable global compartida (Fin) por todos los procesos y acceden a ella sin exclusión mutua, esto puede plantear bastantes problemas. Puede darse el caso que un proceso ponga la variable fin a true porque ha llegado a la tercera iteración, y antes de que consulte la condición del While el otro proceso la ponga fin a false, si esto se repite para el otro proceso ninguno de los dos terminaría. Una secuencia de instrucciones problemática sería: Proceso_1 ejecuta el bucle 3 veces hasta I5 inclusive y pone Fin a true. Proceso_2 ejecuta el bucle 1 vez hasta I6 inclusive y pone Fin a false. Proceso_1 ejecuta el bucle 1 vez hasta I4 inclusive. Proceso_2 ejecuta el bucle 2 veces hasta I5 inclusive y pone Fin a true. Proceso_1 ejecuta I6 y pone Fin a false. A partir de ese momento ningún proceso abandonará el bucle infinito. 8.2) Utilizando la espera activa se pretende resolver el problema de sincronización condicional de dos procesos concurrentes cuyo ciclo de vida es el siguiente: Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 16 package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer8_2 { static volatile boolean flag = false; public static void p1(){ while(true){ print("A"); flag = true; print("B"); } } public static void p2(){ while(true){ print("C"); while(!flag); print("D"); } } public static void main(String[] args) { createThread("p1"); createThread("p2"); startThreadsAndWait(); } } a) Después de la primera sincronización ¿se sincronizarán correctamente ambos procesos? Razone la respuesta. No, ya que después de la primera iteración el flag queda a “true” y el proceso P2 puede ejecutar D sin esperar a que P1 ejecute antes A. b) Qué tipo de interacción se produce entre los procesos P1 y P2? ¿En qué estados pueden estar los procesos P1 y P2? La interacción es una Sincronización Condicional. Los procesos pueden estar en los estados Preparado y Ejecución. No pueden estar en estado bloqueado porque no se usa ninguna herramienta de sincronización. 8.3) ¿Por qué razón el acceso (lecturas y/o escrituras) a variables globales compartidas por varios procesos debe realizarse bajo exclusión mutua en el modelo de variables compartidas? Porque habitualmente la manipulación que se hace de las variables compartidas no son en general instrucciones atómicas. Por ejemplo, el incremento de una variable de tipo entero no es una operación atómica. En case de que varios procesos accedieran a una variable compartida sin que esté bajo exclusión mutua podrían producirse intercalaciones de las instrucciones atómicas en las que se descomponen que provocarían la inconsistencia de tales variables. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 17 8.4) ¿En qué consiste el problema de la exclusión mutua? ¿Cuáles son los requisitos de su solución? El problema de la exclusión mutua consiste en un conjunto de procesos concurrentes que deben ejecutar repetidamente un cierto código llamado sección bajo exclusión mutua, de manera que sólo un proceso puede estar ejecutando ese código en cada instante. 8.5) Dibuje el diagrama de estados de un proceso 8.6) ¿Qué diferencia hay entre los estados de preparado y de ejecución de un proceso? En el estado de preparado, el proceso está dispuesto para ejecutar instrucciones, a falta únicamente de un procesador. En el estado de ejecución, el proceso está ejecutando instrucciones en un procesador. 8.7) Defina en qué consisten las propiedades de seguridad de un programa concurrente. Cite dos propiedades de este tipo. Aquéllas que deben ser ciertas en todo instante de la ejecución del programa. Por tanto, aseguran que nada malo ocurrirá nunca durante una ejecución. Dos propiedades de seguridad son Exclusión Mutua y Ausencia de Interbloqueo Pasivo. 8.8) ¿Qué es un interbloqueo de procesos? Es un estado de un programa concurrente en el que un conjunto de procesos permanece esperando un suceso que sólo puede provocar un proceso del propio conjunto, con lo que todos los procesos esperan indefinidamente. El interbloqueo puede ser pasivo (deadlock) o activo (livelock). 8.9) ¿Cuáles son las dos actividades principales necesarias para gestionar la ejecución de procesos concurrentes en multiprogramación? Planificación y Despacho 8.10) En la gestión de procesos concurrentes, explique la diferencia entre planificación y despacho. La planificación consiste en decidir qué proceso va a ejecutarse cuando quede libre el procesador. El despacho consiste en otorgar el control de la CPU al proceso elegido por el planificador. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 18 Ejercicio 9. Downloader a) Se quiere implementar una aplicación para descargar ficheros. La aplicación debe tener la capacidad de descargar un único fichero, pero debe ser capaz de descargar varios fragmentos del fichero de manera concurrente para aprovechar de forma más eficiente la red. Para simplificar la aplicación, consideramos que un fichero se representa en memoria como un array de enteros. Internamente, la aplicación dispone de una serie de procesos que van descargando los diferentes fragmentos del fichero (posiciones del array). Los procesos están ejecutando tres acciones: primero se determina el siguiente fragmento a descargar, a continuación se descarga ese fragmento, y por último se guarda el fragmento descargado en el array que representa el fichero. La solución se puede implementar con espera activa o con semáforos. La descarga de los distintos fragmentos se simulará con el método: private static int descargaDatos(int numFragmento) { sleepRandom(1000); return numFragmento * 2; } Cuando se ha terminado de descargar completamente el fichero, se muestra por pantalla y el programa termina. Para mostrar el fichero por pantalla se utilizará el siguiente método: private static void mostrarFichero() { println("--------------------------------------------------"); print("File = ["); for (int i = 0; i < N_FRAGMENTOS; i++) { print(fichero[i] + ","); } println("]"); } A continuación se presenta un esqueleto de la aplicación de descargas: package ejercicio.downloader; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer_9A_Downloader_Plantilla { private static final int N_FRAGMENTOS = 10; private static final int N_HILOS = 3; private static volatile int[] fichero = new int[N_FRAGMENTOS]; //Add the attributes you need private static int descargaDatos(int numFragmento) { sleepRandom(1000); return numFragmento * 2; } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 19 private static void mostrarFichero() { println("--------------------------------------------------"); print("File = ["); for (int i = 0; i < N_FRAGMENTOS; i++) { print(fichero[i] + ","); } println("]"); } public static void downloader() { // Mientras hay fragmentos que descargar... //Descargar los datos del siguiente fragmento //Almacenar los datos en el array } public static void main(String[] args) { createThreads(N_HILOS, "downloader"); startThreadsAndWait(); mostrarFichero(); } } Solución usando espera activa: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer9A_Downloader { private static final int N_FRAGMENTOS = 10; private static final int N_HILOS = 3; private static volatile int[] fichero = new int[N_FRAGMENTOS]; private static volatile int fragPendiente = 0; private static int descargarDatos(int numFragment) { sleepRandom(1000); return numFragment * 2; } private static void mostrarFichero() { println("--------------------------------------------------"); print("File = ["); for (int i = 0; i < N_FRAGMENTOS; i++) { print(fichero[i] + ","); } println("]"); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 20 public static void downloader() { while (true) { enterMutex(); if (fragPendiente == N_FRAGMENTOS) { exitMutex(); break; } int fragDescargar = fragPendiente; fragPendiente++; exitMutex(); println(getThreadName() + ": Descargando fragmento " + fragDescargar); int downloadedData = descargarDatos(fragDescargar); println(getThreadName() + ": Escribiendo fragmento " + fragDescargar); fichero[fragDescargar] = downloadedData; } } public static void main(String[] args) { createThreads(N_HILOS, "downloader"); startThreadsAndWait(); mostrarFichero(); } } b) En la plantilla anterior, la impresión del fichero se realiza en el hilo principal. Se pide implementar el mismo programa de descarga de ficheros pero esta vez la impresión será realizada por el último proceso que guarde un fragmento descargado en el fichero. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer9B_Downloader { private static final int N_FRAGMENTOS = 10; private static final int N_HILOS = 3; private static volatile int[] fichero = new int[N_FRAGMENTOS]; private static volatile int fragPendiente = 0; private static volatile int hilosTerminados = 0; private static int descargarDatos(int numFragment) { sleepRandom(1000); return numFragment * 2; } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 21 private static void mostrarFichero() { println("--------------------------------------------------"); print("File = ["); for (int i = 0; i < N_FRAGMENTOS; i++) { print(fichero[i] + ","); } println("]"); } public static void downloader() { descargaFragmentos(); boolean mostrar = false; enterMutex("hilosTerminados"); hilosTerminados++; mostrar = hilosTerminados == N_HILOS; exitMutex("hilosTerminados"); if(mostrar){ mostrarFichero(); } } private static void descargaFragmentos() { while (true) { enterMutex("fragPendiente"); if (fragPendiente == N_FRAGMENTOS) { exitMutex("fragPendiente"); break; } int fragDescargar = fragPendiente; fragPendiente++; exitMutex("fragPendiente"); println(getThreadName() + ": Descargando fragmento " + fragDescargar); int downloadedData = descargarDatos(fragDescargar); println(getThreadName() + ": Escribiendo fragmento " + fragDescargar); fichero[fragDescargar] = downloadedData; } } public static void main(String[] args) { createThreads(N_HILOS, "downloader"); startThreadsAndWait(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 22 Ejercicio 10. Sincronización Condicional Implementar un programa concurrente en Java con SimpleConcurrent que tenga los siguientes requisitos: ● El programa consta de 3 procesos. ● Cada proceso escribe por pantalla varias letras y termina. El proceso 1 debe escribir la letra ‘A’ y la letra ‘B’. El proceso 2 debe escribir la letra ‘C’, la letra ‘D’ y la letra ‘E’. El proceso 3 debe escribir la letra ‘F’ y la letra ‘G’. ● Los procesos deben sincronizarse para que se cumplan las siguientes relaciones de precedencia: ● La sincronización condicional se deberá implementar con espera activa. ● Algunas de las posibles salidas de este programa son: ● CFADBGE ● CFDABGE ● CFDAGBE ● FCDAGBE ● Algunas de las posibles salidas inválidas de este programa son: ● ACFDBGE ● CDFABGE Solución: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer10_SincCond { static volatile boolean continuarA; static volatile boolean continuarD; static volatile boolean continuarE1; static volatile boolean continuarE2; public static void proc1() { while(!continuarA); print("A"); print("B"); continuarE1 = true; } A B D E G C F Proc1 Proc2 2 Proc3 2 Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 23 public static void proc2() { print("C"); continuarA = true; while(!continuarD); print("D"); while(!continuarE1); while(!continuarE2); print("E"); } public static void proc3() { print("F"); continuarD = true; print("G"); continuarE2 = true; } public static void main(String[] args) { continuarA = false; continuarD = false; continuarE1 = false; continuarE2 = false; createThread("proc1"); createThread("proc2"); createThread("proc3"); startThreadsAndWait(); } } Ejercicio 11. Cliente-Servidor con Proxy (1 petición) Implementar un programa concurrente formado por un proceso Servidor, un proceso Proxy y un proceso Cliente. El proceso Cliente genera un número aleatorio, se lo envía al Proxy y espera su respuesta. Cuando la recibe, la imprime por pantalla y termina. El proceso Proxy no hace nada hasta que recibe una petición. Cuando la recibe, el Proxy suma 1 al número enviado por el cliente y hace una petición al servidor con ese número, esperando su respuesta. Cuando el proceso Servidor reciba la petición, le suma 1 y se la envía de vuelta al proxy. Cuando el proxy recibe la petición del servidor, se la reenvía al cliente. La solución deberá implementarse con espera activa. Solución: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer11_ClienteServidorProxy { static volatile boolean pedido; static volatile boolean respondido; static volatile double peticion; static volatile double respuesta; Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 24 static volatile boolean pedidoProxy; static volatile boolean respondidoProxy; static volatile double peticionProxy; static volatile double respuestaProxy; public static void client() { peticionProxy = Math.random(); pedidoProxy = true; while (!respondidoProxy); print("Response: "+respuestaProxy); } public static void proxy() { while (!pedidoProxy); peticion = peticionProxy + 1; pedido = true; while (!respondido); respuestaProxy = respuesta; respondidoProxy = true; } public static void server() { while(!pedido); respuesta = peticion + 1; respondido = true; } public static void main(String[] args) { pedido = false; respondido = false; pedidoProxy = false; respondidoProxy = false; createThread("client"); createThread("proxy"); createThread("server"); startThreadsAndWait(); } } Ejercicio 12. Cliente-Servidor con Proxy (N peticiones) Implementar un programa concurrente similar al anterior con la diferencia de que el proceso Cliente hace 10 peticiones, el proceso Proxy atendiendo esas 10 peticiones y se las redirige al proceso Servidor. El proceso servidor también responde a 10 peticiones. La solución deberá implementarse con espera activa. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 25 Solución: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer12_ClienteServidorProxyN { static volatile boolean pedido; static volatile boolean respondido; static volatile double peticion; static volatile double respuesta; static volatile boolean pedidoProxy; static volatile boolean respondidoProxy; static volatile double peticionProxy; static volatile double respuestaProxy; public static void client() { for (int i = 0; i < 10; i++) { peticionProxy = Math.random(); pedidoProxy = true; while (!respondidoProxy); respondidoProxy = false; println("Response: " + respuestaProxy); } } public static void proxy() { for (int i = 0; i < 10; i++) { while (!pedidoProxy); pedidoProxy = false; peticion = peticionProxy + 1; pedido = true; while (!respondido); respondido = false; respuestaProxy = respuesta; respondidoProxy = true; } } public static void server() { for (int i = 0; i < 10; i++) { while (!pedido); pedido = false; respuesta = peticion + 1; respondido = true; } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 26 public static void main(String[] args) { pedido = false; respondido = false; pedidoProxy = false; respondidoProxy = false; createThread("client"); createThread("proxy"); createThread("server"); startThreadsAndWait(); } } __MACOSX/Academia/Tema2/._Tema2.1 - Ejercicios (soluciones).pdf Academia/Tema2/Tema 2.2. Ejercicios.pdf Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 1 Ejercicios Tema 2. Parte 2 Concurrencia con Memoria Compartida Soluciones Los ejercicios de esta hoja sirven para que los alumnos puedan ejercitar sus conocimientos en el tema de la sincronización con memoria compartida. Ejercicio 13 Una línea del metro de Madrid está formada por varios tramos de vía que son recorridos secuencialmente en un único sentido por diferentes trenes. El ciclo de vida de los procesos que controlan los trenes es el siguiente: public static void tren(int numTren) { sleepRandom(500); recorrerTramoA(numTren); sleepRandom(500); recorrerTramoB(numTren); sleepRandom(500); recorrerTramoC(numTren); } El esquema del programa completo es el siguiente: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer13A_Metro_Plantilla { private static final int NUM_TRENES = 5; public static void tren(int numTren) { sleepRandom(500); recorrerTramoA(numTren); sleepRandom(500); recorrerTramoB(numTren); sleepRandom(500); recorrerTramoC(numTren); } private static void recorrerTramoA(int numTren) { printlnI("Entra TA T" + numTren); sleepRandom(500); printlnI("Sale TA T" + numTren); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 2 private static void recorrerTramoB(int numTren) { printlnI("Entra TB T" + numTren); sleepRandom(500); printlnI("Sale TB T" + numTren); } private static void recorrerTramoC(int numTren) { printlnI("Entra TC T" + numTren); sleepRandom(500); printlnI("Sale TC T" + numTren); } public static void main(String args[]) { for (int i = 0; i < NUM_TRENES; i++) { createThread("tren", i); } startThreadsAndWait(); } } a) Se pide escribir todo el código de sincronización necesario para garantizar que se cumplan las siguientes restricciones: Cada tramo sólo pueda estar ocupado por un tren en cada instante No deben producirse interbloqueos Si un tren puede recorrer un tramo que se encuentra libre, debe hacerlo sin retrasarse innecesariamente Los trenes nunca pueden adelantarse unos a otros. La implementación se realizará con semáforos. b) Ahora se pide generalizar la solución anterior para cualquier número de tramos. Ejercicio 14. Sincronización de barrera La Sincronización de Barrera es una sincronización condicional en la que los procesos tienen que esperar a que el resto de procesos lleguen al mismo punto para poder continuar su ejecución. Este tipo de sincronización es muy utilizada en programas concurrentes. Se pide implementar un programa concurrente con las siguientes características: El programa tendrá N procesos del mismo tipo. Cada proceso escribe la letra ‘A’, luego la ‘B’ y terminan Los procesos tienen que esperar que todos hayan escrito la letra ‘A’ antes de poder escribir la ‘B’ La implementación se realizará con semáforos. La idea básica al implementar la sincronización de barrera es usar un contador de procesos que han llegado a la barrera. Si el contador es menor que el número total de procesos, entonces el proceso se bloquea. Si el contador es igual al número total de procesos, entonces ese proceso desbloquea a los demás para que todos puedan proseguir su ejecución. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 3 Ejercicio 15. Sincronización de barrera cíclica Se pide implementar un programa concurrente con las siguientes características: El programa tendrá 4 procesos que escriben una única letra indefinidamente. Un proceso escribirá la letra A, otra la B, otra la C y otro la D. Cada proceso escribe su letra y espera a que los demás hayan escrito también su letra para poder continuar. Uno de los procesos tiene que escribir un guión – después de que todos hayan escrito su letra y antes de que empiecen a escribirlas de nuevo. La implementación se realizará con semáforos. La salida por pantalla del programa con 4 procesos sería: ACDB-BACD-DCBA-ABCD-BCAD … Ejercicio 16. Descarga de múltiples ficheros Se desea ampliar el ejercicio de descarga de ficheros (Ejercicio 9). Se requiere que cuando los procesos hayan terminado de descargar un fichero, se esperen a que se muestre por pantalla y comiencen a descargar un nuevo fichero, así hasta descargar 10 ficheros. La implementación deberá realizarse íntegramente con semáforos. Ejercicio 17. Filósofos Comilones Se pide implementar un programa con semáforos con las siguientes características: 5 Filósofos que piensan libremente y comen en una mesa con 5 platos Cada filósofo tiene un plato asignado para él sólo Hay 5 tenedores, uno entre cada par de platos adyacentes Cada uno de los 5 filósofos está representado por un proceso. Los filósofos tienen el siguiente ciclo de vida: El filósofo inicialmente piensa. Se sienta delante de su plato y toma de uno en uno los tenedores situados a ambos lados de su plato. Come. F3 F2 F1F5 F4 T4 T3 T2 T1 T5 Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 4 Cuando finaliza, deja los dos tenedores en su posición original. Todo filósofo que come, en algún momento se harta y termina. Vuelve a empezar. Se pide implementar un programa concurrente en Java con SimpleConcurrent que simule la vida de los filósofos. Para ello, se deben cumplir las siguientes restricciones: Un filósofo sólo puede comer cuando tenga los dos tenedores. Los tenedores se cogen y se dejan de uno en uno. Dos filósofos no pueden tener el mismo tenedor simultáneamente (Exclusión Mutua de acceso al tenedor). Si varios filósofos tratan de comer al mismo tiempo, uno de ellos debe conseguirlo (Ausencia Interbloqueo). Si un filósofo desea comer y tiene competencia, en algún momento lo deberá poder hacerlo (Ausencia de Inanición). En ausencia de competencia, un filósofo que quiera comer deberá hacerlo sin retrasos innecesarios (Ausencia retrasos innecesarios). Se propone el siguiente esquema de código para la resolución del ejercicio: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer17_Filosofos_Plantilla { public static final int N_FILOSOFOS = 5; public static void filosofo(int numFilosofo) { while (true) { printlnI("Pensar"); // Obtener tenedores printlnI("Comer"); // Liberar tenedores } } public static void main(String[] args) { for (int i = 0; i < N_FILOSOFOS; i++) { createThread("filosofo", i); } startThreadsAndWait(); } } Ejercicio 18. Preguntas cortas sobre semáforos 18.1) Describa la semántica de los métodos acquire() y release() aplicadas sobre semáforos generales. 18.2) ¿Cómo se resuelve el acceso exclusivo de un conjunto de procesos a una variable compartida mediante semáforos? Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 5 18.3) Complete la siguiente tabla cuando el proceso P ejecuta la operación de la columna izquierda sobre el semáforo s. Operaciones sobre el Semáforo S Contador de Permisos Antes Después Lista de Procesos Bloquedados Antes Después s.acquire() 3 Vacía s.acquire() 0 P1 s.release() 1 Vacía s.release() 0 Vacía s.release() 0 P1, P2 s.acquire() 0 Vacía s.acquire() 0 P1 s.acquire() 1 Vacía s.release() 0 Vacía s.release() 0 P1 s.release() 1 Vacía Solución: Operaciones sobre el Semáforo s Contador de Permisos Antes Después Lista de Procesos Bloquedados Antes Después s.acquire() 3 2 Vacía Vacía s.acquire() 0 0 P1 P1, P s.release() 1 2 Vacía Vacía s.release() 0 1 Vacía Vacía s.release() 0 0 P1, P2 P1 o P2 s.acquire() 0 0 Vacía P s.acquire() 0 0 P1 P1 y P s.acquire() 1 0 Vacía Vacía s.release() 0 1 Vacía Vacía s.release() 0 0 P1 Vacía s.release() 1 2 Vacía Vacía 18.4) ¿Cuántos semáforos se precisan para implantar un buffer de tamaño ilimitado? Razone la respuesta. 18.5) Con qué valores se debe inicializar un semáforo para: a) resolver la sincronización condicional de dos procesos? b) resolver la exclusión mutua en el acceso a un recurso de N procesos? c) resolver la exclusión mutua generalizada de N procesos a un recurso que soporta K accesos simultáneos? Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 6 18.6) ¿Por qué las sentencias s.acquire() y s.release() garantizan la exclusión mutua en el acceso al contador de permisos del semáforo s? 18.7) Escribe el código correspondiente al método puntoSincronización del siguiente programa utilizando semáforos, para evitar que alguno de los N procesos comience a ejecutar el procedimiento B antes de que alguno de los demás haya finalizado la ejecución del procedimiento A. Incorpora los atributos e inicializaciones necesarios. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_7_Enun { private static final int NPROCESOS = 3; public static void proceso() { print("A"); puntoSincronizacion(); print("B"); } private static void puntoSincronizacion() { } public static void main(String[] args) { createThreads(NPROCESOS, "proceso"); startThreadsAndWait(); } } 18.8) En un semáforo cuyo contador de permisos es mayor que cero, ¿puede haber algún proceso bloqueado? Justifique su respuesta 18.9) Dado el siguiente diagrama de precedencia de tres procesos: Escriba en Java con SimpleConcurrent usando Semáforos un programa para cumplir el anterior diagrama de precedencia. 18.10) Describe exactamente todas las situaciones que podrían ocurrir en el siguiente programa. Si adviertes algún error, escribe una solución que garantice que a x se accede bajo exclusión mutua y que P2 nunca termina hasta que ambos procesos hayan incrementado la variable x. A B C D P1 P2 P3 Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 7 package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer18_10_Enun { private static int x; private static SimpleSemaphore sincronizacion; private static SimpleSemaphore exclusion; public static void p1() { exclusion.acquire(); x++; if (x == 2) { sincronizacion.release(); } exclusion.release(); } public static void p2() { exclusion.acquire(); x++; if (x == 1) { sincronizacion.acquire(); } exclusion.release(); } public static void main(String[] args) { x = 0; sincronizacion = new SimpleSemaphore(0); exclusion = new SimpleSemaphore(1); createThread("p1"); createThread("p2"); startThreadsAndWait(); } } 18.11) ¿Hay algún error en el siguiente programa? package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_11_Enun { private static int x; private static int y; private static SimpleSemaphore sY; private static SimpleSemaphore sX; public static void pA() { sX.acquire(); x++; sY.acquire(); y *= x; sY.release(); sX.release(); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 8 public static void pB() { sY.acquire(); y++; sX.acquire(); x *= y; sX.release(); sY.release(); } public static void main(String[] args) { x = 2; y = 3; sX = new SimpleSemaphore(1); sY = new SimpleSemaphore(1); createThread("pA"); createThread("pB"); startThreadsAndWait(); } } 4.12) Escribe en Java con SimpleConcurrent el programa que cumpla el siguiente diagrama de precedencia de procesos: 18.13) El siguiente código trata de implantar una sincronización de barrera para un número de procesos determinado por la constante N. package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_13_Enun { private static final int N = 3; private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore emNProcesos; public static void proceso() { while (true) { print("A"); puntoSincronizacion(); print("B"); } } A C D F P1 P2 P3 B E Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 9 private static void puntoSincronizacion() { emNProcesos.acquire(); nProcesos++; if (nProcesos == N) { nProcesos = 0; emNProcesos.release(); //I0 for (int i = 0; i < N; i++) { sb.release(); //I1 } } else { emNProcesos.release(); sb.acquire(); } } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThreads(N, "proceso"); startThreadsAndWait(); } } Explica brevemente por qué no se sincronizan adecuadamente los procesos en la sincronización de barrera en este ejemplo. Observa las instrucciones etiquetadas con I0 e I1. 18.14) Dados los dos siguientes procesos concurrentes: public static void p1(){ sX.acquire(); sY.acquire(); x += y; sY.release(); sX.release(); } public static void p2(){ sY.acquire(); sX.acquire(); y += x; sX.release(); sY.release(); } Si inicialmente sX y sY tienen el contador de permisos a valor 1, y X e Y tienen valor 1, describe todas las situaciones que podrían ocurrir. Si adviertes algún error, corrígelo. 18.15) Sea el siguiente programa en el que N procesos compiten por utilizar un cierto recurso, de manera que como máximo K de estos procesos puedan usar el recurso concurrentemente. ¿Cómo se denomina este problema clásico? Escriba en Java con SimpleConcurrent una solución a este problema usando semáforos. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 10 package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_15_Enun { private static final int N_PROCESOS = 4; private static final int K = 2; public static void p() { while(true){ // Obtener recurso // Usar recurso // Liberar recurso // Hacer otras cosas } } public static void main(String[] args) { createThreads(N_PROCESOS,"p"); startThreadsAndWait(); } } 18.16) Dibuje el diagrama de precedencia del siguiente programa. package exercises.semaphores; import static simpleconcurrent.SimpleConcurrent.*; import simpleconcurrent.SimpleSemaphore; public class Ejer4_16_Enun { private static SimpleSemaphore[] sinc = new SimpleSemaphore[2]; public static void puntoSinc(int numProc) { if (numProc == 0) { sinc[0].release(); sinc[1].release(); } else { if (numProc == 1) { sinc[1].release(); sinc[0].acquire(); } else { sinc[1].acquire(); sinc[1].acquire(); } } } public static void proceso(int numProc) { print("A"+numProc+" "); puntoSinc(numProc); print("B"+numProc+" "); } public static void main(String[] args) { Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 11 sinc[0] = new SimpleSemaphore(0); sinc[1] = new SimpleSemaphore(0); for (int i = 0; i < 3; i++) { createThread("proceso", i); } startThreadsAndWait(); } } 18.17) Dados el siguiente programa concurrente: package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_17_Enun { private static SimpleSemaphore s; public static void p1(){ print("A"); s.release(); } public static void p2(){ s.acquire(); print("B"); s.release(); s.release(); } public static void p3(){ s.acquire(); s.acquire(); print("C"); } public static void main(String[] args){ s = new SimpleSemaphore(0); createThread("p1"); createThread("p2"); createThread("p3"); startThreadsAndWait(); } } ¿Se puede asegurar que siempre la ejecución de A precede a la de B y la de B precede a la de C? Ejercicio 19. Lectores-Escritores Se desea implementar un programa concurrente con semáforos con procesos lectores y procesos escritores. En este ejercicio vamos a un ver un ejemplo típico de acceso combinado (concurrente y exclusivo) a variables compartidas. Este tipo de acceso se tiene en los sistemas gestores de bases de datos. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 12 Los procesos lectores pueden leer información de la base de datos y los procesos escritores pueden escribir información en ella. Para simplificar la resolución del ejercicio, las operaciones de los procesos lectores y escritores se implementarán como escrituras por pantalla. La solución se basará en el siguiente esquema: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer19_LectoresEscritores_Plantilla { public static void inicioLectura() { } public static void finLectura() { } public static void inicioEscritura() { } public static void finEscritura() { } public static void lector() { while(true){ inicioLectura(); println("Leer datos"); finLectura(); println("Procesar datos"); } } public static void escritor() { while (true) { println("Generar datos"); inicioEscritura(); println("Escribir datos"); finEscritura(); } } public static void main(String[] args) { createThreads(5, "lector"); createThreads(3, "escritor"); startThreadsAndWait(); } } B.D Escritores Lectores Leer Escribir Leer Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 13 Se pide implementar los métodos inicioLectura(), finLectura(), inicioEscritura(), finEscritura() de forma que se cumplan las siguientes propiedades: Cualquier número de lectores puede acceder a la vez a la BD, siempre que no haya escritores accediendo. El acceso a la BD de los escritores es exclusivo. Mientras haya algún lector leyendo, ningún escritor puede acceder a la BD, pero otros lectores sí. Se puede tener varios escritores trabajando, aunque estos se deberán sincronizar para que la escritura se lleve a cabo de uno en uno. Se da prioridad a los escritores. Ningún lector puede acceder a la BD cuando haya escritores que desean hacerlo (aunque esto pueda causar inanición de Lectores). __MACOSX/Academia/Tema2/._Tema 2.2. Ejercicios.pdf Academia/Tema2/Tema2.2 - Ejercicios (soluciones).pdf Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 1 Ejercicios Tema 2. Parte 2 Concurrencia con Memoria Compartida Soluciones Los ejercicios de esta hoja sirven para que los alumnos puedan ejercitar sus conocimientos en el tema de la sincronización con memoria compartida. Ejercicio 13 Una línea del metro de Madrid está formada por varios tramos de vía que son recorridos secuencialmente en un único sentido por diferentes trenes. El ciclo de vida de los procesos que controlan los trenes es el siguiente: public static void tren(int numTren) { sleepRandom(500); recorrerTramoA(numTren); sleepRandom(500); recorrerTramoB(numTren); sleepRandom(500); recorrerTramoC(numTren); } El esquema del programa completo es el siguiente: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer13A_Metro_Plantilla { private static final int NUM_TRENES = 5; public static void tren(int numTren) { sleepRandom(500); recorrerTramoA(numTren); sleepRandom(500); recorrerTramoB(numTren); sleepRandom(500); recorrerTramoC(numTren); } private static void recorrerTramoA(int numTren) { printlnI("Entra TA T" + numTren); sleepRandom(500); printlnI("Sale TA T" + numTren); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 2 private static void recorrerTramoB(int numTren) { printlnI("Entra TB T" + numTren); sleepRandom(500); printlnI("Sale TB T" + numTren); } private static void recorrerTramoC(int numTren) { printlnI("Entra TC T" + numTren); sleepRandom(500); printlnI("Sale TC T" + numTren); } public static void main(String args[]) { for (int i = 0; i < NUM_TRENES; i++) { createThread("tren", i); } startThreadsAndWait(); } } a) Se pide escribir todo el código de sincronización necesario para garantizar que se cumplan las siguientes restricciones: • Cada tramo sólo pueda estar ocupado por un tren en cada instante • No deben producirse interbloqueos • Si un tren puede recorrer un tramo que se encuentra libre, debe hacerlo sin retrasarse innecesariamente • Los trenes nunca pueden adelantarse unos a otros. • La implementación se realizará con semáforos. Solución: La idea básica de la solución es poner cada tramo de la vía bajo exclusión mutua. Eso garantiza que no hay dos trenes en el mismo tramo de vía. Para evitar los adelantamientos, basta con salir de la exclusión mutua del tramo anterior cuando ya se ha entrado en la exclusión mutua del tramo siguiente. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer13A_Metro { private static final int NUM_TRENES = 5; private static SimpleSemaphore emTramoA; private static SimpleSemaphore emTramoB; private static SimpleSemaphore emTramoC; public static void tren(int numTren) { sleepRandom(500); emTramoA.acquire(); recorrerTramoA(numTren); sleepRandom(500); Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 3 emTramoB.acquire(); emTramoA.release(); recorrerTramoB(numTren); sleepRandom(500); emTramoC.acquire(); emTramoB.release(); recorrerTramoC(numTren); emTramoC.release(); } private static void recorrerTramoA(int numTren) { printlnI("Entra TA T" + numTren); sleepRandom(500); printlnI("Sale TA T" + numTren); } private static void recorrerTramoB(int numTren) { printlnI("Entra TB T" + numTren); sleepRandom(500); printlnI("Sale TB T" + numTren); } private static void recorrerTramoC(int numTren) { printlnI("Entra TC T" + numTren); sleepRandom(500); printlnI("Sale TC T" + numTren); } public static void main(String args[]){ emTramoA = new SimpleSemaphore(1); emTramoB = new SimpleSemaphore(1); emTramoC = new SimpleSemaphore(1); for(int i=0; i<NUM_TRENES; i++){ createThread("tren", i); } startThreadsAndWait(); } } b) Ahora se pide generalizar la solución anterior para cualquier número de tramos. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer13B_Metro { private static final int NUM_TRAMOS = 6; private static final int NUM_TRENES = 3; private static SimpleSemaphore[] emTramos; Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 4 public static void tren(int numTren) { for (int numTramo = 0; numTramo < NUM_TRAMOS; numTramo++) { sleepRandom(500); emTramos[numTramo].acquire(); if(numTramo != 0){ emTramos[numTramo-1].release(); } recorrerTramo(numTren, numTramo); } emTramos[NUM_TRAMOS-1].release(); } private static void recorrerTramo(int numTren, int numTramo) { char tramo = (char)('A' + numTramo); printlnI("Entra T"+tramo+" T" + numTren); sleepRandom(500); printlnI("Sale T"+tramo+" T" + numTren); } public static void main(String args[]){ emTramos = new SimpleSemaphore[NUM_TRAMOS]; for(int i=0; i<NUM_TRAMOS; i++){ emTramos[i] = new SimpleSemaphore(1); } for(int i=0; i<NUM_TRENES; i++){ createThread("tren", i); } startThreadsAndWait(); } } Ejercicio 14. Sincronización de barrera La Sincronización de Barrera es una sincronización condicional en la que los procesos tienen que esperar a que el resto de procesos lleguen al mismo punto para poder continuar su ejecución. Este tipo de sincronización es muy utilizada en programas concurrentes. Se pide implementar un programa concurrente con las siguientes características: • El programa tendrá N procesos del mismo tipo. • Cada proceso escribe la letra ‘A’, luego la ‘B’ y terminan • Los procesos tienen que esperar que todos hayan escrito la letra ‘A’ antes de poder escribir la ‘B’ • La implementación se realizará con semáforos. La idea básica al implementar la sincronización de barrera es usar un contador de procesos que han llegado a la barrera. Si el contador es menor que el número total de procesos, entonces el proceso se bloquea. Si el contador es igual al número total de procesos, entonces ese proceso desbloquea a los demás para que todos puedan proseguir su ejecución. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 5 1ª Aproximación Incorrecta - Puede provocar interbloqueo (deadlock) porque nProcesos no está bajo exclusión mutua y se pueden producir errores al contar package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer14_SincBarrera_Mal1 { private static final int NPROCESOS = 3; private static volatile int nProcesos; private static SimpleSemaphore sb; public static void proceso() { print("A"); nProcesos++; if (nProcesos < NPROCESOS) { sb.acquire(); } else { for (int i = 0; i < NPROCESOS - 1; i++) { sb.release(); } } print("B"); } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); createThreads(NPROCESOS, "proceso"); startThreadsAndWait(); } } 2ª Aproximación Incorrecta - Si se deja la consulta del contador fuera de la Exclusión Mutua, puede ocurrir que dos procesos hagan release(). package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer14_SincBarrera_Mal2 { private static final int NPROCESOS = 3; private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore emNProcesos; public static void proceso() { print("A"); emNProcesos.acquire(); nProcesos++; emNProcesos.release(); Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 6 if (nProcesos < NPROCESOS) { sb.acquire(); } else { for (int i = 0; i < NPROCESOS - 1; i++) { sb.release(); } } print("B"); } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThreads(NPROCESOS, "proceso"); startThreadsAndWait(); } } Solución: Si el proceso no es el último, libera la EM y se bloquea. Si es el último, sale de la EM y desbloquea a los demás procesos. package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer14_SincBarrera { private static final int NPROCESOS = 3; private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore emNProcesos; public static void proceso() { print("A"); emNProcesos.acquire(); nProcesos++; if (nProcesos < NPROCESOS) { emNProcesos.release(); sb.acquire(); } else { emNProcesos.release(); for (int i = 0; i < NPROCESOS - 1; i++) { sb.release(); } } print("B"); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 7 public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThreads(NPROCESOS, "proceso"); startThreadsAndWait(); } } Ejercicio 15. Sincronización de barrera cíclica Se pide implementar un programa concurrente con las siguientes características: • El programa tendrá 4 procesos que escriben una única letra indefinidamente. Un proceso escribirá la letra A, otra la B, otra la C y otro la D. • Cada proceso escribe su letra y espera a que los demás hayan escrito también su letra para poder continuar. • Uno de los procesos tiene que escribir un guión – después de que todos hayan escrito su letra y antes de que empiecen a escribirlas de nuevo. • La implementación se realizará con semáforos. La salida por pantalla del programa con 4 procesos sería: ACDB-BACD-DCBA-ABCD-BCAD … 1ª Aproximación Incorrecta La primera aproximación se basa en usar una sincronización de barrera como la que hemos visto en el ejercicio 14. package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer15_SincBarreraCicl_Mal { private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore emNProcesos; public static void procesoA() { while(true){ print("A"); sincronizacion(); } } public static void procesoB() { while(true){ print("B"); sincronizacion(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 8 public static void procesoC() { while(true){ print("C"); sincronizacion(); } } public static void procesoD() { while(true){ print("D"); sincronizacion(); } } public static void sincronizacion(){ emNProcesos.acquire(); nProcesos++; if (nProcesos < 4) { emNProcesos.release(); sb.acquire(); } else { println("-"); nProcesos = 0; emNProcesos.release(); for (int i = 0; i < 3; i++) { //sleepRandom(500); Simular condiciones de carrera sb.release(); } } } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThread("procesoA"); createThread("procesoB"); createThread("procesoC"); createThread("procesoD"); startThreadsAndWait(); } } El problema que tiene esta solución es que es posible que un hilo recién despertado muestre su letra y le de tiempo a invocar sb.acquiere() antes de que el hilo despertador haya despertado al resto de sus compañeros. En ese caso, como el semáforo es aleatorio, es posible que ese hilo que ya ha hecho su trabajo pueda desbloquearse de nuevo, volviendo a realizar su trabajo. Si descomentamos la línea con el sleepRandom(500) veremos que la salida será similar a esta: CDAB- DDDB- DDBD- BBDB- DDBD- Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 9 2ª Aproximación incorrecta: Para solventar los problemas con la aproximación anterior, podríamos pensar en que la exclusión mutua del contador de procesos no se libere hasta que el proceso “despertador” haya despertado a todos los demás, con eso evitaríamos que un proceso recién despertado pueda invocar sb.acquiere() antes de que todos sus compañeros se hayan despertado. Esta aproximación sería la siguiente: package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer15_SincBarreraCicl_Mal2 { private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore emNProcesos; public static void procesoA() { while(true){ print("A"); sincronizacion(); } } public static void procesoB() { while(true){ print("B"); sincronizacion(); } } public static void procesoC() { while(true){ print("C"); sincronizacion(); } } public static void procesoD() { while(true){ print("D"); sincronizacion(); } } public static void sincronizacion(){ emNProcesos.acquire(); nProcesos++; if (nProcesos < 4) { emNProcesos.release(); //sleepRandom(500); Simular condiciones de carrera sb.acquire(); } else { println("-"); nProcesos = 0; for (int i = 0; i < 3; i++) { sb.release(); } emNProcesos.release(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 10 public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThread("procesoA"); createThread("procesoB"); createThread("procesoC"); createThread("procesoD"); startThreadsAndWait(); } } Pero esta aproximación tampoco es correcta. El problema está en que desde que un proceso se da cuenta de que no es el último (nProcesos < 4) e invoca sb.acquiere() puede pasar mucho tiempo. Y en ese tiempo, otro proceso puede haberse bloqueado, despertado y vuelto a bloquear. Este comportamiento se puede similar descomentando la sentencia con sleepRandom(500) y la salida será similar a esta: CABD- BCDB- BAAC- DCDA- BCBA- Es muy difícil darse cuenta de que estas soluciones son incorrectas porque si no se intercalan las sentencias de sleep en la mayoría de los casos los programas funcionan correctamente. Pero en los casos extremos (que forzamos con los sleep) los programas se comportan de forma errónea. En los programas en producción, estas situaciones anómalas se suelen producir cuando el sistema está muy cargado, por lo que son muy difíciles de resolver. Solución: Una de las posibles soluciones consiste en que los hilos que son desbloqueados tengan que notificar al hilo despertador que realmente se han desbloqueado y que han salido de la barrera. Cuando el hilo despertador recibe todas las notificaciones, entonces libera la exclusión mutua de la barrera para comenzar otro ciclo. Para implementar la solución se ha usado un método de la clase SimpleSemaphore que permite hacer release de varios permisos en una única llamada: package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer15_SincBarreraCicl { private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore desbloqueo; private static SimpleSemaphore emNProcesos; public static void procesoA() { while (true) { print("A"); sincronizacion(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 11 public static void procesoB() { while (true) { print("B"); sincronizacion(); } } public static void procesoC() { while (true) { print("C"); sincronizacion(); } } public static void procesoD() { while (true) { print("D"); sincronizacion(); } } public static void sincronizacion() { emNProcesos.acquire(); nProcesos++; if (nProcesos < 4) { emNProcesos.release(); sleepRandom(500); // Simular condiciones de carrera sb.acquire(); desbloqueo.release(); } else { println("-"); nProcesos = 0; sb.release(3); desbloqueo.acquire(3); emNProcesos.release(); } } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); desbloqueo = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThread("procesoA"); createThread("procesoB"); createThread("procesoC"); createThread("procesoD"); startThreadsAndWait(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 12 Ejercicio 16. Descarga de múltiples ficheros Se desea ampliar el ejercicio de descarga de ficheros (Ejercicio 9). Se requiere que cuando los procesos hayan terminado de descargar un fichero, se esperen a que se muestre por pantalla y comiencen a descargar un nuevo fichero, así hasta descargar 10 ficheros. La implementación deberá realizarse íntegramente con semáforos. Solución: La solución se consigue implementando una sincronización de barrera cuando se ha descargado un fichero completo, de forma que el resto de hilos se esperan para volver a descargar un nuevo fichero. Para ello, basta con modificar el método “downloader()” de la solución al Ejercicio 9 para descargar 10 ficheros en vez de 1, reinicializando todas las variables cuando todos los hilos han finalizado su trabajo de descarga. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer16_Downloader { private static final int N_FRAGMENTOS = 10; private static final int N_HILOS = 3; private static volatile int[] fichero = new int[N_FRAGMENTOS]; private static volatile int fragPendiente = 0; private static volatile int hilosTerminados = 0; private static SimpleSemaphore emFragPendiente; private static SimpleSemaphore emHilosTerminados; private static SimpleSemaphore sb; private static SimpleSemaphore desbloqueo; private static int descargarDatos(int numFragment) { sleepRandom(1000); return numFragment * 2; } private static void mostrarFichero() { println("--------------------------------------------------"); print("File = ["); for (int i = 0; i < N_FRAGMENTOS; i++) { print(fichero[i] + ","); } println("]"); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 13 public static void downloader() { for (int i = 0; i < 10; i++) { descargarFragmentos(); emHilosTerminados.acquire(); hilosTerminados++; if (hilosTerminados < N_HILOS) { emHilosTerminados.release(); sb.acquire(); desbloqueo.release(); } else { mostrarFichero(); fragPendiente = 0; hilosTerminados = 0; sb.release(N_HILOS-1); desbloqueo.acquire(N_HILOS-1); emHilosTerminados.release(); } } } private static void descargarFragmentos() { while (true) { emFragPendiente.acquire(); if (fragPendiente == N_FRAGMENTOS) { emFragPendiente.release(); break; } int fragDescargar = fragPendiente; fragPendiente++; emFragPendiente.release(); println(getThreadName() + ": Descargando fragmento " + fragDescargar); int downloadedData = descargarDatos(fragDescargar); println(getThreadName() + ": Escribiendo fragmento " + fragDescargar); fichero[fragDescargar] = downloadedData; } } public static void main(String[] args) { emFragPendiente = new SimpleSemaphore(1); emHilosTerminados = new SimpleSemaphore(1); sb = new SimpleSemaphore(0); desbloqueo = new SimpleSemaphore(0); createThreads(N_HILOS, "downloader"); startThreadsAndWait(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 14 Ejercicio 17. Filósofos Comilones Se pide implementar un programa con semáforos con las siguientes características: • 5 Filósofos que piensan libremente y comen en una mesa con 5 platos • Cada filósofo tiene un plato asignado para él sólo • Hay 5 tenedores, uno entre cada par de platos adyacentes Cada uno de los 5 filósofos está representado por un proceso. Los filósofos tienen el siguiente ciclo de vida: • El filósofo inicialmente piensa. • Se sienta delante de su plato y toma de uno en uno los tenedores situados a ambos lados de su plato. • Come. • Cuando finaliza, deja los dos tenedores en su posición original. • Todo filósofo que come, en algún momento se harta y termina. • Vuelve a empezar. Se pide implementar un programa concurrente en Java con SimpleConcurrent que simule la vida de los filósofos. Para ello, se deben cumplir las siguientes restricciones: • Un filósofo sólo puede comer cuando tenga los dos tenedores. • Los tenedores se cogen y se dejan de uno en uno. • Dos filósofos no pueden tener el mismo tenedor simultáneamente (Exclusión Mutua de acceso al tenedor). • Si varios filósofos tratan de comer al mismo tiempo, uno de ellos debe conseguirlo (Ausencia Interbloqueo). • Si un filósofo desea comer y tiene competencia, en algún momento lo deberá poder hacerlo (Ausencia de Inanición). • En ausencia de competencia, un filósofo que quiera comer deberá hacerlo sin retrasos innecesarios (Ausencia retrasos innecesarios). Se propone el siguiente esquema de código para la resolución del ejercicio: F3 F2 F1F5 F4 T4 T3 T2 T1 T5 Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 15 package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer17_Filosofos_Plantilla { public static final int N_FILOSOFOS = 5; public static void filosofo(int numFilosofo) { while (true) { printlnI("Pensar"); // Obtener tenedores printlnI("Comer"); // Liberar tenedores } } public static void main(String[] args) { for (int i = 0; i < N_FILOSOFOS; i++) { createThread("filosofo", i); } startThreadsAndWait(); } } 1º Aproximación Incorrecta: La siguiente puede ser la primera aproximación más evidente, pero no es correcta. Esta aproximación propone un semáforo por cada tenedor y pone cada tenedor bajo exclusión mutua: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer17_Filosofos_Mal { public static final int N_FILOSOFOS = 5; public static SimpleSemaphore[] tenedores = new SimpleSemaphore[N_FILOSOFOS]; public static void filosofo(int numFilosofo) { while(true){ printlnI("Pensar"); int tIzq = numFilosofo; int tDer = (numFilosofo+1) % N_FILOSOFOS; tenedores[tIzq].acquire(); sleepRandom(500); //Simular interbloqueo tenedores[tDer].acquire(); printlnI("Comer"); tenedores[tIzq].release(); tenedores[tDer].release(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 16 public static void main(String[] args) { for (int i = 0; i < N_FILOSOFOS; i++) { tenedores[i] = new SimpleSemaphore(1); } for (int i = 0; i < N_FILOSOFOS; i++) { createThread("filosofo", i); } startThreadsAndWait(); } } Esta solución no es correcta porque si todos los filósofos avanzan a la vez, cogen el tenedor de su izquierda y se bloquean esperando el de su derecha (interbloqueo). Solución 1: Para evitar los problemas de esta solución, podemos considerar que existe un “comedor” en el que caben todos los filósofos menos 1. De esta forma, nunca se producirá interbloqueo entre los filósofos porque al menos uno de ellos podrá coger los dos tenedores. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer17_Filosofos_1 { public static final int N_FILOSOFOS = 5; public static SimpleSemaphore[] tenedores = new SimpleSemaphore[N_FILOSOFOS]; public static SimpleSemaphore comedor; public static void filosofo(int numFilosofo) { while (true) { printlnI("Pensar"); int tIzq = numFilosofo; int tDer = (numFilosofo + 1) % N_FILOSOFOS; comedor.acquire(); tenedores[tIzq].acquire(); tenedores[tDer].acquire(); printlnI("Comer"); tenedores[tIzq].release(); tenedores[tDer].release(); comedor.release(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 17 public static void main(String[] args) { comedor = new SimpleSemaphore(N_FILOSOFOS - 1); for (int i = 0; i < N_FILOSOFOS; i++) { tenedores[i] = new SimpleSemaphore(1); } for (int i = 0; i < N_FILOSOFOS; i++) { createThread("filosofo", i); } startThreadsAndWait(); } } Solución 2: Otra posible solución consiste en que uno de los filósofos sea zurdo y empiece cogiendo los tenedores por la derecha en vez de por la izquierda. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer17_Filosofos_2 { public static final int N_FILOSOFOS = 5; public static SimpleSemaphore[] tenedores = new SimpleSemaphore[N_FILOSOFOS]; public static SimpleSemaphore comedor; public static void filosofo(int numFilosofo, boolean diestro) { while (true) { printlnI("Pensar"); int tIzq = numFilosofo; int tDer = (numFilosofo + 1) % N_FILOSOFOS; if (diestro) { tenedores[tIzq].acquire(); tenedores[tDer].acquire(); } else { tenedores[tDer].acquire(); tenedores[tIzq].acquire(); } printlnI("Comer"); tenedores[tIzq].release(); tenedores[tDer].release(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 18 public static void main(String[] args) { for (int i = 0; i < N_FILOSOFOS; i++) { tenedores[i] = new SimpleSemaphore(1); } for (int i = 0; i < N_FILOSOFOS - 1; i++) { createThread("filosofo", i, true); } createThread("filosofo", N_FILOSOFOS - 1, false); startThreadsAndWait(); } } Ejercicio 18. Preguntas cortas sobre semáforos 18.1) Describa la semántica de los métodos acquire() y release() aplicadas sobre semáforos generales. s.acquire() – Si el número de permisos del semáforo s es igual a cero el proceso se bloquea en el semáforo s; si no, se decrementa en una unidad el contador de permisos del semáforo s y el proceso prosigue su ejecución. s.release () – Si hay procesos bloqueados en el semáforo s se desbloquea a uno de ellos; si no, se incrementa en una unidad el contador de permisos del semáforo s. En ambos casos, el proceso prosigue su ejecución. Ambos métodos son, por definición, instrucciones atómicas. 18.2) ¿Cómo se resuelve el acceso exclusivo de un conjunto de procesos a una variable compartida mediante semáforos? Con semáforos se asocia un semáforo a esa variable, y se inicializa con valor 1. Cuando un proceso debe acceder a la variable, primero ejecuta una operación acquire() sobre el semáforo para obtener el acceso exclusivo sobre ella; después consulta y/o modifica su valor; y finalmente ejecuta una operación release() sobre el semáforo para liberarla. 18.3) Complete la siguiente tabla cuando el proceso P ejecuta la operación de la columna izquierda sobre el semáforo s. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 19 Operaciones sobre el Semáforo S Contador de Permisos Antes Después Lista de Procesos Bloquedados Antes Después s.acquire() 3 Vacía s.acquire() 0 P1 s.release() 1 Vacía s.release() 0 Vacía s.release() 0 P1, P2 s.acquire() 0 Vacía s.acquire() 0 P1 s.acquire() 1 Vacía s.release() 0 Vacía s.release() 0 P1 s.release() 1 Vacía Solución: Operaciones sobre el Semáforo s Contador de Permisos Antes Después Lista de Procesos Bloquedados Antes Después s.acquire() 3 2 Vacía Vacía s.acquire() 0 0 P1 P1, P s.release() 1 2 Vacía Vacía s.release() 0 1 Vacía Vacía s.release() 0 0 P1, P2 P1 o P2 s.acquire() 0 0 Vacía P s.acquire() 0 0 P1 P1 y P s.acquire() 1 0 Vacía Vacía s.release() 0 1 Vacía Vacía s.release() 0 0 P1 Vacía s.release() 1 2 Vacía Vacía 18.4) ¿Cuántos semáforos se precisan para implantar un buffer de tamaño ilimitado? Razone la respuesta. 2 semaforos. Uno para la exclusión mutua sobre el buffer de datos y otro para sincronizar a los consumidores cuando no hay datos en el buffer. 18.5) Con qué valores se debe inicializar un semáforo para: a) resolver la sincronización condicional de dos procesos? b) resolver la exclusión mutua en el acceso a un recurso de N procesos? c) resolver la exclusión mutua generalizada de N procesos a un recurso que soporta K accesos simultáneos? Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 20 a) 0 b) 1 c) K 18.6) ¿Por qué las sentencias s.acquire() y s.release() garantizan la exclusión mutua en el acceso al contador de permisos del semáforo s? Porque, por definición, son instrucciones atómicas 18.7) Escribe el código correspondiente al método puntoSincronización del siguiente programa utilizando semáforos, para evitar que alguno de los N procesos comience a ejecutar el procedimiento B antes de que alguno de los demás haya finalizado la ejecución del procedimiento A. Incorpora los atributos e inicializaciones necesarios. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_7_Enun { private static final int NPROCESOS = 3; public static void proceso() { print("A"); puntoSincronizacion(); print("B"); } private static void puntoSincronizacion() { } public static void main(String[] args) { createThreads(NPROCESOS, "proceso"); startThreadsAndWait(); } } Solución: package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_7 { private static final int NPROCESOS = 3; private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore emNProcesos; public static void proceso() { print("A"); puntoSincronizacion(); print("B"); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 21 private static void puntoSincronizacion() { emNProcesos.acquire(); nProcesos++; if (nProcesos == NPROCESOS) { for (int i = 0; i < NPROCESOS; i++) { sb.release(); } } emNProcesos.release(); sb.acquire(); } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThreads(NPROCESOS, "proceso"); startThreadsAndWait(); } } 18.8) En un semáforo cuyo contador de permisos es mayor que cero, ¿puede haber algún proceso bloqueado? Justifique su respuesta NO. Sólo puede haber procesos bloqueados en el semáforo cuando el contador vale 0. 18.9) Dado el siguiente diagrama de precedencia de tres procesos: Escriba en Java con SimpleConcurrent usando Semáforos un programa para cumplir el anterior diagrama de precedencia. Solución: package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_9 { private static SimpleSemaphore semA; private static SimpleSemaphore semC; private static SimpleSemaphore semD; A B C D P1 P2 P3 Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 22 public static void proceso1() { semA.acquire(); print("A"); semC.release(); } public static void proceso2() { print("B"); semA.release(); semD.release(); semC.acquire(); semC.acquire(); print("C"); } public static void proceso3() { semD.acquire(); print("D"); semC.release(); } public static void main(String[] args) { semA = new SimpleSemaphore(0); semC = new SimpleSemaphore(0); semD = new SimpleSemaphore(0); createThread("proceso1"); createThread("proceso2"); createThread("proceso3"); startThreadsAndWait(); } } 18.10) Describe exactamente todas las situaciones que podrían ocurrir en el siguiente programa. Si adviertes algún error, escribe una solución que garantice que a x se accede bajo exclusión mutua y que P2 nunca termina hasta que ambos procesos hayan incrementado la variable x. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer18_10_Enun { private static int x; private static SimpleSemaphore sincronizacion; private static SimpleSemaphore exclusion; public static void p1() { exclusion.acquire(); x++; if (x == 2) { sincronizacion.release(); } exclusion.release(); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 23 public static void p2() { exclusion.acquire(); x++; if (x == 1) { sincronizacion.acquire(); } exclusion.release(); } public static void main(String[] args) { x = 0; sincronizacion = new SimpleSemaphore(0); exclusion = new SimpleSemaphore(1); createThread("p1"); createThread("p2"); startThreadsAndWait(); } } Solución: a) Si P1 entra primero en su sección crítica, incrementa x y termina. Luego entra P2, incrementa x y termina. b) Si P2 entra primero, entonces incrementa x, ejecuta sincronizacion.acquire() y se bloquea. Entonces P1 nunca podrá entrar en su sección crítica. Por tanto, se produce un interbloqueo de P1 y P2. La solución consiste en que P2 se bloquee fuera de su sección crítica, por ejemplo: public static void p2() { exclusion.acquire(); x++; if (x == 1) { exclusion.release(); sincronizacion.acquire(); } else { exclusion.release(); } } Aunque otra solución más sencilla para cumplir con los requisitos del programa puede ser la siguiente: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer18_10_2 { private static int x; private static SimpleSemaphore sincronizacion; private static SimpleSemaphore exclusion; public static void p1() { exclusion.acquire(); x++; exclusion.release(); sincronizacion.release(); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 24 public static void p2() { exclusion.acquire(); x++; exclusion.release(); sincronizacion.acquire(); } public static void main(String[] args) { x = 0; sincronizacion = new SimpleSemaphore(0); exclusion = new SimpleSemaphore(1); createThread("p1"); createThread("p2"); startThreadsAndWait(); } } 18.11) ¿Hay algún error en el siguiente programa? package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_11_Enun { private static int x; private static int y; private static SimpleSemaphore sY; private static SimpleSemaphore sX; public static void pA() { sX.acquire(); x++; sY.acquire(); y *= x; sY.release(); sX.release(); } public static void pB() { sY.acquire(); y++; sX.acquire(); x *= y; sX.release(); sY.release(); } public static void main(String[] args) { x = 2; y = 3; sX = new SimpleSemaphore(1); sY = new SimpleSemaphore(1); createThread("pA"); createThread("pB"); startThreadsAndWait(); } } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 25 Si el proceso pA ejecuta su primer acquire(), y antes de que ejecute su segundo acquire(), el proceso B ejecuta su primer acquire() (o viceversa) se produce un interbloqueo entre ambos. 4.12) Escribe en Java con SimpleConcurrent el programa que cumpla el siguiente diagrama de precedencia de procesos: Solución: package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_12 { private static SimpleSemaphore semD; private static SimpleSemaphore semF; public static void proceso1() { print("A"); semD.release(); print("B"); } public static void proceso2() { print("C"); semF.release(); semD.acquire(); print("D"); } public static void proceso3() { print("E"); semF.acquire(); semF.acquire(); print("F"); } public static void main(String[] args) { semD = new SimpleSemaphore(0); semF = new SimpleSemaphore(0); createThread("proceso1"); createThread("proceso2"); createThread("proceso3"); startThreadsAndWait(); } A C D F P1 P2 P3 B E Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 26 } 18.13) El siguiente código trata de implantar una sincronización de barrera para un número de procesos determinado por la constante N. package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_13_Enun { private static final int N = 3; private static volatile int nProcesos; private static SimpleSemaphore sb; private static SimpleSemaphore emNProcesos; public static void proceso() { while (true) { print("A"); puntoSincronizacion(); print("B"); } } private static void puntoSincronizacion() { emNProcesos.acquire(); nProcesos++; if (nProcesos == N) { nProcesos = 0; emNProcesos.release(); //I0 for (int i = 0; i < N; i++) { sb.release(); //I1 } } else { emNProcesos.release(); sb.acquire(); } } public static void main(String[] args) { nProcesos = 0; sb = new SimpleSemaphore(0); emNProcesos = new SimpleSemaphore(1); createThreads(N, "proceso"); startThreadsAndWait(); } } Explica brevemente por qué no se sincronizan adecuadamente los procesos en la sincronización de barrera en este ejemplo. Observa las instrucciones etiquetadas con I0 e I1. El último proceso que llega al punto de sincronización hace N release(), cuando sólo hay N-1 procesos bloqueados en el semáforo, por lo que en la siguiente iteración el primer proceso que llegue al punto de sincronización se encuentra el contador del semáforo con valor 1 y no se bloquea esperando a que llegue el último. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 27 Pero además hay otro problema: la liberación de la exclusión mutua en la instrucción I0 permite que un proceso que se desbloquee como resultado de un release() en I1 se adelante a los demás, y antes de que se puedan desbloquear todos, entre de nuevo en la sección crítica, invoque acquire(), y se desbloquee como resultado del siguiente adquire() en I1. Lo cual puede producir la inanición de aquellos procesos que no pueden desbloquearse porque el proceso que se ha adelantado se desbloquea en su lugar. 18.14) Dados los dos siguientes procesos concurrentes: public static void p1(){ sX.acquire(); sY.acquire(); x += y; sY.release(); sX.release(); } public static void p2(){ sY.acquire(); sX.acquire(); y += x; sX.release(); sY.release(); } Si inicialmente sX y sY tienen el contador de permisos a valor 1, y X e Y tienen valor 1, describe todas las situaciones que podrían ocurrir. Si adviertes algún error, corrígelo. a) Si P1 ejecuta primero sus dos acquire(), asigna 2 a X. Cuando P1 ejecute sus dos release(), entonces P2 podrá asignar 3 a Y, y los dos procesos terminarán correctamente. b) Si P2 ejecuta primero sus dos acquire(), asigna 2 a Y. Cuando P2 ejecute sus dos release(), entonces P2 podrá asignar 3 a X, y los dos procesos terminarán correctamente. c) Si P1 o P2 ejecuta su primer acquire() y después el otro proceso ejecuta el suyo, hay interbloqueo de los dos procesos al ejecutar sus segundos acquire(). La solución consiste en intercambiar el orden de las instrucciones acquire() en uno de los procesos. 18.15) Sea el siguiente programa en el que N procesos compiten por utilizar un cierto recurso, de manera que como máximo K de estos procesos puedan usar el recurso concurrentemente. ¿Cómo se denomina este problema clásico? Escriba en Java con SimpleConcurrent una solución a este problema usando semáforos. package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer18_15_Enun { private static final int N_PROCESOS = 4; private static final int K = 2; public static void p() { while(true){ // Obtener recurso // Usar recurso // Liberar recurso Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 28 // Hacer otras cosas } } public static void main(String[] args) { createThreads(N_PROCESOS,"p"); startThreadsAndWait(); } } Solución: Este problema se denomina exclusión mutua generalizada. Una solución usando semáforos es la siguiente: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer18_15 { private static final int N_PROCESOS = 4; private static final int K = 2; private static SimpleSemaphore emGen; public static void p() { while(true){ emGen.acquire(); // Usar recurso emGen.release(); // Hacer otras cosas } } public static void main(String[] args) { emGen = new SimpleSemaphore(K); createThreads(N_PROCESOS,"p"); startThreadsAndWait(); } } 18.16) Dibuje el diagrama de precedencia del siguiente programa. package exercises.semaphores; import static simpleconcurrent.SimpleConcurrent.*; import simpleconcurrent.SimpleSemaphore; public class Ejer4_16_Enun { private static SimpleSemaphore[] sinc = new SimpleSemaphore[2]; Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 29 public static void puntoSinc(int numProc) { if (numProc == 0) { sinc[0].release(); sinc[1].release(); } else { if (numProc == 1) { sinc[1].release(); sinc[0].acquire(); } else { sinc[1].acquire(); sinc[1].acquire(); } } } public static void proceso(int numProc) { print("A"+numProc+" "); puntoSinc(numProc); print("B"+numProc+" "); } public static void main(String[] args) { sinc[0] = new SimpleSemaphore(0); sinc[1] = new SimpleSemaphore(0); for (int i = 0; i < 3; i++) { createThread("proceso", i); } startThreadsAndWait(); } } 18.17) Dados el siguiente programa concurrente: package ejercicio; import es.sidelab.sc.SimpleSemaphore; import static es.sidelab.sc.SimpleConcurrent.*; A1 A2 A3 B1 B2 B3 P1 P2 P3 A1 A2 A3 A1 A2 A3 B1 B2 B3 B1 B2 B3 P1 P2 P3 P1 P2 P3 Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 30 public class Ejer18_17_Enun { private static SimpleSemaphore s; public static void p1(){ print("A"); s.release(); } public static void p2(){ s.acquire(); print("B"); s.release(); s.release(); } public static void p3(){ s.acquire(); s.acquire(); print("C"); } public static void main(String[] args){ s = new SimpleSemaphore(0); createThread("p1"); createThread("p2"); createThread("p3"); startThreadsAndWait(); } } ¿Se puede asegurar que siempre la ejecución de A precede a la de B y la de B precede a la de C? No. Si primero se ejecuta la primera instrucción de P3, éste queda bloqueado en el semáforo S; si luego se ejecutan todas las instrucciones de P1, P3 queda en estado de preparado; si se ejecuta la segunda instrucción de P3, éste queda de nuevo bloqueado en el semáforo S; el único proceso en estado de preparado será P2, y quedará también bloqueado en cuanto ejecute su primera instrucción. Por tanto, se produce interbloqueo y no se puede asegurar que B precede a C. Ejercicio 19. Lectores-Escritores Se desea implementar un programa concurrente con semáforos con procesos lectores y procesos escritores. En este ejercicio vamos a un ver un ejemplo típico de acceso combinado (concurrente y exclusivo) a variables compartidas. Este tipo de acceso se tiene en los sistemas gestores de bases de datos. Los procesos lectores pueden leer información de la base de datos y los procesos escritores pueden escribir información en ella. Para simplificar la resolución del ejercicio, las operaciones de los procesos lectores y escritores se implementarán como escrituras por pantalla. Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 31 La solución se basará en el siguiente esquema: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; public class Ejer19_LectoresEscritores_Plantilla { public static void inicioLectura() { } public static void finLectura() { } public static void inicioEscritura() { } public static void finEscritura() { } public static void lector() { while(true){ inicioLectura(); println("Leer datos"); finLectura(); println("Procesar datos"); } } public static void escritor() { while (true) { println("Generar datos"); inicioEscritura(); println("Escribir datos"); finEscritura(); } } public static void main(String[] args) { createThreads(5, "lector"); createThreads(3, "escritor"); startThreadsAndWait(); } } Se pide implementar los métodos inicioLectura(), finLectura(), inicioEscritura(), finEscritura() de forma que se cumplan las siguientes propiedades: • Cualquier número de lectores puede acceder a la vez a la BD, siempre que no haya escritores accediendo. B.D Escritores Lectores Leer Escribir Leer Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 32 • El acceso a la BD de los escritores es exclusivo. Mientras haya algún lector leyendo, ningún escritor puede acceder a la BD, pero otros lectores sí. • Se puede tener varios escritores trabajando, aunque estos se deberán sincronizar para que la escritura se lleve a cabo de uno en uno. • Se da prioridad a los escritores. Ningún lector puede acceder a la BD cuando haya escritores que desean hacerlo (aunque esto pueda causar inanición de Lectores). Solución: Para implementar una solución, nos basamos en los siguientes principios de funcionamiento: • Cuando un lector intenta entrar en la BD, primero mira para ver si hay algún escritor en ella (trabajando) o algún escritor a la espera de entrar. Si no hay ningún escritor trabajando o a la espera, entonces entra el lector. En caso contrario, se bloquea a la espera de que terminen los escritores. • Cuando un escritor intenta entrar en la BD, sólo mira por si hay un lector en ella (trabajando), pero no mira si hay algún lector a la espera, porque los escritores tienen prioridad sobre los lectores. Si no hay ningún lector trabajando, entonces entra el escritor. En caso contrario, se bloque a la espera de que terminen los escritores. • Cuando un lector finaliza el acceso a la base de datos y es el último lector trabajando, desbloquea a los escritores que estuvieran esperando. • Cuando un escritor finaliza el acceso a la base de datos y es el último escritor trabajando, desbloquea a los lectores que estuvieran esperando. Con estos principios básicos de funcionamiento, la solución sería: package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer19_LectoresEscritores_1 { // Número de escritores que han conseguido el acceso a la BD private static int escritoresBD = 0; // Número de lectores que han conseguido el acceso a la BD private static int lectoresBD = 0; // Lectores esperando a que finalicen los escritores private static int lectoresEspera; // Escritores esperando a que finalicen los lectores private static int escritoresEspera; // Exclusión mutura de las variables de control private static SimpleSemaphore emControl; // Exclusión mutura para el acceso a los escritores private static SimpleSemaphore emEscritura; // Bloqueo de los lectores cuando hay escritores private static SimpleSemaphore esperaFinEscritores; Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 33 // Bloqueo de los escritores cuando hay lectores private static SimpleSemaphore esperaFinLectores; public static void inicioLectura() { emControl.acquire(); printlnI("inicioLectura"); if (escritoresBD == 0 && escritoresEspera == 0) { lectoresBD++; printlnI("lectTrab="+lectoresBD); emControl.release(); } else { lectoresEspera++; printlnI("lectEspera="+lectoresEspera); emControl.release(); esperaFinEscritores.acquire(); } } public static void finLectura() { emControl.acquire(); lectoresBD--; printlnI("finLectura"); printlnI("lectTrab="+lectoresBD); if (lectoresBD == 0) { printlnI("escriEspera="+escritoresEspera); for(int i=0; i<escritoresEspera; i++) { escritoresBD++; esperaFinLectores.release(); } escritoresEspera = 0; } emControl.release(); } public static void inicioEscritura() { emControl.acquire(); printlnI("inicioEscritura"); if (lectoresBD == 0) { escritoresBD++; printlnI("escTrab="+escritoresBD); emControl.release(); } else { escritoresEspera++; printlnI("escEspera="+escritoresEspera); emControl.release(); esperaFinLectores.acquire(); } emEscritura.acquire(); printlnI("Escribiendo..."); } Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 34 public static void finEscritura() { printlnI("FinEscribiendo..."); emEscritura.release(); emControl.acquire(); escritoresBD--; printlnI("finEscritura"); printlnI("escriTrab="+escritoresBD); if (escritoresBD == 0) { printlnI("lectEspera="+lectoresEspera); for(int i=0; i<lectoresEspera; i++){ lectoresBD++; esperaFinEscritores.release(); } lectoresEspera=0; } emControl.release(); } public static void lector() { while (true) { inicioLectura(); printlnI("Leer datos"); sleepRandom(300); finLectura(); printlnI("Procesar datos"); sleepRandom(500); } } public static void escritor() { while (true) { printlnI("Generar datos"); sleepRandom(2000); inicioEscritura(); printlnI("Escribir datos"); sleepRandom(500); finEscritura(); } } public static void main(String[] args) { emControl = new SimpleSemaphore(1); emEscritura = new SimpleSemaphore(1); esperaFinEscritores = new SimpleSemaphore(0); esperaFinLectores = new SimpleSemaphore(0); createThreads(5, "lector"); createThreads(3, "escritor"); startThreadsAndWait(); } } No obstante, hay otras soluciones posibles. En este caso se presenta otra solución que utiliza el concepto de procesos activos y procesos trabajando para representar de otra forma los procesos que quieren entrar en la base de datos frente a los que realmente han entrado. El código sería el siguiente: Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 35 package ejercicio; import static es.sidelab.sc.SimpleConcurrent.*; import es.sidelab.sc.SimpleSemaphore; public class Ejer19_LectoresEscritores_2 { // Número de escritores que han iniciado inicioEscritura() // Están activos hasta que finalizan finEscritura() private static int escritoresActivos = 0; // Número de escritores que han conseguido el acceso // a la BD (Han terminado inicioEscritura()) // Están trabajando hasta que finalizan finEscritura() // Siempre se cumple escritoresActivos >= escritoresTrabajando private static int escritoresBD = 0; // Número de lectores que han iniciado inicioLectura() // Están activos hasta que finalizan finLectura() // Un lector activo puede estar esperando a obtener el // acceso a los datos, que se le concederá cuando no // haya escritores activos private static int lectoresActivos = 0; // Número de lectores que han conseguido el acceso a // la BD (Han terminado inicioLectura()) // Están trabajando hasta que finalizan finLectura() // Siempre se cumple lectoresActivos >= lectoresTrabajando private static int lectoresBD = 0; // Exclusión mutura de las variables de control private static SimpleSemaphore emControl; // Exclusión mutura para el acceso a los escritores private static SimpleSemaphore emEscritura; // Bloqueo de los lectores cuando hay escritores private static SimpleSemaphore esperaFinEscritores; // Bloqueo de los escritores cuando hay lectores private static SimpleSemaphore esperaFinLectores; public static void inicioLectura() { emControl.acquire(); lectoresActivos++; if (escritoresBD == 0 && escritoresActivos == 0) { lectoresBD++; emControl.release(); } else { emControl.release(); esperaFinEscritores.acquire(); } } public static void finLectura() { emControl.acquire(); Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 36 lectoresActivos--; lectoresBD--; if (lectoresBD == 0) { while (escritoresActivos > escritoresBD) { escritoresBD++; esperaFinLectores.release(); } } emControl.release(); } public static void inicioEscritura() { emControl.acquire(); escritoresActivos++; if (lectoresBD == 0) { escritoresBD++; emControl.release(); } else { emControl.release(); esperaFinLectores.acquire(); } emEscritura.acquire(); } public static void finEscritura() { emEscritura.release(); emControl.acquire(); escritoresActivos--; escritoresBD--; if (escritoresBD == 0) { while (lectoresActivos > lectoresBD) { lectoresBD++; esperaFinEscritores.release(); } } emControl.release(); } public static void lector() { while (true) { inicioLectura(); println("Leer datos"); finLectura(); println("Procesar datos"); } } public static void escritor() { while (true) { println("Generar datos"); inicioEscritura(); println("Escribir datos"); finEscritura(); } } public static void main(String[] args) { Programación Concurrente 3º Grado en Ingeniería de Computadores Concurrencia con Memoria Compartida 37 emControl = new SimpleSemaphore(1); emEscritura = new SimpleSemaphore(1); esperaFinEscritores = new SimpleSemaphore(0); esperaFinLectores = new SimpleSemaphore(0); createThreads(5, "lector"); createThreads(3, "escritor"); startThreadsAndWait(); } } __MACOSX/Academia/Tema2/._Tema2.2 - Ejercicios (soluciones).pdf Academia/Tema5/Tema 5.4 - Concurrencia y orientación a objetos UPDATED.pdf Programación Concurrente en Java Programación Concurrente – Tema 5 Miguel Ángel Rodríguez García Carlos Grima Lucía Serrano Luján Universidad Rey Juan Carlos Curso 2017 -‐ 2018 Concurrencia y orientación a objetos Programación Concurrente en Java -‐ Tema 5.4 3 PROGRAMACIÓN CONCURRENTE EN JAVA Datos compartidos entre hilos Objetos compartidos entre hilos 4 PROGRAMACIÓN CONCURRENTE EN JAVA Datos compartidos entre hilos Objetos compartidos entre hilos 5 PROGRAMACIÓN CONCURRENTE EN JAVA CPU 1 Thread 1 CPU 1cache CUANDO CREAMOS UN HILO…. Datos compartidos entre hilos Datos compartidos entre hilos 6 PROGRAMACIÓN CONCURRENTE EN JAVA CPU 1 Thread 1 CPU 1cache CUANDO CREAMOS MÁS HILOS…. CPU 2 Thread 2 CPU 2cache Datos compartidos entre hilos 7 PROGRAMACIÓN CONCURRENTE EN JAVA CPU 1 Thread 1 CPU 1cache CUANDO CREAMOS MÁS HILOS…. CPU 2 Thread 2 CPU 2cache Memoria principal Datos compartidos entre hilos • Dos o más hilos tienen acceso a una variable: public class objetoCompartido{ public int contador = 0; } 8 PROGRAMACIÓN CONCURRENTE EN JAVA CPU 1 Thread 1 contador = 7 contador = 0 cache cacheCPU 2 Thread 2 contador = 0 cache Datos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA • Los procesadores utilizan memoria cache y registros para almacenar los valores de las variables •En sistemas de varios procesadores (o núcleos), es habitual que exista una cache por cada procesador •¿Qué ocurre con las variables compartidas si dos procesos se ejecutan cada uno en un procesador? •¿Cuándo se sincronizan las cachés? •¿Cuándo son visibles los cambios de las variables por todos los hilos? Datos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA •Para un compilador es imposible determinar qué atributos se comparten entre hilos y cuales no •No es eficiente sincronizar los valores de todos los atributos cada vez que se hace una escritura en ellos por si otro procesador los usa •Existe un problema de visibilidad •¿Cómo se resuelve el problema? Datos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA •El desarrollador tiene que marcar explícitamente aquellos atributos compartidos entre hilos que pueden cambiar de valor •Existen dos formas de hacerlo: •Anadir el modificador volatile •Sincronizar los hilos usando alguna herramienta de sincronización (semáforos, etc...) desde que se escribe el valor hasta que se lee el valor (exclusión mutua, sincronización condicional...) Datos compartidos entre hilos • Dos o más hilos tienen acceso a una variable: public class objetoCompartido{ public volatile int contador = 0; } 12 PROGRAMACIÓN CONCURRENTE EN JAVA CPU 1 Thread 1 lee contador à 7 volatile contador = 7 cache cacheCPU 2 Thread 2 lectura de contador à 7 cache Garantiza la visibilidad Datos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA •El sistema se asegura de que un hilo lea el último valor de un atributo compartido si... • ... el atributo está marcado como volatile o... • ... el hilo ha usado alguna herramienta de sincronización (semáforo...) antes de leer el atributo Datos compartidos entre hilos • Dos o más hilos tienen acceso a una variable: public class objetoCompartido{ public volatile int contador = 0; } 14 PROGRAMACIÓN CONCURRENTE EN JAVA CPU 1 Thread 1 contador = 0+1 volatile contador = 0 cache cacheCPU 2 Thread 2 contador = 0+1 cache ¡¡¡Excepción!!! cuando dos hilos leen y escribe sobre la variable volatile Datos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA •¿Qué ocurre si un atributo es compartido entre varios hilos y no se ha marcado como volatile y no se usa ninguna herramienta de sincronización entre los procesos? •Es posible que un hilo lea un valor antiguo (el que estaba en la caché de ese proceso) y no el ultimo valor del atributo •Es posible que el compilador o la JVM elimine la variable si puede optimizar el código considerando que sólo un hilo accede a ella Datos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA • ¿Siempre hay que usar volatile para variables compartidas entre hilos? •No, no es necesario utilizar volatile si el hilo que escribe la variable se sincroniza con el hilo que lee la variable (usando herramientas de sincronización) •Por ejemplo: •� Si los atributos compartidos están bajo exclusión mutua con un semáforo, se garantiza que se leerán los valores correctos (sin usar volatile) •� Si un proceso escribe un atributo y desbloquea a otro proceso, el otro proceso leerá ́ el valor escrito (sin usar volatile) Datos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA • Esta forma de hacer que dos hilos puedan acceder a la información de forma coherente está definida en el Java Memory Model http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-‐133-‐faq.html • Siempre hay que usar volatile o sincronizar los hilos para que no se produzcan problemas de datos obsoletos o eliminado de variables 18 PROGRAMACIÓN CONCURRENTE EN JAVA Datos compartidos entre hilos Objetos compartidos entre hilos Objetos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA • Varios hilos pueden compartir valores de tipos primitivos y arrays • Los hilos también pueden compartir objetos de clases de la librería estándar o desarrolladas por nosotros • Por ejemplo: Los objetos SimpleSemaphore se comparten entre hilos Objetos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA • Un objeto se puede compartir entre varios hilos sin problemas en alguno de los siguientes casos: • Se accede a los métodos del objeto bajo exclusión mutua (para evitar que varios hilos ejecuten métodos de forma concurrente y produzcan intercalaciones no deseadas) • La clase de ese objeto está preparada para la ejecución concurrente de sus métodos. • Los objetos no se modifican durante el tiempo que se comparten entre los hilos • La clase es inmutable (sus objetos no cambian nunca de estado) Objetos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA Clases thread-‐safe • Una clase es thread-‐safe si los objetos de la clase se pueden compartir entre hilos sin necesidad de ponerlos bajos exclusión mutua • Todas las clases inmutables (cuyos objetos no cambian de estado) son thread-‐safe (p.e: String) • En la librería existen muchas clases thread-‐safe (hay que revisar la documentación) Objetos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA Entonces... ¿Todas las clases que programamos tienen que ser thread-‐safe? • Implementar una clase thread-‐safe es más difícil que una clase que no lo es • Si el objeto finalmente no se comparte entre varios hilos, los mecanismos que hemos implementado para hacerla thread-‐ safe posiblemente la hagan menos eficiente • Lo ideal es hacer que la clase sea inmutable, porque eso la hace thread-‐safe y no tiene ninguna penalización en el rendimiento, pero muchas veces no es posible Objetos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA ¿Como sabemos si una clase de la librería es thread-‐safe o no? • Si es inmutable, es thread-‐safe • En otro caso... hay que revisar la documentación (JavaDoc) o consultar tutoriales o manuales oficiales • En algunos casos existen dos versiones de la misma clase, una para compartir entre hilos y la otra para usarse en un único hilo (más eficiente) Objetos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA Ejemplo de dos versiones de la misma clase: • StringBuilder: A mutable sequence of characters. This class provides an API compatible with StringBuffer, but with no guarantee of synchronization. This class is designed for use as a drop-‐in replacement for StringBuffer in places where the string buffer was being used by a single thread (as is generally the case). Where possible, it is recommended that this class be used in preference to StringBuffer as it will be faster under most implementations. • StringBuffer: A thread-‐safe, mutable sequence of characters. A string buffer is like a String, but can be modified. Objetos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA ¿Mejor usar una clase thread-‐safe o poner bajo exclusión mutua objetos de clase no thread safe? • Siempre que exista una clase thread-‐safe es mejor usarla • Poner un objeto bajo exclusión mutua puede reducir el paralelismo innecesariamente • Los implementadores de una clase thread-‐safe intentan que sea lo más paralela posible, es decir, reducen al mínimo las zonas que no pueden ejecutarse de forma concurrente Objetos compartidos entre hilos PROGRAMACIÓN CONCURRENTE EN JAVA ¿Cómo se implementa una clase thread-‐safe? • Basta con asegurarse de que sus métodos se pueden ejecutar por varios hilos de forma concurrente sin condiciones de carrera ni intercalaciones no deseadas • Cómo • Haciéndola inmutable o… • Sincronizando los hilos de alguna forma... (exclusión mutua, sincronización...) o... • Con atributos thread-‐safe... Objetos compartidos entre hilos EJERCICIO PROGRAMACIÓN CONCURRENTE EN JAVA Crear una clase thread-‐safe llamada SincCond que implemente una sincronización condicional entre dos hilos •Métodos: • await: El hilo que ejecute ese método no podrá ́ continuar hasta que otro hilo ejecute signal • signal: Cuando se ejecute este método, el hilo que ejecuta await podrá ́ continuar • La clase tendrá ́ los atributos necesarios para implementar estos métodos con espera activa Objetos compartidos entre hilos EJERCICIO 2 PROGRAMACIÓN CONCURRENTE EN JAVA • Refactorizar el programa ProdCons para usar la clase SincCond 22 Ejercicio 1 • Refactorizar el programa ProdCons para usar la clase SincCond CONCURRENCIA Y ORIENTACIÓN A OBJETOS public class ProdCons { static volatile boolean producido = false; static volatile double producto; public static void productor() { producto = Math.random(); producido = true; } public static void consumidor() { while (!producido); print("Producto: "+producto); } public void exec() { new Thread(()-> productor()).start(); new Thread(()-> consumidor()).start(); } public static void main(String[] args){ new ProdCons().exec(); } } Objetos compartidos entre hilos EJERCICIO 2 PROGRAMACIÓN CONCURRENTE EN JAVA •Mejorar la clase SincCond para que permita la creación de productos indefinidos • Actualizar la clase ProdCons para que se produzcan y consuman 10 productos __MACOSX/Academia/Tema5/._Tema 5.4 - Concurrencia y orientación a objetos UPDATED.pdf Academia/Tema5/Tema 5.6 - Estructuras de datos concurrentes.pdf Programación Concurrente Tema 5 Programación Concurrente en Java Micael Gallego micael.gallego@urjc.es @micael_gallego mailto:micael.gallego@urjc.es Programación Concurrente Tema 5.5 Estructuras de datos concurrentes Micael Gallego micael.gallego@urjc.es @micael_gallego mailto:micael.gallego@urjc.es 3 Estructuras de datos concurrentes • Introducción • Valores atómicos concurrentes • Estructuras de datos en Java • Estructuras de datos sincronizadas • Estructuras de datos concurrentes • Colas (Queues) • Streams PROGRAMACIÓN CONCURRENTE EN JAVA 4 Introducción • Como ocurre con cualquier otro objeto, compartir estructuras de datos entre varios hilos es muy delicado • Hay que elegir algunas de las siguientes alternativas para evitar condiciones de carrera: Poner bajo exclusión mutua el acceso a las estructuras compartidas Usar estructuras de datos thread-safe, que estén preparadas para ser usadas desde varios hilos ESTRUCTURAS DE DATOS CONCURRENTES 5 Introducción • En la API de Java existe un conjunto de clases e interfaces para la gestión de estructuras de datos llamado Java Collections Framework • Este framework permite usar varios tipos de estructuras de datos (listas, mapas, conjuntos, colas…) • Existen versiones optimizadas para ser usadas por un único hilo y otras versiones diseñadas para ser compartidas entre hilos ESTRUCTURAS DE DATOS CONCURRENTES 6 Introducción • Al igual que existen estructuras de datos preparadas para ser compartidas entre hilos, la API de Java también ofrece clases para compartir valores atómicos entre hilos (thread-safe). • Con estas clases se pueden compartir valores de tipo entero y objetos • Disponen de métodos que realizan operaciones básicas bajo exclusión mutua ESTRUCTURAS DE DATOS CONCURRENTES 7 Estructuras de datos concurrentes • Introducción • Valores atómicos concurrentes • Estructuras de datos en Java • Estructuras de datos sincronizadas • Estructuras de datos concurrentes • Colas (Queues) • Streams PROGRAMACIÓN CONCURRENTE EN JAVA 8 Valores atómicos concurrentes • El paquete java.util.concurrent.atomic tiene varias clases que permiten compartir un valor atómico entre varios hilos (Ver documentación del paquete). • Dispone de clases para: Tipos primitivos: Integer, Long, Boolean Objetos Arrays de tipos primitivos y objetos Otras clases más avanzadas ESTRUCTURAS DE DATOS CONCURRENTES http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html 9 AtomicInteger • Se usa para compartir una variable de tipo entero entre varios hilos. • Habitualmente usada para tener un contador •Métodos: int get(): Devuelve el valor del entero int getAndIncrement(): Devuelve e incrementa int getAndSet(int newValue): Devuelve y establece un nuevo valor boolean compareAndSet(int expect, int update): Establece el nuevo valor si el anterior era el esperado. VALORES ATÓMICOS CONCURRENTES 10 AtomicInteger • Ejemplo de uso VALORES ATÓMICOS CONCURRENTES class AtomicCounter { private AtomicInteger c = new AtomicInteger(0); public void increment() { c.incrementAndGet(); } public void decrement() { c.decrementAndGet(); } public int value() { return c.get(); } } class SynchronizedCounter { private int c = 0; public synchronized void inc(){ c++; } public synchronized void dec(){ c--; } public synchronized int get(){ return c; } } 11 Estructuras de datos concurrentes • Introducción • Valores atómicos concurrentes • Estructuras de datos en Java • Estructuras de datos sincronizadas • Estructuras de datos concurrentes • Colas (Queues) • Streams PROGRAMACIÓN CONCURRENTE EN JAVA 12 Estructuras de datos en Java • El Java Collections Framework es un conjunto de clases e interfaces de la librería estándar para gestionar colecciones de elementos. • Permite gestionar listas, conjuntos, colas y mapas ESTRUCTURAS DE DATOS CONCURRENTES http://docs.oracle.com/javase/8/docs/technotes/guides/collections/ http://docs.oracle.com/javase/8/docs/technotes/guides/collections/ 13 Estructuras de datos en Java • Genéricos • Listas, conjuntos y mapas • Recorrer una colección • Ordenación y Búsqueda • Equals y hashcode • Colecciones con tipos primitivos • Ventajas de la colecciones ESTRUCTURAS DE DATOS CONCURRENTES 14 Estructuras de datos en Java • Genéricos • Listas, conjuntos y mapas • Recorrer una colección • Ordenación y Búsqueda • Equals y hashcode • Colecciones con tipos primitivos • Ventajas de la colecciones ESTRUCTURAS DE DATOS CONCURRENTES 15 Genéricos • Los genéricos son un mecanismo utilizado en los lenguajes de programación con tipado estático para especificar el tipo de los elementos de una estructura de datos • En C++ se les denomina plantillas (templates) • Aparte de las estructuras de datos, también puede utilizarse en otros contextos ESTRUCTURAS DE DATOS EN JAVA 16 Genéricos • Clase pila de fracciones implementada con un array ESTRUCTURAS DE DATOS EN JAVA class PilaFracciones { private Fraccion[] elementos; private int numElementos; public PilaFracciones(int tope) { this.elementos = new Fraccion[tope]; this.setNumElementos(0); } public Fraccion getElemento(int indice) { return this.elementos[indice]; } public void addElemento(Fraccion fraccion) { if(numElementos < elementos.length){ this.elementos[numElementos] = fraccion; numElementos++; } } ... } 17 Genéricos • Uso de la clase pila de fracciones ESTRUCTURAS DE DATOS EN JAVA public static void main(String[] args) { PilaFracciones pila = new PilaFracciones(5); Fraccion fraccion1 = new Fraccion(1,2); Fraccion fraccion2 = new Fraccion(1,3); Fraccion fraccion3 = new Fraccion(1,4); pila.addElemento(fraccion1); pila.addElemento(fraccion2); pila.addElemento(fraccion3); ... } 18 Genéricos • ¿Si queremos implementar pila de Intervalos? • ¿Creamos otra clase cambiando Fracción por Intervalo? • Duplicar código es malo ESTRUCTURAS DE DATOS EN JAVA class PilaIntervalos { private Intervalo[] elementos; private int numElementos; public PilaIntervalos(int tope) { this.elementos = new Intervalo[tope]; this.setNumElementos(0); } public Intervalo getElemento(int indice) { return this.elementos[indice]; } public void addElemento(Intervalo intervalo) { if(numElementos < elementos.length){ this.elementos[numElementos] = intervalo; numElementos++; } } ... } 19 Genéricos • Uso de la clase pila de intervalos ESTRUCTURAS DE DATOS EN JAVA public static void main(String[] args) { PilaIntervalos pila = new PilaIntervalos(5); Intervalo intervalo1 = new Intervalo(1,2); Intervalo intervalo2 = new Intervalo(1,3); Intervalo intervalo3 = new Intervalo(1,4); pila.addElemento(intervalo1); pila.addElemento(intervalo2); pila.addElemento(intervalo3); ... } 20 Genéricos • ¿Si queremos implementar pila de Intervalos? • Podemos usar el polimorfismo • ¿Hacemos que los elementos sean de tipo Object? ESTRUCTURAS DE DATOS EN JAVA class PilaObjects { private Object[] elementos; private int numElementos; public PilaObjects(int tope) { this.elementos = new Object[tope]; this.setNumElementos(0); } public Object getElemento(int indice) { return this.elementos[indice]; } public void addElemento(Object object) { if(numElementos < elementos.length){ this.elementos[numElementos] = object; numElementos++; } } ... } 21 Genéricos • Usar Object funciona • Pero el compilador no nos ayuda • Se puede escribir código incorrecto ESTRUCTURAS DE DATOS EN JAVA //Queremos que la pila contenga Intervalos PilaObjects pilaIntervalos = new PilaObjects(5); pilaIntervalos.addElemento(new Intervalo(2,4)); pilaIntervalos.addElemento(new Intervalo(2,6)); … //Nos equivocamos y el compilador no da error //No se genera una excepción en ejecución pilaIntervalos.addElemento(new Fraccion(1,2)); … //Siempre que sacamos intervalos tenemos //que hacer casting. Puede generar una excepción Intervalo intervalo = (Intervalo) pilaIntervalos.getElemento() 22 Genéricos • Lo ideal sería definir el tipo de los elementos cuando se usa la pila, no cuando se implementa la pila ESTRUCTURAS DE DATOS EN JAVA public static void main(String[] args) { PilaIntervalo pilaIntervalo; PilaFraccion pilaFraccion; ... } 23 Genéricos • Un tipo genérico es un tipo usado al implementar una clase que se concretará cuando se use la clase Al declarar una variable, parámetro o atributo Al instanciar un objeto ESTRUCTURAS DE DATOS EN JAVA 24 Genéricos ESTRUCTURAS DE DATOS EN JAVA class Pila<E> { public Pila(int tope) { ... } public E getElemento(int indice) { ... } public void addElemento(E elem) { ... } ... } class PilaIntervalos { public PilaIntervalos(int tope) { ... } public Intervalo getElemento(int indice) { ... } public void addElemento(Intervalo intervalo) { ... } } 25 Genéricos ESTRUCTURAS DE DATOS EN JAVA Sin usar genéricos //Queremos que la pila contenga Intervalos PilaIntervalos pilaIntervalos = new PilaIntervalos(5); Intervalo i1 = new Intervalo(2,4); Intervalo i2 = new Intervalo(2,6); pilaIntervalos.addElemento(i1); pilaIntervalos.addElemento(i2); … Intervalo i3 = pilaIntervalos.getElemento(1); 26 Genéricos ESTRUCTURAS DE DATOS EN JAVA Sin usar genéricos Usando genéricos //Queremos que la pila contenga Intervalos PilaIntervalos pilaIntervalos = new PilaIntervalos(5); Intervalo i1 = new Intervalo(2,4); Intervalo i2 = new Intervalo(2,6); pilaIntervalos.addElemento(i1); pilaIntervalos.addElemento(i2); … Intervalo i3 = pilaIntervalos.getElemento(1); //Queremos que la pila contenga Intervalos Pila<Intervalo> pilaIntervalos = new Pila<>(5); Intervalo i1 = new Intervalo(2,4); Intervalo i2 = new Intervalo(2,6); pilaIntervalos.addElemento(i1); pilaIntervalos.addElemento(i2); … Intervalo i3 = pilaIntervalos.getElemento(1) 27 Genéricos ESTRUCTURAS DE DATOS EN JAVA Sin usar genéricos Usando genéricos Pila<Intervalo> pilaIntervalos = new Pila<>(5); Pila<Fraccion> pilaFracciones = new Pila<>(5); … Intervalo intervalo = pilaIntervalos.getElemento(1); Fraccion fraccion = pilaFracciones.getElemento(1); PilaIntervalos pilaIntervalos = new PilaIntervalos(5); PilaFracciones pilaFracciones = new PilaFracciones(5); … Intervalo intervalo = pilaIntervalos.getElemento(1); Fraccion fraccion = pilaFracciones.getElemento(1); 28 Genéricos • ¿Cómo se implementaría un método que admitiera pilas, independientemente de cual sea el tipo de sus elementos? • Al símbolo ? Se le denomina comodín (wildcard) • Es importante conocer este símbolo porque se utiliza en diversos métodos del Java Collections Framework ESTRUCTURAS DE DATOS EN JAVA public void sacarElementos(Pila<?> pila, int numElems){ for(int i=0; i<numElementos; i++){ pila.pop(); } } 29 Estructuras de datos en Java • Genéricos • Listas, conjuntos y mapas • Recorrer una colección • Ordenación y Búsqueda • Equals y hashcode • Colecciones con tipos primitivos • Ventajas de la colecciones ESTRUCTURAS DE DATOS CONCURRENTES 30 Listas, conjuntos y mapas • A las estructuras de datos en Java se las denomina colecciones en vez de estructuras de datos • Con este nombre se enfatiza que son objetos que mantienen una colección de elementos, independientemente de su estructura interna • Esto permite separar la parte pública (interfaz) de los detalles de implementación internos ESTRUCTURAS DE DATOS EN JAVA 31 Listas, conjuntos y mapas • Interfaces Permiten manipular las colecciones independientemente de la implementación particular. Definen la funcionalidad, no cómo debe implementarse esa funcionalidad • Implementación Clases que implementan los interfaces que definen los tipos de colecciones (listas, mapas y conjuntos) • Algoritmos Métodos que realizan computaciones sobre colecciones de elementos Búsqueda, ordenación, etc. ESTRUCTURAS DE DATOS EN JAVA 32 Listas, conjuntos y mapas • Tipos de colecciones ESTRUCTURAS DE DATOS EN JAVA 33 Listas, conjuntos y mapas • Set<E> Colección que no mantiene el orden de inserción y que no puede tener dos o más objetos iguales • List<E> Colección que sí mantiene el orden de inserción y que puede contener elementos duplicados • Queue<E> Colección para almacenar múltiples elementos antes de ser procesados. Especialmente utilizada en programas concurrentes ESTRUCTURAS DE DATOS EN JAVA 34 Listas, conjuntos y mapas •Map<K,V> Estructura que guarda los elementos (valores) asociados a una clave ESTRUCTURAS DE DATOS EN JAVA 35 Listas, conjuntos y mapas • Collection<E> Colección genérica Se pueden consultar el número de elementos Se pueden añadir y eliminar elementos: Hay colecciones que permiten elementos duplicados y otras no Hay colecciones ordenadas o desordenadas Hay colecciones que permiten el valor null, otras no ESTRUCTURAS DE DATOS EN JAVA 36 Listas, conjuntos y mapas ESTRUCTURAS DE DATOS EN JAVA • Algunos métodos de Collection<E> Para agregar y eliminar elementos boolean add(E e) boolean remove(Object o) Para realizar consultas int size() boolean isEmpty() boolean contains(Object o) Para realizar varias operaciones de forma simultánea boolean containsAll(Collection<?> collection) void clear() boolean removeAll(Collection<?> collection) 37 Listas, conjuntos y mapas • Iterable<T> Permite acceder a los elementos uno por uno No permite consultar el número de elementos No permite añadir o eliminar elementos Se utiliza para recorrer una colección ESTRUCTURAS DE DATOS EN JAVA 38 Listas, conjuntos y mapas • Estos interfaces tienen una implementación por defecto que puede ser utilizada en la mayoría de los casos List<E>: ArrayList<E> Set<E>: HashSet<E> Map<K,V>: HashMap<K,V> Queue<E>: LinkedList<E>: Cola FIFO PriorityQueue<E>: Ordena sus elementos por prioridad ESTRUCTURAS DE DATOS EN JAVA 39 Listas, conjuntos y mapas ESTRUCTURAS DE DATOS EN JAVA 40 Listas (List<E>) • Interfaces vs Implementaciones Las variables, parámetros y atributos se declaran con el tipo de las interfaces La clase de implementación sólo se usa para instanciar los objetos Se abstrae lo más posible de la implementación concreta (y se puede cambiar fácilmente en el futuro) ESTRUCTURAS DE DATOS EN JAVA List<String> nombres = new ArrayList<>(); 41 Listas (List<E>) • Colección que mantiene el orden de inserción y que puede contener elementos duplicados • Similar a un array pero que crece de forma dinámica • Se accede a los elementos indicando su posición • Algunos métodos: void add(int index, E element) boolean addAll(int index, Collection<? extends E> c) E get(int index) E remove(int index) ESTRUCTURAS DE DATOS EN JAVA 42 Listas (List<E>) • Es la estructura de datos más usada • Es la estructura de datos más eficiente para la inserción de elementos (al final) • No obstante, no es muy eficiente para búsquedas (porque son secuenciales) ESTRUCTURAS DE DATOS EN JAVA 43 Listas (List<E>) • ArrayList<E> es la clase por defecto que implementa List<E> ESTRUCTURAS DE DATOS EN JAVA //Declaro la variable del tipo de la interfaz, //y le asigno un objeto del tipo de la clase de //implementación. List<Intervalo> listaIntervalos = new ArrayList<>(); List<Fraccion> listaFracciones = new ArrayList<>(); listaIntervalos.add(new Intervalo(2,4)); listaFracciones.add(new Fraccion(2,6)); … Intervalo intervalo = listaIntervalos.get(0); Fraccion fraccion = listaFracciones.get(0); 44 Ejercicio 1 • Crear un ejemplo básico para probar el funcionamiento de las listas Declarar una lista de String. Añadir y eliminar elementos de la lista Definir un método addElemToList(…) que reciba una lista de String y un String como parámetro y añada el String a la lista ESTRUCTURAS DE DATOS EN JAVA 45 Conjuntos (Set<E>) • No mantiene el orden de inserción • No es posible recuperar los elementos en el orden en que fueron insertados • No admite elementos duplicados • Si se añade un objeto al conjunto y ya había otro igual, no se produce ningún cambio en el conjunto ESTRUCTURAS DE DATOS EN JAVA 46 Conjuntos (Set<E>) • Es la estructura de datos más eficiente buscando elementos • Pero eso hace que la inserción sea más costosa que en las listas ESTRUCTURAS DE DATOS EN JAVA 47 Conjuntos (Set<E>) • HashSet<E> es la implementación por defecto de Set<E> y se implementa utilizando una tabla hash ESTRUCTURAS DE DATOS EN JAVA //Declaro la variable del tipo de la interfaz, //y le asigno un objeto del tipo de la clase de //implementación. Set<Intervalo> intervalos = new HashSet<>(); Intervalo intervalo = new Intervalo(2,4); intervalos.add(intervalo); //Esta inserción no tiene efecto intervalos.add(intervalo); int numIntervalos = intervalos.size(); // Devuelve 1 48 Mapas (Map<K,V) • Define una estructura de datos que asocia (mapea) claves con valores • No permite claves repetidas • Varias claves distintas pueden estar asociadas al mismo valor (valores repetidos) • La búsqueda de un valor asociado a una clave es muy eficiente ESTRUCTURAS DE DATOS EN JAVA 49 Mapas (Map<K,V) •Métodos más importantes: V put(K key, V value): Insertar un valor asociado a la clave V get(Object key): Obtener un valor asociado a la clave Collection<V> values(): Devuelve la colección de valores Set<K> keySet(): Devuelve el conjunto de claves Entry<K,V> entrySet(): Devuelve el conjunto de pares clave-valor (entradas del mapa) ESTRUCTURAS DE DATOS EN JAVA 50 Mapas (Map<K,V) • HashMap<K,V> es la implementación por defecto de Map<K,V> que implementa el conjunto de datos utilizando una tabla hash ESTRUCTURAS DE DATOS EN JAVA Map<String, Coche> propietarios = new HashMap<>(5); Coche toledo = new Coche(“Seat”, “Toledo”, 110) Coche punto = new Coche(“Fiat”, ”Punto”, 90); propietarios.put(“M-1233-YYY”, toledo); propietarios.put(“M-1234-ZZZ”, punto); Coche c = propietarios.get(“M-1234-ZZZ”); 51 Mapas (Map<K,V) ESTRUCTURAS DE DATOS EN JAVA Map<String, String> configuracion = new HashMap<>(); configuracion.put(“lenguaje”, “ingles”); configuracion.put(“servidor”, ”http://...”); Configuracion.put(“correo”, “a.b@xyz”); … String lenguaje = configuracion.get(“lenguaje”); String servidor = configuracion.get(“servidor”); 52 Ejercicio 2 • Se tiene una colección de aeropuertos (objetos de la clase Aeropuerto), y se desea poder obtener un aeropuerto dado su nombre • Declarar la estructura de datos adecuada para asociar el nombre de cada aeropuerto con el objeto aeropuerto correspondiente • Introducir varios aeropuertos asociados a sus nombres: “El Prat”, “Barajas”, “Castellón” • Obtener el objeto aeropuerto dado su nombre: “Barajas” ESTRUCTURAS DE DATOS EN JAVA 53 Estructuras de datos ordenadas • Cuando los elementos son comparables entre sí, puede ser útil insertar de forma ordenada los elementos • SortedSet<E>: Ordena los elementos Implementación TreeSet<E> • SortedMap<E>: Ordena las claves Implementación TreeMap<E> ESTRUCTURAS DE DATOS EN JAVA 54 Otras implementaciones • Aparte de las implementaciones por defecto, existen otras implementaciones para situaciones especiales De propósito general De propósito específico Para soporte de concurrencia Combinadas Optimizadas para el acceso secuencial Optimizadas para el acceso aleatorio ESTRUCTURAS DE DATOS EN JAVA 55 Estructuras de datos en Java • Genéricos • Listas, conjuntos y mapas • Recorrer una colección • Ordenación y Búsqueda • Equals y hashcode • Colecciones con tipos primitivos • Ventajas de la colecciones ESTRUCTURAS DE DATOS CONCURRENTES 56 Recorrer una colección • Acceder a cada elemento de una colección depende de su tipo: Lista Acceso por posición con bucle for Acceso secuencial Conjunto Acceso secuencial Mapa Acceso secuencial a la colección de valores Acceso secuencial al conjunto de claves Acceso secuencial al conjunto de entradas ESTRUCTURAS DE DATOS EN JAVA 57 Recorrer una Lista • Acceso por posición con bucle for • No se recomienda, sobre todo en estructuras de datos basadas en listas enlazadas (LinkedList<E>) puede ser ineficiente frente al acceso secuencial RECORRER UNA COLECCIÓN List<String> ciudades = new ArrayList<>(); ciudades.add("Ciudad Real"); ciudades.add("Madrid"); ciudades.add("Valencia"); for (int i=0; i < ciudades.size(); i++) { String ciudad = ciudades.get(i); System.out.println(ciudad + "\n"); } 58 Recorrer una Lista • Acceso secuencial Se puede realizar con el for mejorado Se puede realizar con el iterador RECORRER UNA COLECCIÓN 59 Recorrer una Lista • Acceso secuencial con for mejorado • En general es la forma preferida RECORRER UNA COLECCIÓN List<String> ciudades = new ArrayList<>(); ciudades.add("Ciudad Real"); ciudades.add("Madrid"); ciudades.add("Valencia"); for (String ciudad: ciudades) { System.out.println(ciudad + "\n"); } 60 Recorrer una Lista • Acceso secuencial con iterador RECORRER UNA COLECCIÓN interface Iterator<E> { boolean hasNext(); E next(); void remove(); } List<String> ciudades = new ArrayList<>(); ciudades.add("Ciudad Real"); ciudades.add("Madrid"); ciudades.add("Valencia"); Iterator<String> it = ciudades.iterator(); while (it.hasNext()){ String s = it.next(); System.out.println(s + "\n"); } 61 Recorrer una Lista • Acceso secuencial con iterador Es la única forma de borrar elementos de una lista mientras se recorre RECORRER UNA COLECCIÓN List<String> ciudades = new ArrayList<>(); ciudades.add("Ciudad Real"); ciudades.add("Madrid"); ciudades.add("Valencia"); Iterator<String> it = ciudades.iterator(); while (it.hasNext()){ String ciudad = it.next(); if(ciudad.equals(“Madrid”)){ it.remove(); } } 62 Recorrer un Conjunto • Sólo se pueden recorrer los elementos de un conjunto con acceso secuencial • Con for mejorado o con iteradores RECORRER UNA COLECCIÓN Set<String> ciudades = new HashSet<>(); ciudades.add("Ciudad Real"); ciudades.add("Madrid"); ciudades.add("Valencia"); for (String ciudad: ciudades) { System.out.println(ciudad + "\n"); } 63 Recorrer un Mapa • Formas de recorrer un mapa Acceso secuencial a la colección de valores RECORRER UNA COLECCIÓN Map<String, Coche> propietarios = new HashMap<>(5); coches.put(“M-1233-YYY”, new Coche(“Seat”, “Toledo”, 110)); coches.put(“M-1234-ZZZ”, new Coche(“Fiat”, ”Punto”, 90)); for (Coche coche : coches.values()) { System.out.println(“Coche: “+coche); } 64 Recorrer un Mapa • Formas de recorrer un mapa Acceso secuencial al conjunto de claves RECORRER UNA COLECCIÓN Map<String, Coche> propietarios = new HashMap<>(5); coches.put(“M-1233-YYY”, new Coche(“Seat”, “Toledo”, 110)); coches.put(“M-1234-ZZZ”, new Coche(“Fiat”, ”Punto”, 90)); for (String matricula : coches.keySet()) { System.out.println(“Matricular: “+matricula); } 65 Recorrer un Mapa • Formas de recorrer un mapa Acceso secuencial al conjunto de entradas RECORRER UNA COLECCIÓN Map<String, Coche> propietarios = new HashMap<>(5); coches.put(“M-1233-YYY”, new Coche(“Seat”, “Toledo”, 110)); coches.put(“M-1234-ZZZ”, new Coche(“Fiat”, ”Punto”, 90)); for (Entry<String,Coche> e : coches.entrySet()) { System.out.println(“Mat:”+e.getKey()+“ “+e.getValue()); } 66 Estructuras de datos en Java • Genéricos • Listas, conjuntos y mapas • Recorrer una colección • Ordenación y Búsqueda • Equals y hashcode • Colecciones con tipos primitivos • Ventajas de la colecciones ESTRUCTURAS DE DATOS CONCURRENTES 67 Ordenación y Búsqueda • Sólo se pueden ordenar las listas • Ordenación de elementos con métodos estáticos en la clase Collections ESTRUCTURAS DE DATOS EN JAVA List<String> nombres = new ArrayList<>(); nombres.add("Pepe"); nombres.add("Juan"); nombres.add("Antonio"); Collections.sort(nombres); System.out.println(nombres); [Antonio, Juan, Pepe] 68 Ordenación y Búsqueda • Se puede especificar el orden de comparación Se usa una lambda que devuelve un valor positivo si o1 es mayor que 02. Negativo en caso contrario ESTRUCTURAS DE DATOS EN JAVA List<String> nombres = new ArrayList<>(); nombres.add("Juanin"); nombres.add("Pepe"); nombres.add("Antonio"); Collections.sort(nombres,(o1,o2)-> o1.length()-o2.length()); [Pepe, Juanin, Antonio] 69 Ordenación y Búsqueda • Se puede especificar el orden de comparación Se puede usar Comparator.comparing(…) ESTRUCTURAS DE DATOS EN JAVA List<String> nombres = new ArrayList<>(); nombres.add("Juanin"); nombres.add("Pepe"); nombres.add("Antonio"); Collections.sort(nombres, Comparator.comparing(String::length)); [Pepe, Juanin, Antonio] 70 Ordenación y Búsqueda • ¿Qué es una búsqueda? En un conjunto es saber si está el elemento En un mapa es obtener el valor asociado a la clave (si está) En una lista es saber su posición (si está) ESTRUCTURAS DE DATOS EN JAVA 71 Ordenación y Búsqueda •Búsqueda en conjuntos Método: boolean contains(Object o) •Búsqueda en mapas Método : V get(Object key) ESTRUCTURAS DE DATOS EN JAVA 72 Ordenación y Búsqueda • Búsquedas en listas Si la lista está desordenada, usamos el método int indexOf(E e) Si la lista está ordenada, se puede usar una búsqueda binaria (método en la clase Collections) Si el elemento está en la lista, devuelve su posición Si el elemento no está en la lista, devuelve el lugar en el que debería estar ESTRUCTURAS DE DATOS EN JAVA 73 Ordenación y Búsqueda ESTRUCTURAS DE DATOS EN JAVA List<String> nombres = new ArrayList<>(); nombres.add("Pepe"); nombres.add("Juan"); nombres.add("Antonio"); Collections.sort(nombres); //int pos = nombres.indexOf("Mario"); int pos = Collections.binarySearch(nombres, "Mario"); if (pos < 0){ //El nombre no está en la lista int insertPos = -pos-1; System.out.println("No está. Debería estar en: "+insertPos); } else { System.out.println("Está en la posición: "+pos); } 74 Ordenación y Búsqueda • Hay que elegir muy bien la estructura de datos que se utiliza en un programa Listas (basadas en arrays) Eficiente la inserción al final O(1) Eficiente el acceso por posición O(1) Ineficiente la búsqueda O(n) Conjuntos (basados en hash) Eficiente la inserción O(1) (aunque menos que la lista) No se puede hacer acceso por posición Eficiente la búsqueda O(1) Mapas (basados en hash) Igual que los conjuntos ESTRUCTURAS DE DATOS EN JAVA 75 Ejercicio 3 • Implementar una aplicación que permita gestionar en memoria un conjunto de viajes de una aerolínea • Cada viaje se representa con la ciudad origen, destino y la duración del viaje (clase Viaje) • Se dan de alta los viajes en un gestor (clase GestorViajes) • Al gestor de viajes se le pueden pedir: Devolver todos los viajes que tienen una determinada ciudad origen Devolver todos los viajes que tienen una determinada ciudad destino Devolver todos los viajes Devolver todas las ciudades en las que hay viajes • Hay que conseguir el menor tiempo de ejecución de las consultas, aunque sean necesarias varias estructuras de datos ESTRUCTURAS DE DATOS EN JAVA 76 Estructuras de datos en Java • Genéricos • Listas, conjuntos y mapas • Recorrer una colección • Ordenación y Búsqueda • Equals y hashcode • Colecciones con tipos primitivos • Ventajas de la colecciones ESTRUCTURAS DE DATOS CONCURRENTES 77 Equals y hashcode • ¿Objetos iguales o el mismo objeto? Dos variables apuntan al mismo objeto si al comparar con == se obtiene verdadero (true) Dos variables apuntan a objetos iguales si al comparar con el método equals se obtiene verdadero (true) ESTRUCTURAS DE DATOS EN JAVA 78 Equals y hashcode ESTRUCTURAS DE DATOS EN JAVA “a” “a” “b”v1 v3 v4 Expresión Resultado v1 == v2 true v1.equals(v2) true v1 == v3 false v1.equals(v3) true v1 == v4 false v1.equals(v4) false v2 79 Equals y hashcode • Estructuras de datos basadas en hash La estructura de datos HashSet utiliza los métodos equals y hashcode de los objetos que se insertan en ella La estructura de datos HashMap utiliza los métodos equals y hashcode de las claves que se asocian a valores Los métodos equals y hashcode se usan para saber si dos objetos son iguales o diferentes ESTRUCTURAS DE DATOS EN JAVA 80 Equals y hashcode • Estructuras de datos basadas en hash Las clases String, Date, Double… implementan correctamente los métodos equals y hashcode para que funcionen correctamente con HashSet y como claves de un HashMap Las clases implementadas por el desarrollador no tienen esta funcionalidad por defecto ESTRUCTURAS DE DATOS EN JAVA 81 Equals y hashcode • Estructuras de datos basadas en hash Si en una clase propia no implementamos los métodos equals y hashcode Nunca existirán dos objetos iguales de esa clase (aunque tengan los mismos valores de los atributos) Si se intenta insertar el mismo objeto en un conjunto por segunda vez, no tendrá efecto. Si se intenta insertar un objeto con los mismos valores de atributos que otro ya incluido, se insertará el nuevo valor (porque a ojos del HashSet, no son iguales) ESTRUCTURAS DE DATOS EN JAVA 82 Equals y hashcode • Estructuras de datos basadas en hash Si en una clase propia si implementamos los métodos equals y hashcode Decidimos cuándo dos objetos de esa clase se consideran iguales (habitualmente si algunos de sus atributos son iguales) Si se intenta insertar el mismo objeto en un conjunto por segunda vez, no tendrá efecto. Si se intenta insertar un objeto igual que otro ya incluido, no tendrá efecto. ESTRUCTURAS DE DATOS EN JAVA 83 Equals y hashcode • ¿Cómo se implementan los métodos equals y hashcode? Se deciden qué atributos tienen que ser iguales para que los objetos se consideren iguales (pueden ser todos). El entorno de desarrollo genera automáticamente el código en base a esos atributos ESTRUCTURAS DE DATOS EN JAVA * Item 9 en "Effective Java Programming Language Guide, second edition”, (Addison-Wesley,2008) de Joshua Bloch 84 Equals y hashcode public class Fraccion { private float numerador; private float denominador; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Float.floatToIntBits(denominador); result = prime * result + Float.floatToIntBits(numerador); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Fraccion other = (Fraccion) obj; if (Float.floatToIntBits(denominador) != Float .floatToIntBits(other.denominador)) return false; if (Float.floatToIntBits(numerador) != Float .floatToIntBits(other.numerador)) return false; return true; } } 85 Estructuras de datos en Java • Genéricos • Listas, conjuntos y mapas • Recorrer una colección • Ordenación y Búsqueda • Equals y hashcode • Colecciones con tipos primitivos • Ventajas de la colecciones ESTRUCTURAS DE DATOS CONCURRENTES 86 Colecciones con tipos primitivos • Existen clases que se comportan como los tipos primitivos (clases de envoltura, wrapper) Integer, Double, Float, Boolean, Character... • El autoboxing y autounboxing es la capacidad de conversión automática entre un valor de un tipo primitivo y un objeto de la clase correspondiente ESTRUCTURAS DE DATOS EN JAVA int numero = 3; Integer numObj = numero; int otroNum = numObj; 87 Colecciones con tipos primitivos • Esto permite usar las colecciones con tipos primitivos • Hay que ser consciente de que se tienen que realizar conversiones y eso es costoso ESTRUCTURAS DE DATOS EN JAVA List<Integer> enteros = new ArrayList<>(); enteros.add(3); enteros.add(5); enteros.add(10); int num = enteros.get(0); 88 Colecciones con tipos primitivos • Existen implementaciones de terceros con estructuras de datos especialmente diseñadas para tipos primitivos • Deben usarse cuando se utilizan mucho en un programa y las conversiones sean muy numerosas http://trove4j.sourceforge.net/ http://fastutil.dsi.unimi.it/ http://commons.apache.org/primitives/ ESTRUCTURAS DE DATOS EN JAVA http://trove4j.sourceforge.net/ http://fastutil.dsi.unimi.it/ http://commons.apache.org/primitives/ 89 Estructuras de datos en Java • Genéricos • Listas, conjuntos y mapas • Recorrer una colección • Ordenación y Búsqueda • Equals y hashcode • Colecciones con tipos primitivos • Ventajas de la colecciones ESTRUCTURAS DE DATOS CONCURRENTES 90 Ventajas de las colecciones • Reducción del esfuerzo del programador • Incremento de la velocidad y calidad • Interoperabilidad entre APIs no relacionadas •Menor esfuerzo de aprendizaje y uso de otras APIs • Fomenta la reutilización del software ESTRUCTURAS DE DATOS EN JAVA 91 Ventajas de las colecciones • Aunque las estructuras de datos de la API son muy completas, existen librerías de terceros que las complementan Google Guava: http://code.google.com/p/guava-libraries/ Otras: http://java-source.net/open-source/collection-librarie s ESTRUCTURAS DE DATOS EN JAVA http://code.google.com/p/guava-libraries/ http://java-source.net/open-source/collection-libraries http://java-source.net/open-source/collection-libraries 92 Estructuras de datos concurrentes • Introducción • Valores atómicos concurrentes • Estructuras de datos en Java • Estructuras de datos sincronizadas • Estructuras de datos concurrentes • Colas (Queues) PROGRAMACIÓN CONCURRENTE EN JAVA 93 Estructuras de datos sincronizadas • En el Java Collections Framework existen implementaciones de las colecciones denominadas colecciones sincronizadas (synchronized collections) • Estas implementaciones están diseñadas para que se puedan compartir entre varios hilos • Tienen todos sus métodos bajo exclusión mútua (sincronizados) • Ningún hilo podrá ejecutar ningún método mientras otro hilo esté ejecutando otro método (o el mismo) ESTRUCTURAS DE DATOS CONCURRENTES 94 Estructuras de datos sincronizadas • Para crear una colección sincronizada primero hay que crear un objeto de una colección • Luego se invoca algún método estático de la clase Collections • A las colecciones sincronizadas se las denomina envolturas de sincronización (Synchronization Wrappers) porque evuelven a la colección original ESTRUCTURAS DE DATOS CONCURRENTES public static <T> Collection<T> synchronizedCollection(Collection<T> c) public static <T> Set<T> synchronizedSet(Set<T> s) public static <T> List<T> synchronizedList(List<T> list) public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s) public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m) 95 Estructuras de datos sincronizadas • Ejemplo de creación de colecciones sincronizadas ESTRUCTURAS DE DATOS CONCURRENTES List<String> sharedList = Collections.synchronizedList(new ArrayList<>()); Map<String,String> sharedMap = Collections.synchronizedMap(new HashMap<>()); Set<String> sharedSet = Collections.synchronizedSet(new HashSet<>()); 96 Estructuras de datos sincronizadas • Ejemplo de acceso compartido a una lista de Strings ESTRUCTURAS DE DATOS CONCURRENTES public class SynchronizedCollectionsSample { private List<String> sharedList; private void process(int num) { for (int i = 0; i < 5; i++) { sharedList.add("H"+num+"_I"+i); } } public exec() { sharedList = Collections.synchronizedList(new ArrayList<>()); //Create threads and wait for it to finish System.out.println("List: "+sharedList); } } 97 Instrucciones atómicas • ¿El siguiente código es seguro si list es una colección sincronizada compartida por varios hilos? ESTRUCTURAS DE DATOS SINCRONIZADAS public String deleteLast(List<String> list) { int lastIndex = list.size() - 1; return list.remove(lastIndex); } 98 Instrucciones atómicas • ¿El siguiente código es seguro si list es una colección sincronizada compartida por varios hilos? • La colección está preparada para llamadas concurrentes porque está sincronizada • Pero es posible que se produzca una condición de carrera y la colección compartida sea modificada entre la primera consultar el tamaño y devolver el elemento ESTRUCTURAS DE DATOS SINCRONIZADAS public String deleteLast(List<String> list) { int lastIndex = list.size() - 1; return list.remove(lastIndex); } 99 Instrucciones atómicas • Cualquier instrucción atómica de grano grueso, en la que se realice una acción compuesta requiere que el cliente de la colección sincronice su acceso a la misma Recorrer la colección Borrar/Devolver el último elemento Operaciones condicionales como “insertar si ausente” (put-if- absent) … • El cliente tiene que poner las acciones compuestas bajo exclusión mutua con el mismo cerrojo (lock) que se sincroniza internamente la colección ESTRUCTURAS DE DATOS SINCRONIZADAS 100 Instrucciones atómicas • Borrar el último elemento de una lista sincronizada • Recorrer una lista sincronizada ESTRUCTURAS DE DATOS SINCRONIZADAS synchronized(sharedList) { for (String elem : sharedList){ System.out.print(elem+","); } System.out.println(); } public String deleteLast(List<String> list) { synchronized (list) { int lastIndex = list.size() - 1; return list.remove(lastIndex); } } 101 Instrucciones atómicas • Insertar un elemento en un mapa sincronizado si no existen ya antes ESTRUCTURAS DE DATOS SINCRONIZADAS synchronized (map) { if (!map.containsKey(key)) { map.put(key, value); } } 102 Instrucciones atómicas • Al recorrer un mapa sincronizado hay que usar como cerrojo (lock) al mapa, no a las colecciones auxiliares • Si se recorre una colección sin exclusión mutua y otro hilo la modifica antes de finalizar el recorrido, se puede producir una ConcurrentModificationException ESTRUCTURAS DE DATOS SINCRONIZADAS synchronized(sharedMap) { for (Entry<String,Integer> elem : sharedMap.entrySet()){ System.out.print( elem.getKey()+">"+elem.getValue()+","); } System.out.println(); } 103 Problemas con las colecciones sincronizadas • Todas las acciones que se realizan sobre la colección deben sincronizarse (están bajo exclusión mutua) • Esto puede limitar innecesariamente la concurrencia en operaciones de lectura simultáneas (que no causan interferencias) • Se limita el aprovechamiento de los recursos, se limita la escalabilidad • Esto es especialmente problemático al poner la colección bajo exclusión mutua para recorrerla y realizar operaciones en los elementos ESTRUCTURAS DE DATOS SINCRONIZADAS 104 Estructuras de datos concurrentes • Introducción • Valores atómicos concurrentes • Estructuras de datos en Java • Estructuras de datos sincronizadas • Estructuras de datos concurrentes • Colas (Queues) • Streams PROGRAMACIÓN CONCURRENTE EN JAVA 105 Estructuras de datos concurrentes • Las colecciones concurrentes son implementaciones de las colecciones diseñadas para compartirse entre hilos • Para ciertos casos, son más eficientes que las colecciones sincronizadas ESTRUCTURAS DE DATOS CONCURRENTES 106 Estructuras de datos concurrentes • Algunas de las colecciones concurrentes más usadas: Map: ConcurrentHashMap List: CopyOnWriteArrayList Set: CopyOnWriteArraySet , set basado en ConcurrentHashMap ESTRUCTURAS DE DATOS CONCURRENTES 107 ConcurrentHashMap • La clase ConcurrentHashMap está diseñada para permitir acceso concurrente de lectura y escritura • Es mucho más eficiente que el mapa sincronizado y se recomienda su uso siempre ESTRUCTURAS DE DATOS CONCURRENTES Threads ConcurrentHashMap Mapa sincronizado 1 1.00 1.03 4 5.58 78.23 8 13.21 163.48 32 57.27 778.41 http://www.ibm.com/developerworks/library/j-jtp07233/ http://www.ibm.com/developerworks/library/j-jtp07233/ 108 ConcurrentHashMap • En las colecciones concurrentes no se puede usar la sincronización para implementar operaciones atómicas de grano grueso • Para solucionarlo, el interfaz ConcurrentMap proporciona las operaciones atómicas de grano grueso más habituales como métodos ESTRUCTURAS DE DATOS CONCURRENTES 109 ConcurrentHashMap •Métodos de ConcurrentMap V putIfAbsent(K key, V value): Si la clave especificada no está asociada con ningún valor, se asocia con el valor indicado. Se devuelve el antiguo valor si existía. boolean remove(Object key, Object value): Borra la entrada para la clave sólo si está actualmente asociada al valor indicado V replace(K key, V value): Reemplaza la entrada para la clave sólo si está actualmente asociada a algún valor Muchos más… ESTRUCTURAS DE DATOS CONCURRENTES https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html 110 • Asociar un valor a una clave sólo si no existe esa clave en el mapa synchronized (map) { if (!map.containsKey(key)){ map.put(key, value); } else { // Ya existe la clave } } ConcurrentHashMap ESTRUCTURAS DE DATOS CONCURRENTES Object old = map.putIfAbsent(key, value); if(old != null){ // Ya existe la clave } synchronizedMap ConcurrentHashMap 111 CopyOnWriteArrayList • CopyOnWriteArrayList es una implementación concurrente de una lista • Es más eficiente que la lista sincronizada si hay muchas lecturas y muy pocas escrituras • Las colecciones copia-al-escribir (copy-on-write) no bloquean los hilos que leen. • Cuando un hilo quiere escribir, se crea una copia interna de los elementos (muy costoso), por eso sólo se usan con pocas escrituras. ESTRUCTURAS DE DATOS CONCURRENTES 112 Estructuras de datos concurrentes • Colecciones concurrentes para conjuntos La clase CopyOnWriteArraySet es similar a CopyOnWriteArrayList pero para conjuntos Siempre se puede usar un ConcurrentMap como si fuera un conjunto, considerando las claves (keys) del map como los valores del conjunto (y usando cualquier valor) ESTRUCTURAS DE DATOS CONCURRENTES 113 Estructuras de datos concurrentes • Colecciones concurrentes para conjuntos Si necesitamos ver ese mapa como un conjunto, podemos “recubrirlo” Si queremos usar las operaciones atómicas de grano grueso, lo tendremos que hacer sobre el mapa concurrente ESTRUCTURAS DE DATOS CONCURRENTES ConcurrentMap<String,Boolean> map = new ConcurrentHashMap<>(); Set<String> names = Collections.newSetFromMap(map); 114 Ejercicio 4 • Se desea implementar de forma concurrente un programa que busca ficheros con el mismo nombre dentro de una carpeta • La búsqueda se realiza recursivamente en unas carpetas dentro de otras • Se proporciona la versión secuencial del programa • Por simplicidad, en la carpeta raíz no hay ficheros y se crearán tantos hilos como carpetas ESTRUCTURAS DE DATOS CONCURRENTES 115 Ejercicio 4 ESTRUCTURAS DE DATOS CONCURRENTES public class FindDuplicates { private Map<String,String> duplicates = new HashMap<>(); public void findDuplicates(File root) { if (root.isDirectory()) { for (File file : root.listFiles()) { if (file.isDirectory()) { findDuplicates(file); } else { String path = duplicates.get(file.getName()); if(path == null){ duplicates.put(file.getName(), file.getAbsolutePath()); } else { System.out.println("Found duplicate file: "+file.getName()); System.out.println(" "+path); System.out.println(" "+file.getAbsolutePath()); } } } } } public void exec() { findDuplicates(new File("X:\\Dir")); } } 116 Estructuras de datos concurrentes • Introducción • Valores atómicos concurrentes • Estructuras de datos en Java • Estructuras de datos sincronizadas • Estructuras de datos concurrentes • Colas (Queues) • Streams PROGRAMACIÓN CONCURRENTE EN JAVA 117 Colas (Queues) • Las colas (queues) son colecciones que albergan elementos antes de ser procesados • Las colas son clases que implementan el interfaz java.util.Queue<E> • El orden de procesado de elementos puede ser FIFO, por prioridades… y depende de la implementación ESTRUCTURAS DE DATOS CONCURRENTES 118 Colas (Queues) • Proporciona métodos para insertar, extraer e examinar elementos (sin extraerlos) • Los métodos se proporcionan de dos formas Métodos que lanzan excepciones si fallan Métodos que devuelven false o null si fallan ESTRUCTURAS DE DATOS CONCURRENTES Lanza una excepción Devuelve false o null Insertar add(e) offer(e) Extraer remove() poll() Examinar element() peek() 119 Colas (Queues) • Insertar Los métodos offer(e) y add(e) insertan un elemento si la cola no está llena Si la cola está llena: offer(e) devuelve false add(e) lanza una excepción no chequeada ESTRUCTURAS DE DATOS CONCURRENTES 120 Colas (Queues) • Extraer Los métodos poll() y remove() extraen elementos de la cabeza si la cola no está vacía El orden de extracción depende de la política de ordenación de la cola (FIFO, Prioridad…) Si la cola está vacía: poll() devuelve null remove() lanza una excepción no chequeada ESTRUCTURAS DE DATOS CONCURRENTES 121 Colas (Queues) • Examinar Los métodos peek() y element() devuelven, pero no eliminan, la cabeza de la cola, si la cola no está vacía Si la cola está vacía: peek() devuelve null element() lanza una excepción no chequeada ESTRUCTURAS DE DATOS CONCURRENTES 122 Colas (Queues) • Las colas se utilizan habitualmente para implementar esquemas de productores consumidores en programación concurrente • Para ello es necesario que los métodos para extraer y añadir sean bloqueantes • El interfaz de colas (Queue) no define métodos bloqueantes • Estos métodos están definidos en el interfaz BlockingQueue, que hereda de Queue ESTRUCTURAS DE DATOS CONCURRENTES 123 Colas bloqueantes (BlockingQueue) • Las colas bloqueantes (java.util.concurrent.BlockingQueue) ofrecen operaciones que se quedan bloqueadas • Si la cola está llena, los métodos de inserción se bloquean hasta que haya espacio (o salte un timeout) • Si la cola está vacía, los métodos de extracción se bloquean hasta que exista un elemento (o salte un timeout) ESTRUCTURAS DE DATOS CONCURRENTES 124 Colas bloqueantes (BlockingQueue) • Inserción bloqueante put(e): Bloquea hasta que se pueda realizar la operación offer(e, time, unit): Bloquea y devuelve false si no se realiza la operación en el tiempo indicado • Extracción bloqueante take(): Bloquea hasta que se pueda realizar la operación poll(time, unit): Bloquea y devuelve null si no se realiza la operación en el tiempo indicado ESTRUCTURAS DE DATOS CONCURRENTES 125 Colas bloqueantes (BlockingQueue) • Las colas bloqueantes (BlockingQueue) son clases que permiten compartirse por la varios hilos (thread-safe) • Pero algunas operaciones compuestas como addAll, containsAll,… puede que no se implementen de forma atómica (conviene revisar la documentación de cada método) ESTRUCTURAS DE DATOS CONCURRENTES 126 Colas dobles (Deque) • Las colas dobles (java.util.Deque) son colecciones lineales que permiten inserción y eliminación en cualquier extremo • Deque proviene de cola doblemente finalizada (double ended queue) • Este estructura se puede comportar como una cola FIFO o como una pila LIFO dependiendo de los métodos usados • El interfaz Deque hereda de Queue ESTRUCTURAS DE DATOS CONCURRENTES 127 ESTRUCTURAS DE DATOS CONCURRENTES 128 Implementaciones de Queue • Queue PriorityQueue Ordenación basada en prioridades No concurrente (no thread-safe) ConcurrentLinkedQueue Ordención FIFO Concurrente (thread-safe) LinkedList Ordenación FIFO No concurrente (no thread-safe) ESTRUCTURAS DE DATOS CONCURRENTES 129 Implementaciones de Deque • Deque ArrayDeque Implementación FIFO basada en array No concurrente (no thread-safe) LinkedList Implementación FIFO basada en lista enlazada No concurrente (no thread-safe) ConcurrentLinkedDeque (Java 7) Implementación FIFO basada en lista enlazada Concurrente (thread-safe) ESTRUCTURAS DE DATOS CONCURRENTES 130 Implementaciones de BlockingQueue • BlockingQueue ArrayBlockingQueue Cola FIFO basada en arrays LinkedBlockingQueue Cola FIFO basada en listas enlazadas PriorityBlockingQueue Cola por prioridades ESTRUCTURAS DE DATOS CONCURRENTES 131 Implementaciones de BlockingQueue • BlockingQueue SynchronousQueue Cola sin capacidad Las inserciones deben esperar a las extracciones Las extracciones deben esperar a las inserciones DelayQueue Cola FIFO que mantiene los elementos en la cola durante un tiempo especificado (delay) antes de que estén disponibles para la extracción ESTRUCTURAS DE DATOS CONCURRENTES 132 Implementaciones • BlockingDeque LinkedBlockingDeque Cola doble basada en listas enlazas Concurrente (thread-safe) • TransferQueue Cola con métodos para esperar a consumidores al insertar un elemento LinkedTransferQueue Cola de transferencia basada en listas enlazadas Concurrente (thread-safe) ESTRUCTURAS DE DATOS CONCURRENTES 133 Uso de las colas • Las colas se utilizan para esquemas de productores- consumidores ESTRUCTURAS DE DATOS CONCURRENTES public class ProdConsQueue { private BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); public void productor() throws InterruptedException{ for (int i = 0; i < 20; i++) { Thread.sleep((long)(Math.random() * 500)); queue.put(i); } } public void consumidor() throws InterruptedException{ while (true) { int data = queue.take(); Thread.sleep((long)(Math.random() * 500)); System.out.println(data + " "); } } public void exec() { //Crear hilos productores y consumidores // } } 134 Ejercicio 5 • Se pretende simular mediante un programa concurrente el comportamiento de una Fábrica de coches. • La fábrica tiene dos tipos de elementos: Máquinas: Que generan piezas de un determinado tipo. Robots: Que ensamblan todas las piezas que forman el coche. • Para hacer el programa más genérico, se utilizan las siguientes constantes: NUM_ROBOTS: Número de robots NUM_TIPOS_PIEZAS: Tipos diferentes de piezas que se necesitan para montar un coche. Cada pieza tiene asociado su tipo y es un valor entre 0 y NUM_TIPOS_PIEZAS-1 ESTRUCTURAS DE DATOS CONCURRENTES 135 Ejercicio 5 •Máquinas Las máquinas fabrican piezas y las almacenan. Este proceso se repite indefinidamente. Las piezas se simulan como un valor aleatorio de tipo double Los métodos que se simulan estas operaciones son: fabricarPieza(…) y almacenarPieza(…) Cada máquina sólo genera piezas del mismo tipo. Hay una máquina por cada tipo de pieza. El almacén donde se guardan las piezas es común para todas las máquinas. El almacén de piezas puede albergar un máximo de piezas de cada tipo (identificado por la constante MAX_PIEZAS ) ESTRUCTURAS DE DATOS CONCURRENTES 136 Ejercicio 5 • Robots Los robots recogen piezas del almacén y las montan para crear los coches. Este proceso se repite indefinidamente. Los métodos que simulan estas operaciones son: recogerPieza(…) y montarPieza(…) Las piezas deben recogerse en orden (de 1 a NUM_TIPOS_PIEZAS) para que se puedan montar adecuadamente. ESTRUCTURAS DE DATOS CONCURRENTES 137 Ejercicio 5 • Requisitos Las máquinas pueden fabricar piezas y los robots pueden montar piezas en el producto sin sufrir ninguna interferencia entre ellos El programa debe implementarse de forma que ni las máquinas ni los robots interfieran entre sí de forma inadecuada al acceder al almacén ESTRUCTURAS DE DATOS CONCURRENTES 138 Estructuras de datos concurrentes • Introducción • Valores atómicos concurrentes • Estructuras de datos en Java • Estructuras de datos sincronizadas • Estructuras de datos concurrentes • Colas (Queues) • Streams PROGRAMACIÓN CONCURRENTE EN JAVA 139 Streams • Los streams (flujos) permiten procesar colecciones de elementos con un estilo funcional (declarativo) • Ventajas frente al estilo actual: Más compacto y de alto nivel Fácilmente paralelizable No se necesita tener todos los elementos en memoria para empezar a procesar ESTRUCTURAS DE DATOS CONCURRENTES 140 Streams • Tenemos la clase Person y la lista people ESTRUCTURAS DE DATOS CONCURRENTES public class Person { private String name; private int age; // constructors // getters / setters } List<Person> people = new ArrayList<>(); 141 Streams • Se quiere calcular la edad media ESTRUCTURAS DE DATOS CONCURRENTES int sum = 0; int average = 0; for (Person person : people) { sum += person.getAge(); } if (!list.isEmpty()) { average = sum / list.size(); } 142 Streams • Se quiere calcular la edad media de los menores de 20 ESTRUCTURAS DE DATOS CONCURRENTES int sum = 0; int n = 0; int average = 0; for (Person person : people) { if (person.getAge() > 20) { n+ + ; sum + = person.getAge() ; } } if (n > 0) { average = sum / n ; } 143 Streams • La programación imperativa es muy verbosa porque se indica qué se quiere y cómo se consigue • La programación declarativa es mucho más compacta porque basta indicar qué se quiere ESTRUCTURAS DE DATOS CONCURRENTES select avg(age) from Person where age > 20 Sentencia SQL equivalente 144 Streams ESTRUCTURAS DE DATOS CONCURRENTES Mapeo (mapping): De cada elemento de la lista original obtiene un nuevo valor para la lista resultante. Se devuelve el mismo número de elementos Filtrado: Filtra los elementos. Se devuelven menos elementos de los originales Reducción: Se agregan todos los elementos en uno solo: Ejemplos: media, mínimo, máximo, suma… 145 Streams • Los streams (flujos) permiten aplicar operaciones de mapeo, filtrado y reducción a colecciones de datos • Un stream es una secuencia de elementos que sólo pueden procesarse una vez • Puede generarse de forma dinámica (no tiene que estar toda la secuencia en memoria) • Esto permite implementaciones muy eficientes de las operaciones sin crear estructuras de datos intermedias ESTRUCTURAS DE DATOS CONCURRENTES 146 Streams • La forma más habitual de crear un stream es partiendo de una colección ESTRUCTURAS DE DATOS CONCURRENTES int sumOver20 = persons.stream() .map(Person::getAge) .filter(age -> age > 20) .reduce(0, Integer::sum); Creación del stream 147 Streams • También se pueden crear de forma literal: • Partiendo de un array: ESTRUCTURAS DE DATOS CONCURRENTES Stream<String> s = Stream.of("one","two","three"); Stream<String> s2 = Stream.empty(); String[] numbers = {"one", "two", "three"}; Stream<String> s = Arrays.stream(numbers); 148 Streams • O desde algunos métodos de la API: ESTRUCTURAS DE DATOS CONCURRENTES IntStream chars = “Hola”.chars(); Stream<String> lines = lineNumberReader.lines(); IntStream nums = random.ints(); Stream s3 = Stream.concat(stream1, stream2); Stream<Integer> pares = Stream.iterate(0,n->n+2); Stream<Double> rand = Stream.generate(Math::random); 149 Streams ESTRUCTURAS DE DATOS CONCURRENTES Stream Colección Secuencia de elementos Elementos que se pueden procesar en secuencia o con acceso directo (get) Se pueden calcular según se van procesando Tienen que estar en memoria antes de procesarse Información volátil que sólo se puede procesar una vez Estructuras de datos en memoria Tamaño infinito Tamaño finito Tiene versiones eficientes para tipos primitivos (IntStream, DoubleStream, LongStream) No tiene versiones para tipos primitivos 150 Streams • Operaciones que se pueden realizar sobre un stream: 1) Operaciones intermedias Se procesan de forma perezosa (sólo si son necesarias) Puede haber varias (incluso del mismo tipo) Ejemplos: map, filter, skip,… 2) Operaciones terminales Inician la computación y devuelven un objeto, un valor, una lista o nada… Sólo puede haber una al final Una vez aplicada, el stream no se puede reutilizar Ejemplos: sum, find, min, toArray… ESTRUCTURAS DE DATOS CONCURRENTES 151 Streams • Operaciones que se pueden realizar sobre un stream: ESTRUCTURAS DE DATOS CONCURRENTES int sumOver20 = persons.stream() .map(Person::getAge) .filter(age -> age > 20) .reduce(0, Integer::sum); Operaciones intermedias Operación terminal 152 Streams • Operaciones intermedias: filter: quita algunos elementos map: por cada elemento obtiene un nuevo valor: sorted: ordena peek: aplica una operación a cada elemento distinct: filtra dejando los distintos limit: limita el número de elementos skip: ignora los primeros elementos range: devuelve un rango de los elementos (from, to) ESTRUCTURAS DE DATOS CONCURRENTES 153 Streams • Operaciones terminales en Stream<T>: count: Cuenta los elementos. min, max: Obtiene el mínimo el y máximo anyMatch, allMatch, noneMatch(): Indica si se cumple (o no) el criterio. findFirst, findAny: Devuelve el elemento que cumpla el criterio mapToInt: Convierte a IntStream toArray: Devuelve el contenido como array forEach, forEachOrdered: Ejecuta por cada elemento reduce: Mecanismo genérico de reducción de todos los elementos collect: Mecanismo genérico para “recolectar” los elementos ESTRUCTURAS DE DATOS CONCURRENTES 154 Streams • Operaciones terminales en streams numéricos (IntStream, LongStream, DoubleStream): average(): Calcula la media sum(): Suma los elementos summaryStatistics(): Calcula estadísticas de los datos (media, cuenta, min, max, sum) ESTRUCTURAS DE DATOS CONCURRENTES 155 Streams • Operación terminal Collect (Recolectar) La operación collect es un mecanismo genérico para implementar operaciones terminales Se puede implementar cualquier política de recolección de los elementos: Devolver una estructura de datos (List, Set, Map…) Fusionar todos los elementos en un String Obtener un valor (suma, media, min, max…) La clase Collectors tiene métodos estáticos para crear una infinidad de recolectores ESTRUCTURAS DE DATOS CONCURRENTES ArrayList<String> l = stream.collect(Collectors.toList()); 156 Streams • Edad media • Número de personas mayores de 20 • Persona más mayor (si existen) ESTRUCTURAS DE DATOS CONCURRENTES double avgAge = persons.stream() .collect(Collectors.averagingDouble(Person::getAge)); int num = persons.stream().filter(p -> p.getAge() > 20) .collect(Collectors.counting()); Optional<Person> older = persons.stream() .collect(Collectors.maxBy(Comparator.comparing(Person::getAge)); 157 Streams • Devolver los nombres en una lista • Devolver los nombres sin repeticiones ordenados (TreeSet) • Devolver los nombres separados por comas ESTRUCTURAS DE DATOS CONCURRENTES List<String> list = people.stream().map(Person::getName) .collect(Collectors.toList()); Set<String> set = people.stream().map(Person::getName) .collect(Collectors.toCollection(TreeSet::new)); String joined = things.stream().map(Object::toString) .collect(Collectors.joining(", ")); 158 Streams • Suma de salarios de los empleados • Agrupar por departamento • Suma de salarios por departamento ESTRUCTURAS DE DATOS CONCURRENTES int total = employees.stream() .collect(Collectors.summingInt(Employee::getSalary))); Map<Department, List<Employee>> byDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment)); Map<Department, Integer> totalByDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.summingInt(Employee::getSalary))); 159 Streams • Suma de salarios por departamento • Dividir los estudiantes entre aprobados y suspensos ESTRUCTURAS DE DATOS CONCURRENTES Map<Department, Integer> totalByDept = employees.stream() .collect( Collectors.groupingBy( Employee::getDepartment, Collectors.summingInt(Employee::getSalary) ) ); Map<Boolean, List<Student>> passingFailing = students.stream() .collect( Collectors.partitioningBy(s->s.getGrade() >= PASS_THR) ); 160 Procesamiento en paralelo de streams • La principal ventaja de los streams es que especificamos las operaciones que queremos que se realicen, pero no especificamos cómo deben realizarse. • Por defecto las operaciones se ejecutan de forma secuencial en el hilo de ejecución, pero podemos pedir que se ejecuten en paralelo STREAMS 161 Procesamiento en paralelo de streams • Partiendo de un stream se puede obtener su stream paralelo • Todas las operaciones que se ejecutarán de forma automática en paralelo, dividiendo las tareas en diferentes hilos STREAMS Stream<String> s = nombres.parallelStream(); Stream<String> s = nombres.stream().parallel(); 162 Procesamiento en paralelo de streams • Ejemplos de streams paralelos: ESTRUCTURAS DE DATOS CONCURRENTES List<Integer> even = numbers.stream() .filter(n -> n % 2 == 0) .sorted() .collect(Collectors.toList()); Secuencial 808ms Paralelo 507ms List<Integer> even = numbers.parallelStream() .filter(n -> n % 2 == 0) .sorted() .collect(Collectors.toList()); 163 Procesamiento en paralelo de streams • Por defecto se usan tantos hilos como procesadores tiene el sistema, porque se asume que las operaciones no realizan ninguna operación bloqueante • Si se quiere usar un número diferente de hilos: ESTRUCTURAS DE DATOS CONCURRENTES List<Integer> even = new ForkJoinPool(NUM_THREADS).submit(()-> numbers.stream() .filter(n -> n % 2 == 0) .sorted() .collect(Collectors.toList() ).get(); 164 Procesamiento en paralelo de streams • Cuidado al usar streams paralelos Paralelizar un algoritmo tiene un coste de gestión asociado (división del trabajo, gestión de los hilos, consolidación de resultados, etc…) Para pocos datos o para ciertos tipos de algoritmos, no merece la pena paralelizar porque sería más lento. Es importante comparar la versión secuencial y la paralela para unos datos de entrada habituales ESTRUCTURAS DE DATOS CONCURRENTES https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html Slide 1 Slide 2 Estructuras de datos concurrentes Introducción Introducción Introducción Estructuras de datos concurrentes Valores atómicos concurrentes AtomicInteger AtomicInteger Estructuras de datos concurrentes Estructuras de datos en Java Estructuras de datos en Java Estructuras de datos en Java Genéricos Genéricos Genéricos Genéricos Genéricos Genéricos Genéricos Genéricos Genéricos Genéricos Genéricos Genéricos Genéricos Genéricos Estructuras de datos en Java Listas, conjuntos y mapas Listas, conjuntos y mapas Listas, conjuntos y mapas Listas, conjuntos y mapas Slide 34 Slide 35 Slide 36 Slide 37 Listas, conjuntos y mapas Listas, conjuntos y mapas Slide 40 Listas (List<E>) Listas (List<E>) Listas (List<E>) Ejercicio 1 Conjuntos (Set<E>) Conjuntos (Set<E>) Conjuntos (Set<E>) Mapas (Map<K,V) Mapas (Map<K,V) Mapas (Map<K,V) Mapas (Map<K,V) Ejercicio 2 Estructuras de datos ordenadas Otras implementaciones Estructuras de datos en Java Recorrer una colección Recorrer una Lista Recorrer una Lista Recorrer una Lista Recorrer una Lista Recorrer una Lista Recorrer un Conjunto Recorrer un Mapa Recorrer un Mapa Recorrer un Mapa Estructuras de datos en Java Ordenación y Búsqueda Ordenación y Búsqueda Ordenación y Búsqueda Ordenación y Búsqueda Slide 71 Ordenación y Búsqueda Ordenación y Búsqueda Ordenación y Búsqueda Ejercicio 3 Estructuras de datos en Java Equals y hashcode Equals y hashcode Equals y hashcode Equals y hashcode Equals y hashcode Equals y hashcode Equals y hashcode Equals y hashcode Estructuras de datos en Java Colecciones con tipos primitivos Colecciones con tipos primitivos Colecciones con tipos primitivos Estructuras de datos en Java Ventajas de las colecciones Ventajas de las colecciones Estructuras de datos concurrentes Estructuras de datos sincronizadas Estructuras de datos sincronizadas Estructuras de datos sincronizadas Estructuras de datos sincronizadas Instrucciones atómicas Instrucciones atómicas Instrucciones atómicas Instrucciones atómicas Instrucciones atómicas Instrucciones atómicas Problemas con las colecciones sincronizadas Estructuras de datos concurrentes Estructuras de datos concurrentes Estructuras de datos concurrentes ConcurrentHashMap ConcurrentHashMap ConcurrentHashMap ConcurrentHashMap CopyOnWriteArrayList Estructuras de datos concurrentes Estructuras de datos concurrentes Ejercicio 4 Ejercicio 4 Estructuras de datos concurrentes Colas (Queues) Colas (Queues) Colas (Queues) Colas (Queues) Colas (Queues) Colas (Queues) Colas bloqueantes (BlockingQueue) Colas bloqueantes (BlockingQueue) Colas bloqueantes (BlockingQueue) Colas dobles (Deque) Slide 127 Implementaciones de Queue Implementaciones de Deque Implementaciones de BlockingQueue Implementaciones de BlockingQueue Implementaciones Uso de las colas Ejercicio 5 Ejercicio 5 Ejercicio 5 Ejercicio 5 Estructuras de datos concurrentes Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Streams Procesamiento en paralelo de streams Procesamiento en paralelo de streams Procesamiento en paralelo de streams Procesamiento en paralelo de streams Procesamiento en paralelo de streams __MACOSX/Academia/Tema5/._Tema 5.6 - Estructuras de datos concurrentes.pdf Academia/Tema5/.DS_Store __MACOSX/Academia/Tema5/._.DS_Store Academia/Tema5/Tema 5.1 - Introducción updated.pdf Programación Concurrente en Java Programación Concurrente – Tema 5 Miguel Ángel Rodríguez García Carlos Grima Lucía Serrano Luján Universidad Rey Juan Carlos Curso 2017 -‐ 2018 Introducción Programación Concurrente en Java -‐ Tema 5.1 Introducción •Modelo de concurrencia § Java ofrece un modelo de concurrencia de memoria compartida integrado en el lenguaje Java y en la librería estándar § El soporte de concurrencia ha evolucionado mucho en Java § En las últimas versiones existen herramientas de alto nivel en la librería estándar que permiten al desarrollador abstraerse de detalles de bajo nivel Tema 5 -‐ Programación Concurrente en Java 3 PROGRAMACIÓN CONCURRENTE EN JAVA Introducción •La concurrencia también está integrada en el propio lenguaje de programación § Está definido cómo se comparte la memoria entre diferentes hilos en el Modelo de Memoria de Java [1] (Java Memory Model) § Existen palabras reservadas en el propio lenguaje relacionadas con la concurrencia, por ejemplo para delimitar zonas de exclusión mutua se usa synchronized Tema 5 -‐ Programación Concurrente en Java 4 PROGRAMACIÓN CONCURRENTE EN JAVA [1] http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-‐133-‐faq.html Introducción •También tiene soporte en la librería estándar § Como Java es un lenguaje orientado a objetos, los hilos se representan como objetos de la clase java.lang.Thread § Esta clase tiene métodos para controlar el ciclo de vida del hilo (configurar su nombre, prioridad, iniciar, esperar a que finalice, etc…) § En Java los hilos se pueden crear y destruir en cualquier momento de la ejecución del programa, no tienen que seguir la rígida estructura de los programas de SimpleConcurrent Tema 5 -‐ Programación Concurrente en Java 5 PROGRAMACIÓN CONCURRENTE EN JAVA Historia de la Concurrencia en Java •En las sucesivas versiones de la plataforma Java el soporte de programación concurrente ha avanzado considerablemente •Es muy importante tener en cuenta la evolución de las herramientas de concurrencia en Java cuando se revise documentación (para evitar que esté obsoleta) Tema 5 -‐ Programación Concurrente en Java 6 INTRODUCCIÓN Historia de la Concurrencia en Java Tema 5 -‐ Programación Concurrente en Java 7 INTRODUCCIÓN Año Versión Java Concurrencia 1995 Java 1.02 Hilos y monitores (exclusión mutua y sinc condicional) 1996 Java 1.1 1998 Java 2 1.2 Estructuras de datos concurrentes. Se desaconsejó el uso de ciertas funciones de gestión de hilos por ser inseguras (stop, pause, resume) 2000 Java 2 1.3 2002 Java 2 SE 1.4 2004 Java 2 SE 1.5 o 5 Muchas mejoras en concurrencia: Java Memory Model, Ejecutores, Colas de procesamiento, Temporizadores, Mejores estructuras de datos concurrentes 2006 Java SE 6 Colas de procesamiento mejoradas 2011 Java SE 7 Framework Fork/Join 2014 Java SE 8 Mejoras en estructuras de datos concurrentes, lambdas, estructuras de datos con procesamiento funcional (declarativo) y ejecución paralela, Futuros mejorados 2017 ..Java SE 8 … se continúa actualizando. Por ejemplo MAX_LOCKS àMáximo de bloqueos simultáneos que FileHandler puede manejar Introducción •En java se pueden usar otros modelos de concurrencia con librerías § Actores: • Akka: http://akka.io/ •Quasar: http://www.paralleluniverse.co/quasar/ § Memoria software transaccional: •Multiverse: http://multiverse.codehaus.org § Comunicando procesos secuenciales (CSP): • JCSP: http://www.cs.kent.ac.uk/projects/ofa/jcsp/ § Programación funcional: • Framework Collections en Java 8: http://java.dzone.com/articles/title-‐devoxx-‐2012-‐java-‐8 • RxJava: https://github.com/Netflix/RxJava Tema 5 -‐ Programación Concurrente en Java 8 PROGRAMACIÓN CONCURRENTE EN JAVA Introducción •Colección de libros recomendables para dominar la programación concurrente, especialmente para sistemas de memoria compartida Tema 5 -‐ Programación Concurrente en Java 9 PROGRAMACIÓN CONCURRENTE EN JAVA http://software.intel.com/en-‐us/articles/technical-‐books-‐for-‐multi-‐core-‐software-‐developers/ (y muchos más…) 2006 20041999 __MACOSX/Academia/Tema5/._Tema 5.1 - Introducción updated.pdf Academia/Tema5/Tema 5.8 - Ejecución concurrente y asíncrona de tareas.pdf Programación Concurrente Tema 5 Programación Concurrente en Java Micael Gallego micael.gallego@urjc.es @micael_gallego mailto:micael.gallego@urjc.es Programación Concurrente Tema 5.6 Ejecución concurrente y asíncrona de tareas Micael Gallego micael.gallego@urjc.es @micael_gallego mailto:micael.gallego@urjc.es 3 Ejecución de tareas en hilos • Ejecución de tareas • Ejecución secuencial de tareas • Ejecución de tareas en un nuevo hilo • Framework de ejecución de tareas • Ejecución de tareas con grupos de hilos • Ejecución asíncrona de tareas • Conclusiones PROGRAMACIÓN CONCURRENTE EN JAVA 4 Ejecución de tareas • La mayoría de las aplicaciones concurrentes se estructuran en base a la ejecución de tareas • Las tareas son unidades de trabajo abstractas e independientes entre sí • La división en tareas tiene las siguientes ventajas: Simplifica la organización del programa Facilita la recuperación de errores Promueve la concurrencia porque permite paralelizar la ejecución de tareas EJECUCIÓN DE TAREAS EN HILOS 5 Ejecución de tareas • El ámbito de cada tarea tiene que seleccionarse de forma adecuada • Las tareas deberían ser lo más independientes entre sí. Una tarea no debería depender del estado, el resultado o lo que realice otra tarea • La independencia favorece la ejecución en paralelo (aprovechamiento de recursos) • Para favorecer la escalabilidad y el balanceo de carga, las tareas no deberían ser muy grandes EJECUCIÓN DE TAREAS EN HILOS 6 Ejecución de tareas • En las aplicaciones web se considera que atender a una petición de un usuario representa una tarea • Las tareas se ejecutan concurrentemente: Si una tarea tarda mucho en procesarse, no afecta a los demás usuarios Si existen varios procesadores, se podrán repartir el trabajo entre ellos cuando varios usuarios hagan peticiones concurrentes EJECUCIÓN DE TAREAS EN HILOS 7 Ejecución de tareas en hilos • Ejecución de tareas • Ejecución secuencial de tareas • Ejecución de tareas en un nuevo hilo • Framework de ejecución de tareas • Ejecución de tareas con grupos de hilos • Ejecución asíncrona de tareas • Conclusiones PROGRAMACIÓN CONCURRENTE EN JAVA 8 Ejecución secuencial de tareas • Existen varias políticas para ejecutar las tareas de una aplicación • La forma más sencilla de ejecutar tareas es hacerlo de forma secuencial • Una aplicación que ejecuta tareas de forma secuencial sólo necesita un único hilo de ejecución (single thread) EJECUCIÓN DE TAREAS EN HILOS 9 Ejecución secuencial de tareas • Ejecución secuencial EJECUCIÓN DE TAREAS EN HILOS class ServidorWebUnicoHilo { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { Socket conexion = socket.accept(); procesarPeticion(conexion); } } } Se bloquea a la espera de una nueva petición http 10 Ejecución secuencial de tareas • El problema con esta solución es que el servidor no puede aceptar una nueva conexión mientras está procesando la petición actual • Además, si el servidor tiene varios procesadores o atender la petición requiere acceso de entrada/salida se estarán desaprovechando los recursos del sistema EJECUCIÓN DE TAREAS EN HILOS 11 Ejecución de tareas en hilos • Ejecución de tareas • Ejecución secuencial de tareas • Ejecución de tareas en un nuevo hilo • Framework de ejecución de tareas • Ejecución de tareas con grupos de hilos • Ejecución asíncrona de tareas • Conclusiones PROGRAMACIÓN CONCURRENTE EN JAVA 12 Ejecución de tareas en un nuevo hilo • Para solucionar el problema, se puede ejecutar cada tarea en su propio hilo de ejecución • De esa forma se pueden aceptar nuevas conexiones aunque se esté procesando la petición actual • Se aprovechan mejor los recursos cuando se producen peticiones concurrentes EJECUCIÓN DE TAREAS EN HILOS 13 Ejecución de tareas en un nuevo hilo • Nuevo hilo por cada tarea EJECUCIÓN DE TAREAS EN HILOS class ServidorWebHiloPorTarea { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { Socket conexion = socket.accept(); Runnable tarea = ()-> procesarPeticion(conexion); new Thread(tarea).start(); } } } Se crea un hilo que ejecuta la tarea para atender la petición 14 Ejecución de tareas en un nuevo hilo • Hay que tener en cuenta que ahora se pueden estar procesando varias peticiones de forma concurrente • Si se comparte algún objeto al procesar la petición, ese objeto debe ser thread-safe o estar apropiadamente sincronizado EJECUCIÓN DE TAREAS EN HILOS 15 Ejecución de tareas en un nuevo hilo • Problemas con la creación ilimitada de hilos Si existen pocas peticiones concurrentes, la creación de un hilo por petición puede ser aceptable Pero para un sistema en producción con mucha carga presenta varios problemas: La creación de un hilo es costosa Cada hilo activo consume memoria (para la pila de ejecución) y tiempo de CPU en cambios de contexto Está limitado el número máximo de hilos (depende del sistema operativo, JVM, configuraciones, etc…) EJECUCIÓN DE TAREAS EN HILOS 16 Ejecución de tareas en un nuevo hilo • Problemas con la creación ilimitada de hilos La aplicación puede funcionar hasta cierto límite y alcanzado ese límite puede fallar repentinamente por problemas de memoria Es mejor que se degrade el rendimiento de forma gradual y en un punto determinado que no se pueda atender a nuevos clientes EJECUCIÓN DE TAREAS EN HILOS 17 Ejecución de tareas en hilos • Ejecución de tareas • Ejecución secuencial de tareas • Ejecución de tareas en un nuevo hilo • Framework de ejecución de tareas • Ejecución de tareas con grupos de hilos • Ejecución asíncrona de tareas • Conclusiones PROGRAMACIÓN CONCURRENTE EN JAVA 18 Framework de ejecución de tareas • Las tareas son unidades lógicas de trabajo, y los hilos permiten ejecutar las tareas de forma asíncrona • Ni la ejecución secuencial de tareas ni la creación de un hilo por tarea son soluciones aceptables • La solución es limitar el número máximo de hilos posibles y reutilizar los hilos en vez de crear uno nuevo por cada tarea EJECUCIÓN DE TAREAS EN HILOS 19 Framework de ejecución de tareas • La biblioteca estándar de Java dispone de un interfaz para ejecutar tareas llamado Executor • Existen varias clases que implementan el interfaz con diferentes políticas de ejecución EJECUCIÓN DE TAREAS EN HILOS package java.util.concurrent; public interface Executor { void execute(Runnable task); } 20 Framework de ejecución de tareas • Si se quisiera, se podrían implementar ejecutores que se comportaran como los ejemplos anteriores EJECUCIÓN DE TAREAS EN HILOS public class HiloPorTareaExecutor implements Executor { public void execute(Runnable tarea) { new Thread(tarea).start(); }; } public class TareasSecuenciaExecutor implements Executor { public void execute(Runnable tarea) { tarea.run(); }; } 21 Framework de ejecución de tareas EJECUCIÓN DE TAREAS EN HILOS class ServidorWebHiloPorTarea { private static Executor executor = new HiloPorTareaExecutor(); public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { Socket conexion = socket.accept(); Runnable tarea = ()-> procesarPeticion(conexion); executor.execute(tarea); } } } Usamos el ejecutor para que la política de ejecución concreta la decida el ejecutor 22 Framework de ejecución de tareas • Las tareas son unidades lógicas de trabajo, y los hilos permiten ejecutar las tareas de forma asíncrona • Ni la ejecución secuencial de tareas ni la creación de un hilo por tarea son soluciones aceptables • La solución es limitar el número máximo de hilos posibles y reutilizar los hilos en vez de crear uno nuevo por cada tarea EJECUCIÓN DE TAREAS EN HILOS 23 Framework de ejecución de tareas • La ventaja de usar un ejecutor es que se puede elegir de forma sencilla la política de ejecución Cuántas tareas concurrentemente (número máximo de hilos) Reutilización de hilos para varias tareas (evita crear un hilo por tarea) En qué orden se deben ejecutar (FIFO, prioridad) Cuántas tareas pendientes de ejecución … EJECUCIÓN DE TAREAS EN HILOS 24 Framework de ejecución de tareas • No existe una política de ejecución adecuada para todos los casos • Depende de: Los recursos disponibles (número de cores, memoria) Si las tareas hacen mucho uso de entrada/salida bloqueante Si es preferible reducir el rendimiento para un cliente o bien rechazar a nuevos clientes EJECUCIÓN DE TAREAS EN HILOS 25 Ejecución de tareas en hilos • Ejecución de tareas • Ejecución secuencial de tareas • Ejecución de tareas en un nuevo hilo • Framework de ejecución de tareas • Ejecución de tareas con grupos de hilos • Ejecución asíncrona de tareas • Conclusiones PROGRAMACIÓN CONCURRENTE EN JAVA 26 Ejecución de tareas con grupos de hilos • Grupos de hilos (Thread Pools) Executors implementados con un conjunto de hilos que ejecutan las tareas Las tareas están en una cola y los hilos las ejecutan según van llegando. Cuando un hilo termina de ejecutar una tarea, selecciona la siguiente tarea de la cola. Si no hay, se bloquea a la espera de una nueva Siguen un esquema productor / consumidor EJECUCIÓN DE TAREAS EN HILOS 27 Ejecución de tareas con grupos de hilos • Grupos de hilos (Thread Pools) Reutilizar un hilo reduce el tiempo de creación y los recursos de CPU asociados a costa de algo de memoria Se inicia la ejecución de las tareas más rápido (responsiveness) Es una forma de limitar el número máximo de hilos ejecutándose a la vez y reduce el riesgo de sobrecarga EJECUCIÓN DE TAREAS EN HILOS 28 Ejecución de tareas con grupos de hilos • Grupos de hilos (Thread Pools) EJECUCIÓN DE TAREAS EN HILOS Executor executor = Executors.newFixedThreadPool(10); Runnable tarea = ...; executor.execute(tarea); Conjunto fijo de hilos. Si no hay hilo disponible, la tarea se encola y se espera hasta que haya un hilo disponible 29 Ejecución de tareas con grupos de hilos ● Grupos de hilos (Thread Pools) (métodos estáticos de la clase Executors) ● newFixedThreadPool: Inicialmente crea un conjunto de hilos fijo para ejecutar las tareas. Si no hay hilo disponible, la tarea se encola. ● newCachedThreadPool: Cada tarea se ejecuta en un hilo. Si no existe ninguno libre, se crea uno. Un hilo permanece libre durante cierto tiempo, si no se usa, se elimina. ● newSingleThreadExecutor: Un único hilo de ejecución para las tareas (las tareas se ejecutarán de forma secuencial) ● newScheduledThreadPool: Ejecuta tareas a intervalos regulares de tiempo (p.e.: Ejecutar esta tarea cada 2 segundos). EJECUCIÓN DE TAREAS EN HILOS 30 Ejecución de tareas con grupos de hilos • Ciclo de vida de los executors Los executors deben finalizarse de forma adecuada (para que la aplicación pueda finalizar su ejecución) Como las tareas se ejecutan de forma asíncrona, en un momento dado las tareas pueden estar: En ejecución A la espera Ejecutadas EJECUCIÓN DE TAREAS EN HILOS 31 Ejecución de tareas con grupos de hilos • Ciclo de vida de los executors Se puede finalizar un executor con más o menos “delicadeza”. Hay diferentes opciones entre los extremos: Shutdown: Ejecutar las tareas enviadas pero no aceptar nuevas tareas. Shutdown now: Finalizar (interrumpir) todas las tareas en ejecución y descartar las tareas previamente enviadas. EJECUCIÓN DE TAREAS EN HILOS 32 Ejecución de tareas con grupos de hilos • Ciclo de vida de los executors Se controla mediante el interfaz ExecutorService EJECUCIÓN DE TAREAS EN HILOS public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; // Otros métodos... } Terminación ordenada Terminación forzada 33 Ejecución de tareas con grupos de hilos • Ciclo de vida de los executors Se controla mediante el interfaz ExecutorService EJECUCIÓN DE TAREAS EN HILOS public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; // Otros métodos... } En proceso de terminación? 34 Ejecución de tareas con grupos de hilos • Ciclo de vida de los executors Se controla mediante el interfaz ExecutorService EJECUCIÓN DE TAREAS EN HILOS public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; // Otros métodos... } Terminado? 35 Ejecución de tareas en hilos • Ejecución de tareas • Ejecución secuencial de tareas • Ejecución de tareas en un nuevo hilo • Framework de ejecución de tareas • Ejecución de tareas con grupos de hilos • Ejecución asíncrona de tareas • Conclusiones PROGRAMACIÓN CONCURRENTE EN JAVA 36 Ejecución asíncrona de tareas • Hasta ahora hemos visto que un código envía una tarea para su ejecución y se olvida de ella • Hay veces que se quiere tener un mayor control de la tarea que se ha enviado al executor: Esperar a que termine (o hasta un timeout) Obtener un valor obtenido por la tarea Gestionar una excepción producida en la tarea EJECUCIÓN DE TAREAS EN HILOS 37 Ejecución asíncrona de tareas • Si se quiere tener un mayor control sobre las tareas deben implementan Callable<V> (en vez de Runnable) • Los executors tienen el método submit(Callable<V> task) EJECUCIÓN DE TAREAS EN HILOS public interface Callable<V> { V call() throws Exception; } 38 Ejecución asíncrona de tareas • Además, el método submit(Callable<V> c) devuelve un objeto de la clase Future<V> para controlar el estado de la tarea EJECUCIÓN DE TAREAS EN HILOS Callable<String> tarea = new DescargaTweets(); Future<String> f = executor.submit(tarea); try { String tweets = f.get(); // Procesar los tweets... } catch (ExecutionException e) { Throwable cause = e.getCause(); // Tratar la excepción producida en la tarea } 39 Ejecución asíncrona de tareas EJECUCIÓN DE TAREAS EN HILOS public interface Future<V> { V get() throws InterruptedException, ExecutionException, CancellationException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, CancellationException, TimeoutException; boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); } Acceder al valor obtenido por la tarea 40 Ejecución asíncrona de tareas EJECUCIÓN DE TAREAS EN HILOS public interface Future<V> { V get() throws InterruptedException, ExecutionException, CancellationException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, CancellationException, TimeoutException; boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); } Cancelar la tarea (indicando si debe interrumpir si se está ejecutando) 41 Ejecución asíncrona de tareas EJECUCIÓN DE TAREAS EN HILOS public interface Future<V> { V get() throws InterruptedException, ExecutionException, CancellationException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, CancellationException, TimeoutException; boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); } Consulta del estado de la tarea 42 Ejecución asíncrona de tareas • Hay veces que controlar a una única tarea no es suficiente. Es necesario gestionar un conjunto de tareas a la vez • Por ejemplo, si queremos descargar contenido de twitter, facebook, tuenti, instagram a la vez y mostrar el contenido según va llegando tenemos que gestionar todas las descargas de forma conjunta EJECUCIÓN DE TAREAS EN HILOS 43 Ejecución asíncrona de tareas • Para gestionar varias tareas a la vez se usa un ExecutorCompletionService. Se pueden enviar tareas (como cualquier executor) Dispone del método take() para obtener el Future<V> de las tareas según van completando Tiene internamente una cola (queue) con los resultados de las tareas creando un esquema productor-consumidor EJECUCIÓN DE TAREAS EN HILOS 44 Ejecución asíncrona de tareas EJECUCIÓN DE TAREAS EN HILOS ExecutorService executor = Executors.newFixedThreadPool(10); CompletionService<String> completionService = new ExecutorCompletionService<>(executor); completionService.submit(new ConsultaTwitterTask()); completionService.submit(new ConsultaFacebookTask()); completionService.submit(new ConsultaInstagramTask()); for (int i = 0, i = 3; i++) { try { Future<String> f = completionService.take(); String info = f.get(); procesarInfo(info); } catch (ExecutionException e) { throw e.getCause(); } catch (Exception e){...} } Creación de un ExecutorCompletionService usando un executor creado previamente. 45 Ejecución asíncrona de tareas EJECUCIÓN DE TAREAS EN HILOS ExecutorService executor = Executors.newFixedThreadPool(10); CompletionService<String> completionService = new ExecutorCompletionService<>(executor); completionService.submit(new ConsultaTwitterTask()); completionService.submit(new ConsultaFacebookTask()); completionService.submit(new ConsultaInstagramTask()); for (int i = 0, i = 3; i++) { try { Future<String> f = completionService.take(); String info = f.get(); procesarInfo(info); } catch (ExecutionException e) { throw e.getCause(); } catch (Exception e){...} } Creación de las tareas y envío al executor 46 Ejecución asíncrona de tareas EJECUCIÓN DE TAREAS EN HILOS ExecutorService executor = Executors.newFixedThreadPool(10); CompletionService<String> completionService = new ExecutorCompletionService<>(executor); completionService.submit(new ConsultaTwitterTask()); completionService.submit(new ConsultaFacebookTask()); completionService.submit(new ConsultaInstagramTask()); for (int i = 0, i = 3; i++) { try { Future<String> f = completionService.take(); String info = f.get(); procesarInfo(info); } catch (ExecutionException e) { throw e.getCause(); } catch (Exception e){...} } Obtención del Future<V> que permite acceder al valor de la tarea que acaba de terminar 47 Ejecución asíncrona de tareas EJECUCIÓN DE TAREAS EN HILOS ExecutorService executor = Executors.newFixedThreadPool(10); CompletionService<String> completionService = new ExecutorCompletionService<>(executor); completionService.submit(new ConsultaTwitterTask()); completionService.submit(new ConsultaFacebookTask()); completionService.submit(new ConsultaInstagramTask()); for (int i = 0, i = 3; i++) { try { Future<String> f = completionService.take(); String info = f.get(); procesarInfo(info); } catch (ExecutionException e) { throw e.getCause(); } catch (Exception e){...} } Uso del valor 48 Ejecución de tareas en hilos • Ejecución de tareas • Ejecución secuencial de tareas • Ejecución de tareas en un nuevo hilo • Framework de ejecución de tareas • Ejecución de tareas con grupos de hilos • Ejecución asíncrona de tareas • Conclusiones PROGRAMACIÓN CONCURRENTE EN JAVA 49 Conclusiones • En general no es buena idea gestionar los hilos directamente, es mejor utilizar un executor. • En vez de hilos que ejecutan infinitamente, hay que diseñar el programa en base a tareas: Acotadas en el tiempo Independientes entre sí EJECUCIÓN DE TAREAS EN HILOS 50 Conclusiones • Habitualmente el pool de hilos (ThreadPool)