Sie sind auf Seite 1von 7
UNIVERSIDAD NACIONAL JORGE BASADRE GROHMANN | FACULTAD DE INGENIERIA, ESCUELA ACADEMICO PROFESIONAL DE INGENIERIA EN INFORMATICA Y SISTEMAS (CURSO: PROGRAMACION PARALELA [ARO DE ESTUDIOS: SEGUNDO. LABORATORIO N° 12 “THREADS — DATOS COMPARTIDOS” 1. OBJETIVOS Conocer el funcionamiento de los hilos en C++ 11 Implementar semaforos en C++ 11 2. FUNDAMENTO TEORICO En el fundamento tedrico debe averiguar sobre los siguientes temas: cH Tt Compilador GCC. Code Blocks. ‘Semaforo binario. 3. PROCEDIMIENTO En la practica anterior, se estudié cémo inicializar hilos para ejecutar algun cédigo en paralelo. En ‘esos ejemplos, todos los cédigos ejecutados en los hilos eran totalmente independientes. En programas mas complejos, a menudo se utilizan datos compartidos entre los hilos. Y cuando esto se hace, nos enfrentaremos un problema el cual ya hemos tratado ampliamente: la sincronizacién. Estudiaremos este problema programando en C++ 11 los siguientes trozos de cédigo: PROBLEMAS DE SINCRONIZACION Como ejemplo, programaremos una estructura simple a la que llamaremos Cont. Esta estructura tiene datos y métodos para aumentar o disminuir un valor determinado, struct Cont { int valor; Cont() { valor = 83 } //constructor void increnentar() { valor++5 } % Ahora crearemas algunos hilos que realizaran incrementos. Para usar la clase vector, debe afiadir a la cabecera de su cédigo fuente #tinclude . int main) { Cont contador; //instancia de la estructura Cont std::vector hilos; for(int i= 0; i < 53 i++) { hilos.push_back(std: :thread([&contador]() { for(int i = 0; i < 10000; i++) { contador.incrementar(); } y)s + for(auto& thread : hilos){ thread. join(); Jing. Hugo Manuel Barraza Vizcarra wT UNIVERSIDAD NACIONAL JORGE BASADRE GROHMANN | FACULTAD DE INGENIERIA, ESCUELA ACADEMICO PROFESIONAL DE INGENIERIA EN INFORMATICA Y SISTEMAS (CURSO: PROGRAMACION PARALELA [ARO DE ESTUDIOS: SEGUNDO. y std::cout << contador.valor << std::endl; return 05 } Inicializamos 5 hilos y cada uno realiza el incremento de valor 10 000 veces. Después que todos los hilos terminaron su trabajo, imprimimos el valor del contador. Si lanzamos este programa, debemos esperar que se imprimiré 50 000. Pero este no es el caso, Nadie puede decir lo que va a imprimir este programa. Experimentaci6n: Ejecuta 10 veces la aplicacién y anota los resultados y sus tiempos de ejecucién. El problema es que el incremento no es una operacién atémica. Como cuestin de hecho, esl incremento esté compuesto de tres operaciones: Lea el valor actual del valor. Afiadir k al valor actual Escribe que el nuevo valor de valor. Al ejecutar ese cédigo utllizando un solo hilo, no hay problemas. Se ejecutard cada parte de la ‘operacién de uno tras otro. Pero cuando se tiene varios hilos, usted puede comenzar a tener problemas. Imaginese esta situacién 4, Hilo 1: leer el valor, obtener 0, afiadir 1, por lo que el valor 2. Hilo 2: leer el valor, obtener 0, afiadir 1, por lo que el valor = 3. Hilo 1: 1 para escribir el valor del campo y volver 1 4, Hilo 2: escribir 1 para el valor del campo y devolver 1 Estas situaciones vienen de lo que llamamos el interleaving (entrelazado). interleaving describe las posibles situaciones de varios hilos estén ejecutando algunas sentencias. Incluso para tres ‘operaciones y dos hilos, hay una gran cantidad de posibles interleavings. Cuando usted tiene mas hilos y mas operaciones, es casi imposible enumerar los posibles interleavings. El problema puede también ocurrir cuando un hilo se adelanté entre las instrucciones de la operacién, Para solucionar este problema, utlizaremos seméforos, Utilizaremos un tipo especial de semaforo que soporta C++ 11 llamado mutex. Mutex es un objeto. Solo un hilo puede bloquearse en un mutex al mismo tiempo. Esta propiedad simple, pero potente, de mutex nos permite usarlo para salvar los problemas de sincronizacién. UTILIZANDO MUTEX En la librerfa threads de C+#11, la clase que representa a un mutex es la clase std: :mutex. Para uitiizarlo debe afiadir en la cabecera del cédigo fuente #includecmutex>. Hay dos métodos importantes en un mutex: lock() y unlock(). Como sus nombres lo indican, el primero bloquea un hilo, y el segundo lo libera del bloqueo, Para hacer que nuestra estructura Cont, tenemos que afiadir la sentencia std: :mutex y luego las sentencias lock() y unlock() ena seccién critica de la estructura struct Cont { std :: mutex mtx; int valor Cont() { valor = 93 } void incrementar() { mtx. lock()5 Jing. Hugo Manuel Barraza Vizcarra aT UNIVERSIDAD NACIONAL JORGE BASADRE GROHMANN | FACULTAD DE INGENIERIA, ESCUELA ACADEMICO PROFESIONAL DE INGENIERIA EN INFORMATICA Y SISTEMAS (CURSO: PROGRAMACION PARALELA [ARO DE ESTUDIOS: SEGUNDO. valores mtx.unlock()5 on Si probamos ahora esta aplicacién con el mismo cédigo, el programa siempre muestra 50 000 EXCEPCIONES Y BLOQUEOS Ahora, vamos a ver lo que sucede en otro caso. Imaginese que el contador tiene una operacién de disminucién que se produce una excepcién si el valor es 0: struct Cont { int valors Cont() { valor = 0; } void incrementar() { valors+s ) void decrementar() if(valor == 0) { throw "El valor no puede se que 0"3 > valor--5 eQuiere tener varios accesos a esta estructura al mismo tiempo sin modificar la clase. Crearemos Una estructura adicional con bloqueos para esta clase. Struct Concurrentcont { std::mutex mtxs Cont contador void incrementar() { mtx. Lock()5 contador. incrementar(); mtx.unlock() 5 } void decrementar() { mtx. lock()5 contador .decrementar(); mtx. unlock() 5 } nn Esta nueva estructura trabaja bien para la mayoria de los casos, pero cuando una excepcién acurre en el método decrementar, se presenta un gran problema. De hecho, cuando una excepcién ocurre, Jing. Hugo Manuel Barraza Vizcarra a7 UNIVERSIDAD NACIONAL JORGE BASADRE GROHMANN | FACULTAD DE INGENIERIA, ESCUELA ACADEMICO PROFESIONAL DE INGENIERIA EN INFORMATICA Y SISTEMAS (CURSO: PROGRAMACION PARALELA [ARO DE ESTUDIOS: SEGUNDO. unlock() no es llamado, y el bloqueo no se libera. Como consecuencia el programa se queda completamente bloqueado. Para solucionar este problema, se debe usar una estructura try/catch. ‘void decrementar() { mtx. lock()s try { + catch (ste { contador.decrementar(); string e) mtx unlock()5 throw e5 mtx.unlock()5 } El cédigo anterior no es dificil, pero empieza a complicarse. Ahora imagine que usted esta en una funcién con 10 puntos de salida diferentes. Se deberd llamar a unlock() por cada uno de estos puntos y la probabilidad de que olvidar uno es grande. Ain mas grande es el riesgo de que no se agregue una llamada a unlock() cuando se agrega un nuevo punto de salida a una funcién. GESTION AUTOMATICA DE BLOQUEOS Cuando se quiere proteger a toda un bloque de cédigo (una funcién en nuestro caso, pero puede ser dentro de un bucle u otra estructura de control), existe una buena solucién para evitar olvidarse de liberar el bloqueo: std :: lock_guard. Esta clase es un gestor inteligente simple para bloqueos. Cuando se crea el std :: lock guard, automaticamente se llama a lock() en mutex. Cuando la proteccién se destruye, eso también libera el bloqueo. ‘Struct Concurrentsafecont { st Cont contador; void incrementar(){ std::1ock_guard guard(mtx) contador. incrementar(); + void decrementar(){ std::1ock_guard guard(mtx) contador.decrementar() 5 ‘ 4, ACTIVIDADES A, {Qué es sincronizaci6n? ZQué es exclusi6n mutua? B. ZQué es una operacién atémica? . En un supuesto que tenemos varios terminales que comparten una impresora, programar el acceso a N impresoras usando seméforos, D. Afiadir un seméforo al siguiente programa para que siempre escriba 100000. int ny void inc() Jing. Hugo Manuel Barraza Vizcarra aT UNIVERSIDAD NACIONAL JORGE BASADRE GROHMANN | FACULTAD DE INGENIERIA, ESCUELA ACADEMICO PROFESIONAL DE INGENIERIA EN INFORMATICA Y SISTEMAS (CURSO: PROGRAMACION PARALELA [ARO DE ESTUDIOS: SEGUNDO. t for (int ist; i¢=500005 i++) « y y int main() { thread t1(inc); thread t2(inc); 41. join(); t2.join(); coutecns return 03 } E. Programa el siguiente cédigo y adjunte capturas de pantalla. include cout Hinclude mutex Hinclude ‘thread std: :mutex mtx; // mutex para exclusion mutua void bloque_impr (int n, char c) { J/ seccion critica mtx.lock()s for (int i=03 dens i++) { std::cout << ¢3 } Std::cout << '\n'5 mtx.unlock()5 } int main () { std::thread th1 (bloque_impr,50,'*")5 std::thread th2 (bloque_impr,50,'%')5 thi. join(); ‘th2. join(); return 2; } Responda: Qué es lo que realiza el programa? Para qué sirven las funciones lock? y unlock()? Qué significa std? ZEs posible omitirio? F. Programa el siguiente c6 10 @ interprétalo, 77 Ejenplo de Productor Consumidor en C++ 11 #include #include #include #include Jing. Hugo Manuel Barraza Vizcarra UNIVERSIDAD NACIONAL JORGE BASADRE GROHMANN | FACULTAD DE INGENIERIA, ESCUELA ACADEMICO PROFESIONAL DE INGENIERIA EN INFORMATICA Y SISTEMAS (CURSO: PROGRAMACION PARALELA [ARO DE ESTUDIOS: SEGUNDO. std::mutex mtx; std: :condition variable cv; int comida = @; /* Consumidor */ void mesero(int numpedido) { std: :unique_lock«std: :mutex> Ick(mtx) while(comida == 0) cv.wait(1ck); std::cout << "Pedido "s std::cout << numpedido + 1 << " siendo atendido con "3 std::cout << comida - 1 << " comidas ya esta listo." << std::endl; comida--5 } 7* Productor */ void hacercomida(int numpedido) { std: :unique_lock 1ck(mtx) 5 comidat+5 cv.notify_one(); + int main() { std::thread chefs[10]; std::thread meseros[10]; /* Inicializando clientes and chefs */ for (int pedido = 0; pedido < 103 pedido++) chefs[pedido] = std: meseros[pedido] = sti hread(hacercomida, pedido); thread(mesero, pedido) ; } /* Uniendo los hilos*/ for (int pedido = 25 pedido < 10; pedido++) { meseros[pedido]. join(); chefs [pedido] . join(); } return 0 } Responda’ Qué es lo que realiza el programa? zCémo y para que se esta usando el seméforo mtx en el programa? ZQué es std: :condition_variable? Para qué se usa en el programa? 5. INDICACIONES Para la elaboracién de su informe de laboratorio, tome en cuenta as siguientes indicaciones: ‘a. El informe se presenta de manera individual y se debe subir antes de la fecha y hora indicada en la clase. b. Se sugiere que use el siguiente esquema: * Caratula, Jing. Hugo Manuel Barraza Vizcarra o7 UNIVERSIDAD NACIONAL JORGE BASADRE GROHMANN | FACULTAD DE INGENIERIA, ESCUELA ACADEMICO PROFESIONAL DE INGENIERIA EN INFORMATICA Y SISTEMAS (CURSO: PROGRAMACION PARALELA [ARO DE ESTUDIOS: SEGUNDO. + Objetivos. + Fundamento teérico. + Procedimiento y desarrollo. + Actividades + Conclusiones. + Referencias bibliogréficas. c. En su carétula, considere como minimo los siguientes puntos: Como cabecera: Nombre de la Universidad / Facultad / Escuela. Namero y nombre del informe de laboratorio. Nombre del curso / Nombre del docente. Nombre del estudiante / Cédigo universitaro. ‘Afio / Seccién | Horario, Fecha de elaboracién. (Dia que se realizé el laboratorio) Fecha de entrega del informe. (Dia que debe presentar el informe) d. Debe ampliar el fundamento tedrico de esta practica. Use como minimo 3 fuentes bibliograficas y citelas en su fundamento teérico y on las referencias bibliograficas. Debe usar el estilo ‘APA para citar material bibliografico. . Debe enumerar las figuras y tablas de su fundamento teérico (si las tiene) Todo el texto de su informe debe estar con alineacién justificada (excepto titulos, figuras y tablas). El texto debe estar con sangrias dependiendo del nivel de titulo en que se encuentre. Todas las paginas de su informe deben estar numeradas. 9. La cantidad de conclusiones debe ser la misma que la cantidad de objetivos y deben estar estrechamente relacionadas entre ellas. Jing. Hugo Manuel Barraza Vizcarra wT

Das könnte Ihnen auch gefallen