Skip to content

Commit

Permalink
deploy: 5857b2f
Browse files Browse the repository at this point in the history
  • Loading branch information
zeroidentidad committed Feb 7, 2024
1 parent 30b9918 commit e9fcd4a
Show file tree
Hide file tree
Showing 2 changed files with 4 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<a class=tag href=/tags/software>software</a></span>
<span class=meta><label>Por: <b>Andres Reyes El Programador Pobre</b></label>
<label>Publicado <b>February 6, 2024</b></label>
<label>Tiempo de lectura: <b>13 minutos</b>.</label></span></header><section class=body><figure class=post-figure><a href=/posts/2024/02/codelab.-implementemos-un-worker-pool/images/pools.png><img src=/posts/2024/02/codelab.-implementemos-un-worker-pool/images/pools_hu25c2df14317a6a74ebc7cc77c76ece4a_666661_1024x0_resize_box_3.png width=1024 height=794 alt=" Mengniu Dairy production line. Gentileza de Peter Tittenberger y flickr. https://www.flickr.com/photos/ext504/3639675762/in/photostream/"></a><figcaption>Mengniu Dairy production line. Gentileza de Peter Tittenberger y flickr. https://www.flickr.com/photos/ext504/3639675762/in/photostream/</figcaption></figure><p>La concurrencia es una herramienta que nos ayuda a ejecutar tareas pesadas o que bloquean el avance de un proceso, mientras otras tareas se ejecutan, mejorando el rendimiento general.</p><p>Pero ¿Podemos hacer uso de la concurrencia sin ponernos un límite?</p><p>Concurrencia es un aspecto integral del desarrollo moderno de software que permite que nuestras aplicaciones ejecuten múltiples tareas simultáneamente y nos permite usar efectivamente los recursos del sistema, especialmente en situaciones donde debemos llevar a cabo mucho computo u operaciones de entrada/salida.</p><p>Pero ¿Hay un Límite?</p><p>Imaginemos el periódo de matriculas de un colegio en donde se debe registrar el ingreso y asignar curso a cientos de alumnos. Podríamos poner un funcionario encargado para el registro de cada alumno, pero solo podríamos hacerlo hasta completar el máximo número de funcionarios disponibles</p><p>¡Además de que todas las demás tareas de esos funcionarios quedarían en pausa hasta terminar el proceso de registro!</p><hr><h2 id=worker-pool-es-un-patrón-concurrente-cuya-idea-base-es-tener-un-número-de-gorutinas-esperando-a-que-se-le-asignen-trabajos-ejecutándolos-a-medida-que-se-le-van-asignando><em>Worker pool</em> es un patrón concurrente cuya idea base es tener un número de gorutinas esperando a que se le asignen trabajos, ejecutándolos a medida que se le van asignando</h2><p>Como eso es poco verosímil, que tal si dejamos solo a 4 funcionarios realizando el proceso de matricula mientras que los demás se reparten las tareas habituales. De esta forma los 4 funcionarios registran alumnos <em>concurrentemente</em>. Junto con ello, esto nos permite que si vemos que se produce un cuello de botella, por ejemplo muchos apoderados llegan a registrar a sus pupilos al mismo tiempo, podemos reforzar con algunos funcionarios extra, y devolverlos a sus tareas habituales cuando se haya procesado el cuello de botella.</p><p>Pues bien, con esta analogía hemos descrito el funcionamiento de un <em>worker pool</em>, el cual es un patrón para lograr concurrencia, cuya idea base es tener un número de gorutinas, que reciben el nombre de <em>worker</em>, esperando a que se le asignen trabajos. Cuando un trabajo se le asigna a un worker, se ejecuta concurrentemente mientras la gorutina principal sigue ejecutando otro código.</p><hr><h2 id=la-idea-base-de-nuestra-implementación-es-gatillar-bajo-demanda-un-número-fijo-de-gorutinas-que-harán-las-veces-de-workers-e-iran-recibiendo-tareas-a-ejecutar-a-través-de-un-canal-por-el-cual-las-enviaremos>La idea base de nuestra implementación es gatillar bajo demanda un número fijo de gorutinas que harán las veces de workers e iran recibiendo tareas a ejecutar a través de un canal por el cual las enviaremos</h2><p>Worker pool nos permite <em>administrar</em> el nivel de concurrencia de nuestras aplicaciones controlando el uso de recursos de procesamiento.</p><p>¡Pero basta de teoría y ensuciemonos las manos que a eso hemos venido!</p><p>Implementaremos un worker pool definiendo cada parte que lo constituye, junto con algunas utilidades para obtener métricas.</p><p>La idea base de nuestra implementación es gatillar bajo demanda un número fijo de gorutinas que harán las veces de workers e iran recibiendo tareas a ejecutar a través de un canal por el cual las enviaremos.</p><p>Decimos que gatillaremos bajo demanda a los workers porque los iremos levantando a medida que vayan llegando tareas y no hayan workers para ejecutarlas, hasta llegar al límite definido de workers. En pocas palabras, implementaremos una lazy inicialization de los workers de nuestro pool.</p><p>Dentro de las consideraciones de diseño, haremos un fuerte uso de interfaces pues nos permiten que los elementos que componen el worker pool sean plugables y posibles de reemplazar por otros componentes que implementen la interface.</p><p>La belleza de esto radica en que si a Ud. se le ocurre una mejor implementación, o necesita funcionalidades extra que no están contempladas en las implementaciones por defecto, puede construir su propia implementación de acuerdo a sus gustos o necesidades, y mientras implemente la interface definida su código personalizado trabajará perfectamente con el resto del worker pool.</p><h2 id=tareas>Tareas</h2><p>Dentro del contexto de programación concurrente es usual llamar aquello que se procesa como <em>tarea</em>, que son trabajos que pueden ser ejecutados asíncrona y concurrentemente con otras tareas. Así que empecemos por definir la estructura de datos que nuestro worker pool será capaz de procesar.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#66d9ef>type</span> <span style=color:#a6e22e>Task</span> <span style=color:#66d9ef>interface</span> {
<label>Tiempo de lectura: <b>13 minutos</b>.</label></span></header><section class=body><figure class=post-figure><a href=/posts/2024/02/codelab.-implementemos-un-worker-pool/images/pools.png><img src=/posts/2024/02/codelab.-implementemos-un-worker-pool/images/pools_hu25c2df14317a6a74ebc7cc77c76ece4a_666661_1024x0_resize_box_3.png width=1024 height=794 alt=" Mengniu Dairy production line. Gentileza de Peter Tittenberger y flickr. https://www.flickr.com/photos/ext504/3639675762/in/photostream/"></a><figcaption>Mengniu Dairy production line. Gentileza de Peter Tittenberger y flickr. https://www.flickr.com/photos/ext504/3639675762/in/photostream/</figcaption></figure><p>La concurrencia es una herramienta que nos ayuda a ejecutar tareas pesadas o que bloquean el avance de un proceso, mientras otras tareas se ejecutan, mejorando el rendimiento general.</p><p>Pero ¿Podemos hacer uso de la concurrencia sin ponernos un límite?</p><p>Concurrencia es un aspecto integral del desarrollo moderno de software que permite que nuestras aplicaciones ejecuten múltiples tareas simultáneamente y nos permite usar efectivamente los recursos del sistema, especialmente en situaciones donde debemos llevar a cabo mucho computo u operaciones de entrada/salida.</p><p>Pero ¿Hay un Límite?</p><p>Imaginemos el periódo de matriculas de un colegio en donde se debe registrar el ingreso y asignar curso a cientos de alumnos. Podríamos poner un funcionario encargado para el registro de cada alumno, pero solo podríamos hacerlo hasta completar el máximo número de funcionarios disponibles</p><p>¡Además de que todas las demás tareas de esos funcionarios quedarían en pausa hasta terminar el proceso de registro!</p><hr><h2 id=worker-pool-es-un-patrón-concurrente-cuya-idea-base-es-tener-un-número-de-gorutinas-esperando-a-que-se-le-asignen-trabajos-ejecutándolos-a-medida-que-se-le-van-asignando><em>Worker pool</em> es un patrón concurrente cuya idea base es tener un número de gorutinas esperando a que se le asignen trabajos, ejecutándolos a medida que se le van asignando</h2><p>Como eso es poco verosímil, que tal si dejamos solo a 4 funcionarios realizando el proceso de matricula mientras que los demás se reparten las tareas habituales. De esta forma los 4 funcionarios registran alumnos <em>concurrentemente</em>. Junto con ello, esto nos permite que si vemos que se produce un cuello de botella, por ejemplo muchos apoderados llegan a registrar a sus pupilos al mismo tiempo, podemos reforzar con algunos funcionarios extra, y devolverlos a sus tareas habituales cuando se haya procesado el cuello de botella.</p><p>Pues bien, con esta analogía hemos descrito el funcionamiento de un <em>worker pool</em>, el cual es un patrón para lograr concurrencia, cuya idea base es tener un número de gorutinas, que reciben el nombre de <em>worker</em>, esperando a que se le asignen trabajos. Cuando un trabajo se le asigna a un worker, se ejecuta concurrentemente mientras la gorutina principal sigue ejecutando otro código.</p><hr><h2 id=la-idea-base-de-nuestra-implementación-es-gatillar-bajo-demanda-un-número-fijo-de-gorutinas-que-harán-las-veces-de-workers-e-iran-recibiendo-tareas-a-ejecutar-a-través-de-un-canal-por-el-cual-las-enviaremos>La idea base de nuestra implementación es gatillar bajo demanda un número fijo de gorutinas que harán las veces de workers e iran recibiendo tareas a ejecutar a través de un canal por el cual las enviaremos</h2><p>Worker pool nos permite <em>administrar</em> el nivel de concurrencia de nuestras aplicaciones controlando el uso de recursos de procesamiento.</p><p>¡Pero basta de teoría y ensuciemonos las manos que a eso hemos venido!</p><p>Implementaremos un worker pool definiendo cada parte que lo constituye, junto con algunas utilidades para obtener métricas.</p><p>La idea base de nuestra implementación es gatillar bajo demanda un número fijo de gorutinas que harán las veces de workers e iran recibiendo tareas a ejecutar a través de un canal por el cual las enviaremos.</p><p>Decimos que gatillaremos bajo demanda a los workers porque los iremos levantando a medida que vayan llegando tareas y no hayan workers para ejecutarlas, hasta llegar al límite definido de workers. En pocas palabras, implementaremos una lazy inicialization de los workers de nuestro pool.</p><p>Dentro de las consideraciones de diseño, haremos un fuerte uso de interfaces pues nos permiten que los elementos que componen el worker pool sean plugables y posibles de reemplazar por otros componentes que implementen la interface.</p><p>La belleza de esto radica en que si a Ud. se le ocurre una mejor implementación, o necesita funcionalidades extra que no están contempladas, puede construir su propia implementación de acuerdo a sus gustos o necesidades, y mientras exponga los métodos de la interface definida, su código personalizado trabajará perfectamente con el resto del worker pool.</p><h2 id=tareas>Tareas</h2><p>Dentro del contexto de programación concurrente es usual llamar aquello que se procesa como <em>tarea</em>, que son trabajos que pueden ser ejecutados asíncrona y concurrentemente. Así que empecemos por definir la estructura de datos que nuestro worker pool será capaz de procesar.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#66d9ef>type</span> <span style=color:#a6e22e>Task</span> <span style=color:#66d9ef>interface</span> {
</span></span><span style=display:flex><span> <span style=color:#a6e22e>Run</span>()
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>Una interface llamada <code>Task</code>, exponiendo un método <code>Run</code> que será implementada por las tareas concretas que necesitemos procesar.</p><h2 id=executor-y-spawner>Executor y Spawner</h2><p>Comentamos que los workers deben levantarse a medida que las tareas vayan llegando, y que deben ir ejecutandolas. Podemos abstraer esas dos funcionalides en respectivas interfaces.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#66d9ef>type</span> <span style=color:#a6e22e>Executor</span> <span style=color:#66d9ef>interface</span> {
Expand Down Expand Up @@ -319,7 +319,7 @@
</span></span><span style=display:flex><span> <span style=color:#a6e22e>d</span>.<span style=color:#a6e22e>spawner</span> = <span style=color:#a6e22e>spn</span>
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>En nuestra implementación hemos decidido usar el paquete atomic que provee primitivas de memoria de bajo nivel. La documentación de Go recomienda preferir la paquete sync o canales para sincronizar memoria, pero como lo que necesitabamos hacer era aumentar en 1 algunas variables nos decantamos por atomic.</p><p>Como ninguna implementaciónm está terminada si sus pruebas, construyamos pruebas para nuestro invento.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-go data-lang=go><span style=display:flex><span>
</span></span></code></pre></div><p>En nuestra implementación hemos decidido usar el paquete <code>atomic</code> que provee primitivas de memoria de bajo nivel. La documentación de Go recomienda preferir el paquete sync o canales para sincronizar memoria, pero como lo que necesitabamos hacer era aumentar en 1 algunas variables nos decantamos por atomic.</p><p>Como ninguna implementaciónm está terminada sin sus pruebas, construyamoslas para probar nuestro invento.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-go data-lang=go><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#66d9ef>func</span> <span style=color:#a6e22e>Test_deadpool_ErrsOnCreate</span>(<span style=color:#a6e22e>t</span> <span style=color:#f92672>*</span><span style=color:#a6e22e>testing</span>.<span style=color:#a6e22e>T</span>) {
</span></span><span style=display:flex><span> <span style=color:#a6e22e>_</span>, <span style=color:#a6e22e>err</span> <span style=color:#f92672>:=</span> <span style=color:#a6e22e>New</span>(<span style=color:#a6e22e>WithMax</span>(<span style=color:#f92672>-</span><span style=color:#ae81ff>1</span>))
</span></span><span style=display:flex><span>
Expand Down Expand Up @@ -414,7 +414,7 @@
</span></span><span style=display:flex><span> deadpool_test.go:61: tiempo promedio por tarea: 10.145993ms
</span></span><span style=display:flex><span> deadpool_test.go:62: tiempo total de proceso: 223.349239ms
</span></span><span style=display:flex><span>--- PASS: Test_deadpool_Flow <span style=color:#f92672>(</span>0.22s<span style=color:#f92672>)</span>
</span></span></code></pre></div><p>Donde vemos que el tiempo sumado de la ejecución de todas las tareas fue <em>1.298687182s</em>, pero el tiempo total de proceso solo fue de <em>223.349239ms</em>, mientras que cada tarea demoro en promedio <em>10.145993ms</em> que es una fracción mayor a los 10ms que le indicamos que debía esperar la tarea mock.</p><p>Como alternativa al worker pool, según sea nuestro caso podriamos haber implementado un <a href=https://medium.com/@chess.coach.ar/concurrencia-en-go-implementando-pipelines-d23a58fa2405>pipeline</a>, que consiste en una cadena de gorutinas cada una de las cuales realiza una acción sobre un elemento hasta completarlo, y cuya analogía es una línea de producción.</p><p>Sea cual sea el patrón concurrente que elijamos, debemos ser cuidadosos de no provocar condiciones de carrera ni <a href=https://gophers-latam.github.io/posts/2023/12/fuga-de-gorutinas/>fugas de gorutinas</a></p><p>Hemos implementado un worker pool funcional, pero aun no hemos respondido a la pregunta con que iniciamos este artículo ¿Hay algún límite para la concurrencia? Preferimos dejar la pregunta abierta y esperamos sus respuestas en los comentarios.</p><p>Puede hackear el código que hemos construido en este <a href=https://go.dev/play/p/x7R2nwq5um7>playground</a>, y como de costumbre, le proveemos con el <a href=https://github.com/profe-ajedrez/deadpool>repositorio</a> donde se aloja.</p><p>Y bien, con eso llegamos al final de este codelab. Esperamos que haya sido de su agrado y como siempre le recordamos que si le gustó este artículo no dude en compartirlo o en comentarnos si considera que hay algo en lo que podamos mejorar.</p></section><section class=related><h3>Relacionados</h3><div class=post-list><article class="box box-post"><a class=title href=/posts/2021/12/ecosistema-lenguage-go/><h3>Ecosistema lenguage Go</h3></a><time class=date>December
</span></span></code></pre></div><p>Donde vemos que el tiempo sumado de la ejecución de todas las tareas fue <em>1.298687182s</em>, pero el tiempo total de proceso solo fue de <em>223.349239ms</em>, mientras que cada tarea demoro en promedio <em>10.145993ms</em> que es una fracción mayor a los 10ms que le indicamos que debía esperar la tarea mock.</p><p>Como alternativa al worker pool, según sea nuestro caso podriamos haber implementado un <a href=https://medium.com/@chess.coach.ar/concurrencia-en-go-implementando-pipelines-d23a58fa2405>pipeline</a>, que consiste en una cadena de gorutinas cada una de las cuales realiza una acción sobre un elemento hasta completarlo, y cuya analogía es una línea de producción.</p><p>Sea cual sea el patrón concurrente que elijamos, debemos ser cuidadosos de no provocar condiciones de carrera ni <a href=https://gophers-latam.github.io/posts/2023/12/fuga-de-gorutinas/>fugas de gorutinas</a>, por lo que debemos hacer uso exhaustivo del flag <code>-race</code> al ejecutar nuestras pruebas.</p><p>Hemos implementado un worker pool funcional, pero aun no hemos respondido a la pregunta con que iniciamos este artículo ¿Hay algún límite para la concurrencia? Preferimos dejar la pregunta abierta y esperamos sus respuestas en los comentarios.</p><p>Puede hackear el código que hemos construido en este <a href=https://go.dev/play/p/x7R2nwq5um7>playground</a>, y como de costumbre, le proveemos con el <a href=https://github.com/profe-ajedrez/deadpool>repositorio</a> donde se aloja.</p><p>Y bien, con eso llegamos al final de este codelab. Esperamos que haya sido de su agrado y como siempre le recordamos que si le gustó este artículo no dude en compartirlo o en comentarnos si considera que hay algo en lo que podamos mejorar.</p></section><section class=related><h3>Relacionados</h3><div class=post-list><article class="box box-post"><a class=title href=/posts/2021/12/ecosistema-lenguage-go/><h3>Ecosistema lenguage Go</h3></a><time class=date>December
27
2021</time><div class=summary-container><figure class=post-figure><a href=/posts/2021/12/ecosistema-lenguage-go/images/golang.png><img src=/posts/2021/12/ecosistema-lenguage-go/images/golang_hu57fed90b28f13fac04b0b2a1fb7dff6d_26167_1024x0_resize_box_3.png width=1024 height=346 alt="json to go"></a><figcaption>json to go</figcaption></figure><p>Go, también conocido como Golang, es un lenguaje de programación de tipado estático desarrollado por Robert Griesemer, Rob Pike y Ken Thompson en Google.</p></div></article><article class="box box-post"><a class=title href=/posts/2021/05/golang-implementaci%C3%B3n-del-almacenamiento-en-cach%C3%A9/><h3>Golang: Implementación del almacenamiento en caché</h3></a><time class=date>May
6
Expand Down
2 changes: 1 addition & 1 deletion search/index.json

Large diffs are not rendered by default.

0 comments on commit e9fcd4a

Please sign in to comment.