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 wTUNIVERSIDAD 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 aTUNIVERSIDAD 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 a7UNIVERSIDAD 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 aTUNIVERSIDAD 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 VizcarraUNIVERSIDAD 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 o7UNIVERSIDAD 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