Curso de Ruby

Curso de Ruby

Sim贸n Soriano

Sim贸n Soriano

Concurrencia vs Paralelismo: Threads en Ruby

17/39

Lectura

Concurrencia VS Paralelismo

Los t茅rminos concurrencia y paralelismo pueden ser f谩cilmente confundidos. Por un lado 2 tareas se ejecutan en paralelo cuando ambas se ejecutan en unidades de procesamiento independientes al mismo tiempo, es decir, ambas tareas pueden comenzar exactamente al mismo tiempo pues su ejecuci贸n es manejada por dos unidades de procesamiento diferente. Por otro lado, dos tareas se ejecutan concurrentemente cuando se pueden ejecutar en la misma unidad de procesamiento intercalando subtareas de ambas tareas.

Por ejemplo:

Hay una empresa que est谩 desarrollando un blog como una aplicaci贸n web. Para esto necesita desarrollar el backend y el frontend para lo cual contrata a un desarrollador fullstack llamado Pepe que se puede encargar de ambas tareas. Pepe entonces tiene 2 tareas, hacer el backend y hacer el frontend, y a su vez estas 2 tareas se pueden dividir en peque帽as subtareas as铆:

  • Backend
    • API para la autenticaci贸n
    • CRUD de posts
  • Frontend
    • Vista de autenticaci贸n
    • Vista de los posts

Pepe puede trabajar en ambas tareas concurrentemente pues puede primero hacer una versi贸n inicial de la implementaci贸n del API para la autenticaci贸n. Mientras su l铆der t茅cnico revisa el c贸digo de esta versi贸n inicial, Pepe puede comenzar a implementar la vista de autenticaci贸n y una vez su l铆der t茅cnico termine la revisi贸n de la versi贸n inicial del API de autenticaci贸n, puede continuar con esta tarea y repetir el mismo proceso hasta terminar con todas las subtareas y tareas.

Si la empresa contrata a un desarrollador frontend y a un desarrollador backend, ambos desarrolladores pueden trabajar en ambas tareas en paralelo. Pues ambos pueden comenzar con ambas tareas al tiempo. En este ejemplo los desarrolladores son las unidades de procesamiento y el desarrollo del backend y frontend son las tareas que se pueden ejecutar en diferentes threads o unidades de procesamiento.
Ahora un ejemplo gr谩fico:

Captura de pantalla 2018-12-06 a la(s) 13.37.11.png

Tomado de: https://joearms.github.io/published/2013-04-05-concurrent-and-parallel-programming.html

Limitaciones de concurrencia en Ruby

En Ruby tenemos la posibilidad de crear Threads, sin embargo, su comportamiento depende del int茅rprete que utilicemos. El interprete que usamos en el curso que adicionalmente es el interprete m谩s popular (cruby o MRI) no permite paralelismo asi el computador en el que ejecutemos nuestro programa tenga m煤ltiples cores en su procesador. MRI utiliza un mecanismo llamado Global Interpreter Lock (GIL) que hace que el interprete solo pueda ejecutar un Thread a la vez. Esto es una decisi贸n que tomaron quienes dise帽aron el lenguaje pues es una manera relativamente sencilla de evitar race conditions, deadlocks y otros problemas comunes que surgen cuando se est谩 haciendo programaci贸n concurrente o en paralelo.

Aunque el GIL no permite que multiples threads se ejecuten, s铆 permite cambiar de contexto cuando se esta realizando una operaci贸n por fuera del interprete como operaciones de lectura o escritura. Como estas operaciones suceden por fuera del interprete, cruby permite cambiar de contexto para ejecutar otro thread mientras estas operaciones terminan y de esta manera se puede hacer programacion concurrente.

Otros interpretes como JRuby y Rubinius no tienen un GIL as铆 que permiten ejecuci贸n en paralelo.

Threads en Ruby

Captura de pantalla 2018-12-06 a la(s) 13.39.17.png
Captura de pantalla 2018-12-06 a la(s) 13.41.06.png

En este ejemplo estamos simulando hacer 3 llamados HTTP. Cada llamado toma 3 segundos y estamos ejecutando estos llamados con y sin threads. Aunque el GIL no permite la ejecuci贸n en paralelo de m煤ltiples threads, como la operaci贸n HTTP debe esperar un segundo por la respuesta (este comportamiento es simulado usando 鈥渟leep(1)鈥), el interprete puede cambiar de contexto y ejecutar los dem谩s threads que necesiten ser ejecutados. De esta manera podemos ver que la ejecuci贸n sin threads toma 3 segundos pues ejecuta los 3 llamados en serie, sin embargo la ejecuci贸n que utiliza threads solo toma 1 segundo pues tan pronto un thread llama al m茅todo sleep, el interprete detecta que debe esperar as铆 que puede cambiar de contexto y permitir la ejecuci贸n de los otros 2 threads.

A tener en cuenta

  • Para inicializar un Thread se utiliza debe crear un objeto Thread con Thread.new y pasarle un bloque en donde definimos lo que se debe ejecutar.
  • El punto de entrada de un programa Ruby se ejecuta en un thread principal o 鈥渕ain thread鈥. Tan pronto este thread termina, la ejecuci贸n de todo el programa es terminado, as铆 que si creamos varios threads pero el 鈥渕ain thread鈥 finaliza primero los otros threads van a ser terminados. Para evitar esto debemos hacer 鈥渏oin鈥 de los threads adicionales. Join es un mecanismo que hace que el thread principal espere a la finalizacion del thread al que se le hace join lo que podemos ver en la linea " threads.map(&:join)".

Referencias:

https://github.com/simon0191/platzi-curso-ruby/commit/8497e9ace3011767355fb814a473c39f896dfe67
https://www.toptal.com/ruby/ruby-concurrency-and-parallelism-a-practical-primer
https://joearms.github.io/published/2013-04-05-concurrent-and-parallel-programming.html
https://medium.com/@franzejr/ruby-3-mri-and-gil-a302577c6634
https://robots.thoughtbot.com/untangling-ruby-threads

Aportes 11

Preguntas 0

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesi贸n.

Ojo, Ruby no utiliza los 鈥淭hread del sistema operativo鈥 ya que no usa paralelismo (MRI), utiliza 鈥淕reen Threads鈥.
me explico.
El sistema operativo usa un 鈥淪heduler鈥 que es quien le da tiempo de CPU y orden a las tareas que pasan por la CPU y sus cores, osea llega una tarea y el Sheduler dice ok a esta tarea de doy 10 mili segundos (tiempo variable) y cuando pasa ese tiempo, toma otra tarea, pero si la tarea inicial no se termino de resolver despu茅s el scheduler la toma otra vez y le da otro tiempo m谩s.

el Scheduler del sistema operativo tira en este caso a Ruby por un proceso(Hilo). Ruby por dentro, tiene su propio Scheduler para gestionar los hilos verdes que uno crea y de la misma manera que el S.O osea con tiempo y orden, esto ocurre tan r谩pido que nos da la impresi贸n de que se est谩n haciendo tareas a la vez鈥 pero no.

En cuanto al cambio de Contexto es cuando el 鈥淪cheduler鈥 toma una tarea, le da tiempo y se termina y toma otra (cambio de contexto)

Ahora como yo entend铆 Concurrencia y Paralelismo

Concurrencia es 鈥淐oncurrir鈥 por ejemplo: 50 personas se conectan a la base de datos, ocurre concurrencia鈥 ah y si no concurre nada, el sistema se queda esperando鈥 concurrencia.

Paralelismo es 鈥渆n Paralelo鈥 estas conexiones se atienden en distintos lugares a la vez del procesador del equipo. pero MRI no hace esto.

PD: en Python pasa lo mismo usa Hilos Verdes, en otros lenguajes les llaman Fibras, en C# se le llama Task, pero s铆 hay otros Lenguajes que utilizan llamadas a los Hilos del S.O

Fuente de Informaci贸n: Me lo explico un amigo llamado Nicolas Merino =)

Resumen: Imagina que vas al super
Concurrencia: 1 solo cajero atiende a 3 clientes (debe atender un momento al cliente A, otro momento al cliente B y otro rato al cliente C). Puede parecer algo 鈥渓ento鈥 y aunque no atiende a todos al mismo momento, fin y al cabo 鈥渆st谩 atendiendo a los 3鈥.

Paralelismo: 3 cajeros atienden a 3 clientes, cada cajero atiende a un cliente en el mismo instante. Fin de la historia.

馃帹 Tambien te recomiendo esta lectura si quieres trabajar con Paralelismo y Concurrencia en JavaScript 馃憣:

Deber铆an mejorar el contraste de las im谩genes. Leyendo de noche quede as铆 o.O

Muy buena explicaci贸n, al usar thread o hilo de ejecuci贸n es mas r谩pido porque se implementa la concurrencia de ejecuci贸n de tareas ahora mi duda es: 驴usando thread la ejecuci贸n la realiza la unidad de procesamiento usando concurencia y sin hilos ejecuta las instrucciones de manera secuencial?

En ruby no hay manera directa de ejecutar desde un programa multi-threading (paralelismo real), a menos de que se utilice una JVM por medio de Jruby la cual no utiliza el GIL, aunque es cirteo que ruby en su version 19+ perimite a un programa poder comunicarse con varios hilos del SO, siempre esta el cuello de botella del GIL, la unica manera de ejecutar paralelismo (real no simulado) usando MRI, es creando diferentes programas de ruby a la cual cada programa le es asignado un diferente GIL, por lo cual, tendras que partir tu programa en varios subprogramas y comunicarlos, esto tiene sus beneficios, ya que permites que tu programa sea mas escalable

Paralelismo implica concurrencia, pero concurrencia no implica paralelismo.
馃槈

Muy buena informaci贸n sobre concurrencia y paralelismo, me quedo todo claro.

Excelente explicaci贸n

Muy buena explicaci贸n.

Gran informaci贸n en este articulo