La descarga está en progreso. Por favor, espere

La descarga está en progreso. Por favor, espere

Desarrollador Profesional de Juegos Programación III Unidad II Trabajando con bloqueo de datos.

Presentaciones similares


Presentación del tema: "Desarrollador Profesional de Juegos Programación III Unidad II Trabajando con bloqueo de datos."— Transcripción de la presentación:

1 Desarrollador Profesional de Juegos Programación III Unidad II Trabajando con bloqueo de datos

2 Threading en window- Mutex Temas Race conditions Que son y como se usan

3 Condición de carrera Una "condición de carrera" es una situación donde dos hilos compiten para leer y grabar valores en nuestros datos críticos. Nuestro programa anterior presenta una condición buena carrera donde, según el sleep que hemos previsto en nuestros hilos, un hilo puede leer o escribir datos más rápido que el otro hilo. Esto crea el concepto de un “race". ¿Qué hilo llega primero? El hilo que llena el buffer, o el hilo que lee del buffer. Obviamente, el hilo que ejecuta más rápido terminará la carrera antes que el otro hilo. Pero desde que termina de leer losd datos del buffer, ya no podría afectar negativamente a los datos del búfer.

4 Ahora, ¿cómo hacer que el código no se comporte de la manera que esperamos? Esta es la clásica condición de carrera que dará una mejor idea sobre trabajar con la exclusión mutua. En este ejemplo, vamos a crear dos hilos que realicen exactamente la misma tarea. El propósito de estos hilos es imprimir caracteres en la pantalla. Se imprimirá o un "." o un '#'. Se determinará qué carácter se va a imprimir de acuerdo con el último carácter impreso. Si el último carácter impreso fue un '.', el hilo imprime un "#". Asimismo, si el último carácter impreso fue un "#", el hilo imprimirá un "." Race conditions

5 Ejemplo #include using namespace std; static char lastChar = '#'; void threadProc(int *sleepVal) { cout << "sleepVal: " << *sleepVal << endl; for (int i = 0; i < 100; i++) { char currentChar; if (lastChar == '#') currentChar = '.'; else currentChar = '#'; Sleep(*sleepVal); lastChar = currentChar; cout << currentChar; } Race conditions

6 void main() { int sleepVal1 = 50; int sleepVal2 = 40; HANDLE threadHandle1; threadHandle1 = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) threadProc, &sleepVal1, 0, NULL); HANDLE threadHandle2; threadHandle2 = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) threadProc, &sleepVal2, 0, NULL); WaitForSingleObject( threadHandle1, INFINITE); WaitForSingleObject( threadHandle2, INFINITE); cout << endl << endl; system("pause"); }

7 / / Este es el último carácter que se imprime. Se deberá protegerá con un mutex. static char lastChar = '#'; Esta es la función que los hilos ejecutarán. Acepta un valor de sleep como argumento para que podamos dormir a cada hilo con el valor que le pasemos y no tener que escribir el mismo código dos veces sólo para dormir los hilos en diferentes intervalos. void threadProc( int *sleepVal) { cout << "sleepVal: " << *sleepVal << endl; for (int i = 0; i < 100; i++) { // imprimimos 100 caracteres // este es el caracter que vamos a imprimir ESTA vez char currentChar; if (lastChar == '#') currentChar = '.'; else currentChar = '#'; //Dormimos este hilo, para que pueda correr a un ritmo diferente a los otros Sleep(*sleepVal); // seteamos el último carácter con el carácter que se va a imprimir lastChar = currentChar; cout << currentChar; // imprimimos el caracter }

8 Podemos predecir lo que podría suceder? Lo veremos al ejecutar el programa. int main() { Ya hablado sobre ello, pero necesitamos un valor de sleep para cada subproceso Este valor va a cambiar la velocidad a la que cada hilo se ejecutará. Le daremos valores sólo ligeramente diferentes por lo que correran y cerraran casi a la misma velocidad, Esto creará el efecto deseado. int sleepVal1 = 50; int sleepVal2 = 40; Ahora aquí está un truco muy útil. Vamos a pasar un argumento a nuestros hilos en la función de CreateThread. También recordemos que threadProc tiene un argumento (int * sleepVal) o puntero a un entero. El cuarto argumento en CreateThread es el puntero a threadProc. Cualquier dirección de un entero que pasemos en este argumento será el valor de puntero de sleepVal en threadProc. Pasamos la dirección de nuestros dos sleepVal en cada una de nuestras llamadas a CreateThread. HANDLE threadHandle; // creamos thread 1 con sleepVal1 threadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) threadProc, &sleepVal1, 0, NULL); HANDLE threadHandle2; // creamos el thread 2 con sleepVal2 threadHandle2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) threadProc, &sleepVal2, 0, NULL);

9 //esperamos a los threads a completar su tarea antes de terminar la aplicación WaitForSingleObject(threadHandle, INFINITE); WaitForSingleObject(threadHandle2, INFINITE); cout << endl << endl; // terminado presione un tecla para salir system("pause"); return 0; } Ahora, ¿qué se puede espera cuando se ejecuta este programa. Naturalmente que todo se comportan como se espera y ver 200 caracteres alternados entre '#‘‘ y '. ' Al menos esto es lo que queremos que nuestro programa haga. Si ejecutamos el programa y vemos los resultados que sucede?

10 No es exactamente lo que esperábamos no? Vimos un patrón similar a esto: ".#.##..##.#.##..##" Vemos un patrón de. 'S y #' s, con ocasionales, si no a menudo, dobles. Estos no se alternan en todos!! Pero, ¿por qué es eso? Bueno, esta es la condición de carrera clásico. ¿Cuál va a llegar primero? Un hilo se las arreglan para leer y escribir en la variable ultimocarac antes de la siguiente conversación tiene la oportunidad de hacer lo mismo? A veces sí, a veces no. Podemos ver diferentes grados de ello cambiando el sleep alrededor de los valores. Trate de hacer a los dos 50 y ejecuta la aplicación. ¿Se soluciona el problema? Es más que probable que no! Con nuestro ejemplo anterior, cuando el hilo que inserta los datos en el búfer corría a la misma velocidad que el hilo de la lectura de los datos del buffer, los dos lograron mantenerse casi en sincronía. Con este ejemplo, sin embargo, a menos que comiencen en el momento justo, los dos hilos nunca estará en sincronía. Es probable que siempre obtengamos un patrón alternante de "..##..##".

11 Vamos a examinar lo que sucede en este ejemplo más de cerca. Observaremos detenidamente a cada hilo y ver si podemos determinar por qué estamos obteniendo estos resultados. Así que digamos: El hilo 1 inicia y se pone a examinar la variable ultimocarac primero. Ve que ultimocarac es un '#' y establece el carácter a imprimir a un "." como se determinó. Imprime el "." y establece ultimocarac a "." y sigue como debería. Al mismo tiempo, el hilo 2 comienza a ejecutarse. Entra y ve que ultimocarac es un "." y decide imprimir un '#'. Establece el carácter que se va a imprimir en '#'. El hilo 1 continúa su ejecución, examina el último carácter y ve que todavía es un '.' entonces decide imprimir un '#'. El hilo 2 imprime ahora su '#' y establece ultimocarac a '#'. Pero el hilo 1 hace exactamente lo mismo, e imprime un '#' y establece ultimocarac a '.'. Como vemos, los dos hilos accedieron a la ultimocarac al mismo tiempo. Si ambos pueden observar ultimocarac al mismo tiempo, ambos pueden, erróneamente, determinar que van a imprimir el mismo carácter, y entonces tendremos dos caracteres impresos en pantalla. Después de jugar un poco con el programa, vemos que no tiene mucha importancia cuando en el proceso se imprimir el carácter, ni donde se establece el valor ultimocarac al nuevo caracter.

12 Por supuesto que podemos crear situaciones en las que los hilos parecen comportarse de la manera esperada, pero no se puede estar seguro de ello. Un simple cambio como correr las aplicaciones en un pc más lento o más rápido tiraría abajo toda la sincronización. Podemos experimentar con el valor de sleep, así como la asignación de la ultimocarac y la impresión. Entonces, ¿cómo resolver este pequeño problema? Ahora sabemos que la respuesta es con un mutex!!!! Necesitamos un mutex para que un hilo puede mantener el control completo de acceso a ultimocarac hasta que termine de trabajar con él. Bloqueará el mutex antes de leer ultimocarac. Luego desbloqueará el mutex después de escribir ultimocarac. De esta manera, cuando el otro hilo va a buscar ultimocarac, primero debe obtener un bloqueo en el mutex para asegurarse de que el otro hilo no tiene acceso al dato compartido.

13 Tareas Como tarea, se modificará el programa usando un mutex para que el acceso a ultimocarac se limita a un hilo a la vez. Después se creará un tercer hilo para realizar la misma tarea. Una vez hecho esto, estamos seguros que tenemos todas las herramientas que necesitamos para construir cualquier aplicación multihilo. Ciertamente, hay más trucos y formas más eficientes para manejar los hilos, hay que investigar y experimentar.


Descargar ppt "Desarrollador Profesional de Juegos Programación III Unidad II Trabajando con bloqueo de datos."

Presentaciones similares


Anuncios Google