No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Aprovecha el precio especial y haz tu profesión a prueba de IA

Antes: $249

Currency
$209
Suscríbete

Termina en:

0 Días
3 Hrs
43 Min
12 Seg

Worker pools

26/30
Recursos

¿Qué es Workerpool en Go y cómo puede transformar tus proyectos?

El mundo de la programación es vasto y está lleno de paradigmas interesantes. Uno de los conceptos más poderosos en el lenguaje de programación Go es el de workerpool, que permite la ejecución concurrente de tareas específicas por múltiples "trabajadores" o workers. Por tanto, es esencial entender cómo implementar esto en tus proyectos para optimizar tareas que demandan tiempo. ¡Veamos cómo funciona en Go!

¿Cómo se inicia un proyecto de workerpool en Go?

El primer paso es crear un archivo en Go, por ejemplo, wp.go. Dicho archivo comenzará como la mayoría de los programas en Go, definiendo el paquete con la clausula package main. Luego, se definirá una función para el cálculo de la serie de Fibonacci. Esta función, que utiliza una implementación recursiva, será la pieza central del trabajo que realizarán los trabajadores.

func fibonacci(n int) int {
    if n <= 1 {
        return n
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

¿Cómo se crea un worker en Go?

Un worker es una función que recibe tareas (jobs) a través de un canal, las procesa y envía el resultado a través de otro canal. Es fundamental asignar un ID a cada worker para identificar qué trabajador realiza cada tarea.

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, job)
        results <- fibonacci(job)
        fmt.Printf("Worker %d finished job %d\n", id, job)
    }
}

¿Cómo implementar el workerpool en la función principal?

La función main es donde se coordina todo el proceso: definir las tareas, crear los canales de comunicación, iniciar los workers y manejar los resultados.

  1. Definir las tareas: Se utiliza un slice para almacenar los números a los que se les aplicará la serie de Fibonacci.
  2. Configurar los workers: Crear varios workers, asignándoles un ID y pasando los canales de tareas y resultados.
  3. Manejar los canales: Asignar las tareas a los workers a través de un canal y recibir los resultados por otro.
func main() {
    tasks := []int{2, 3, 4, 5, 7, 10, 12, 40}
    numOfWorkers := 3

    jobs := make(chan int, len(tasks))
    results := make(chan int, len(tasks))

    for w := 1; w <= numOfWorkers; w++ {
        go worker(w, jobs, results)
    }

    for _, task := range tasks {
        jobs <- task
    }
    close(jobs)

    for i := 0; i < len(tasks); i++ {
        <-results
    }
}

¿Cómo optimiza el uso de go rutinas y canales en Go?

La magia de Go se encuentra en su capacidad para manejar la concurrencia de manera eficiente y sencilla. Al utilizar go rutinas y canales de comunicación, es posible crear programas altamente concurrentes sin la complejidad que otros lenguajes requieren. Cada worker es manejado como una gorutina, procesando tareas de manera concurrente y colaborando mediante canales de comunicación.

Esta estructura no solo mejora el tiempo de ejecución de tareas pesadas, como el cálculo de la serie de Fibonacci para grandes números, sino que también garantiza que los recursos del sistema se usen de manera eficiente.

Con esta implementación, aumentamos nuestra capacidad de procesar tareas concurrentemente, algo vital para aplicaciones que requieren manejar cientos o miles de acciones simultáneamente.


Explorar y utilizar workerpools en Go abre la puerta a automáticamente escalar el procesamiento de tareas y es una manera efectiva de manejar cargas de trabajo masivas. Te animo a que profundices más en este tema y sigas experimentando con lo que Go tiene para ofrecer en programación concurrente. ¡La práctica te permitirá dominar estas herramientas poderosas!

Aportes 9

Preguntas 5

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Worker pools are a model in which a fixed number of m workers (implemented in Go with goroutines) work their way through n tasks in a work queue (implemented in Go with a channel). Work stays in a queue until a worker finishes up its current task and pulls a new one off.

Basically there are a set amount of workers and n tasks. Every worker takes a task, and when it’s done it takes another one. This is very useful for breakable task that require a lot of processing.

This is an example program:

package main

import "fmt"

func Worker(id int, jobs <- chan int, results chan <- int) {
	for job := range jobs {
		fmt.Printf("--> Worker #%d started fib with %d\n", id, job)
		fib := Fibo(job)
		fmt.Printf("Worker #%d finished. Job: %d; Fibo: %d\n", id, job, fib)
		results <- fib
	}
}

func Fibo(n int) int {
	if n <= 1 {
		return n
	}

	return Fibo(n - 1) + Fibo(n - 2)
}

func main() {
	tasks := []int{2, 3, 4, 7, 10, 15}
	nWorkers := 3
	jobs := make(chan int, len(tasks))
	results := make(chan int, len(tasks))

	// creates the workers
	for i := 0; i < nWorkers; i++ {
		go Worker(i, jobs, results)
	}

	// sends them to work
	for _, v := range tasks {
		jobs <- v
	}

	// close channel to indicate that's all the work to do
	close(jobs)

	for i := 0; i < len(tasks); i++ {
		<- results
	}
}

Explanation

https://gobyexample.com/worker-pools

Escribí unas modificaciones del código visto en clase para observar realmente cómo es que las gorutines y los workers funcionan a gran escala (o bueno eso creo), el código al final de su ejecución dice cuánto tiempo tomó y en mi caso, realizando las series de Fibonacci con los workers y gorutines tomó 9.544643109 segundos, de la forma convencional tomó 18.054724068 les dejo el código por si alguien quiere probarlo, sólo deben cambiar el valor de la constante withGoRutinesAndWorkers para hacer la ejecución de una forma u otra.

Código:

package main

import (
	"fmt"
	"time"
)

func Worker(id int, jobs <-chan int, results chan<- int) {
	for job := range jobs {
		fmt.Printf("Worker with id %d started fib with %d\n", id, job)
		fib := Fibonacci(job)
		fmt.Printf("Worker with id %d has finished the job %d, and fib %d\n", id, job, fib)
		results <- fib
	}
}

func Fibonacci(n int) int {
	if n <= 1 {
		return n
	}
	return Fibonacci(n-1) + Fibonacci(n-2)
}

func main() {
	start := time.Now()
	tasks := []int{2, 34, 36, 17, 10, 40, 6, 22, 41, 44, 33, 2, 5, 2, 34, 36, 17, 10, 40, 6, 22, 41, 44, 33, 2, 5, 17, 10, 40, 6, 22, 2, 5, 2, 34, 36, 22, 41, 44}
	const withGoRutinesAndWorkers = false
	if withGoRutinesAndWorkers {
		nWorkers := 12
		jobs := make(chan int, len(tasks))
		results := make(chan int, len(tasks))

		for i := 0; i <= nWorkers; i++ {
			go Worker(i, jobs, results)
		}

		for _, value := range tasks {
			jobs <- value
		}
		close(jobs)

		for i := 0; i < len(tasks); i++ {
			<-results
		}
	} else {

		for _, v := range tasks {
			val := Fibonacci(v)
			fmt.Println("val->", val)
		}

	}

	elapsed := time.Since(start)
	fmt.Printf("Program took %s", elapsed)

}

Si alguien no entiende puede tratar con este codigo, tienes unos prints y unos sleep para comprender mejor lo q pasa paso a paso

package main

import (
	"fmt"
	"time"
)

func Worker(id int, jobs <-chan int, results chan<- int) {
	fmt.Println("Worker with id", id, "started")
	for j := range jobs {
		fmt.Printf("Worker with id %v started job %v\n", id, j)
		results <- Fibonacci(j)
		fmt.Printf("Worker with id %v finished job %v\n", id, j)
	}
}

func Fibonacci(n int) int {
	if n <= 1 {
		return n
	}
	return Fibonacci(n-1) + Fibonacci(n-2)
}

func main() {
	tasks := []int{7, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42}
	jobs := make(chan int, len(tasks))
	results := make(chan int, len(tasks))
	for w := 1; w <= 3; w++ {
		go Worker(w, jobs, results)
	}
	time.Sleep(3 * time.Second)
	for _, task := range tasks {
		println("Sending task", task)
		jobs <- task
		time.Sleep(1 * time.Second)
	}
	close(jobs)
	for a := 1; a <= len(tasks); a++ {
		fmt.Println(<-results)
	}
}

Basicamente un worker es la combinación de Buffered Channels, GoRoutines y se emplea el patrón de semáforos.

Es interesante saber como funciona la función recursiva para resolver el fibonacci, dejo link donde lo explican:
Fibonacci

package main

import "fmt"

// Worker is a function that does the work.
func Worker(id int, jobs <-chan int, results chan<- int) {
	for j := range jobs {
		fmt.Println("worker", id, "started  job", j)
		fib := Fibonnaci(j)
		results <- fib
		fmt.Println("worker", id, "finished job", j, "result", fib)
	}
}

// Fibonacci returns the nth number in the Fibonacci sequence.
func Fibonnaci(n int) int {
	if n < 2 {
		return n
	}
	return Fibonnaci(n-1) + Fibonnaci(n-2)
}

func main() {
	// tasks to do
	tasks := []int{2, 3, 4, 5, 7, 10, 12, 35, 37, 40, 41, 42}

	nWorkers := 5
	jobs := make(chan int, len(tasks))
	results := make(chan int, len(tasks))

	// start the workers
	for w := 1; w <= nWorkers; w++ {
		go Worker(w, jobs, results)
	}

	// give the workers jobs
	for _, t := range tasks {
		jobs <- t
	}
	close(jobs)

	// get the results (consume the channel)
	for a := 1; a <= len(tasks); a++ {
		<-results
	}
}

por que no se pasa como referencia usando '&' ?

Les dejo un ejemplo de este ejercicio usando WaitGroups, en lugar de un channel extra (results). El canal results no me parecio necesario asi que decidi borrarlo y tiene el mismo funcionamiento.

package main

import (
	"fmt"
	"sync"
)

var (
	wg = sync.WaitGroup{}
)

func Worker(id int, jobs <-chan int) {
	for job := range jobs {
		fmt.Printf("Worker with id %d started fib with %d\n", id, job)
		fib := Fibonacci(job)
		fmt.Printf("Worker with id %d, job %d fib with %d\n", id, job, fib)
		wg.Done()
	}
}

func Fibonacci(n int) int {
	if n <= 1 {
		return n
	}
	return Fibonacci(n-1) + Fibonacci(n-2)
}

func main() {
	tasks := []int{2, 3, 4, 5, 7, 10, 12, 40}
	nWorkers := 3
	jobs := make(chan int, len(tasks))

	for i := 0; i < nWorkers; i++ {
		go Worker(i, jobs)
	}

	for _, value := range tasks {
		wg.Add(1)
		jobs <- value
	}
	close(jobs)

	wg.Wait()
}

Estaba viendo ¿Por qué en los resultados sólo hay dos IDs? Y resulta que es por que en la condición del “for” puso “i<3” por lo que solo se crearon 2 workers y no 3