Sobes.tech
Middle

¿Qué es el workshopping?

sobes.tech AI

Respuesta de la IA

El work stealing (robo de trabajo) es un mecanismo de balanceo de carga en sistemas multihilo, donde un hilo o trabajador inactivo "roba" tareas de la cola de otro hilo ocupado.

Características clave:

  • Descentralizado: Cada hilo gestiona su propia cola de tareas.
  • Activo: El hilo inactivo busca activamente trabajo en otros.
  • Alta eficiencia: Es efectivo para tareas con carga desigual.

En Golang, se usa en el planificador (scheduler) para distribuir goroutines entre los hilos del sistema operativo (M). Cuando un M termina su trabajo actual (el conjunto de goroutines en P), busca goroutines en otros P a través de sus colas locales.

// Pseudocódigo que demuestra el concepto de work stealing
package main

import "fmt"
import "sync"
import "time"

type Task struct {
	id int
}

// Simulación de cola de tareas
type TaskQueue struct {
	tasks []Task
	mu    sync.Mutex
}

func (q *TaskQueue) AddTask(task Task) {
	q.mu.Lock()
	defer q.mu.Unlock()
	q.tasks = append(q.tasks, task)
}

func (q *TaskQueue) GetLocalTask() (Task, bool) {
	q.mu.Lock()
	defer q.mu.Unlock()
	if len(q.tasks) == 0 {
		return Task{}, false
	}
	task := q.tasks[0]
	q.tasks = q.tasks[1:]
	return task, true
}

// Intento de "robar" tarea de otra cola
func (q *TaskQueue) StealTask() (Task, bool) {
	q.mu.Lock()
	defer q.mu.Unlock()
	if len(q.tasks) < 2 { // No "robamos" si hay pocas tareas
		return Task{}, false
	}
	// Robamos desde el medio o el final para evitar conflictos con la extracción local
	index := len(q.tasks) / 2
	task := q.tasks[index]
	q.tasks = append(q.tasks[:index], q.tasks[index+1:]...)
	return task, true
}

// Simulación de un worker
func worker(id int, localQueue *TaskQueue, otherQueues []*TaskQueue, wg *sync.WaitGroup) {
	defer wg.Done()

	for {
		// Intentar obtener tarea de la cola local
		task, ok := localQueue.GetLocalTask()
		if ok {
			fmt.Printf("Worker %d ejecuta tarea %d localmente\n", id, task.id)
			time.Sleep(100 * time.Millisecond) // Simulación de trabajo
			continue
		}

		// Si la cola local está vacía, intentar robar
		stolen := false
		for _, queue := range otherQueues {
			if queue == localQueue {
				continue // No robar de uno mismo
			}
			task, ok := queue.StealTask()
			if ok {
				fmt.Printf("Worker %d robó tarea %d\n", id, task.id)
				time.Sleep(100 * time.Millisecond) // Simulación de trabajo
				stolen = true
				break // Exit al éxito de robo
			}
		}

		if !stolen {
			// Si no se pudo robar, quizás no queden tareas
			// En un planificador real, habría mecanismos más complejos para terminar
			// Para demostración, simplemente salir después de un tiempo
			fmt.Printf("Worker %d está inactivo, esperando...\n", id)
			time.Sleep(50 * time.Millisecond)
			// En un escenario real, aquí habría un mecanismo de finalización o parking
			// return // Para demostración, permitimos salir
			break // Para simplificar la simulación
		}
	}
}

func main() {
	numWorkers := 4
	numTasks := 20
	taskQueues := make([]*TaskQueue, numWorkers)
	for i := range taskQueues {
		taskQueues[i] = &TaskQueue{}
	}

	// Distribuir tareas de manera no uniforme para demostrar el work stealing
	for i := 0; i < numTasks; i++ {
		queueIndex := i % 2 // Más tareas en los primeros dos workers
		taskQueues[queueIndex].AddTask(Task{id: i})
	}

	var wg sync.WaitGroup
	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		// Pasar referencias a todas las colas para que el worker pueda "robar"
		otherQueues := make([]*TaskQueue, 0, numWorkers-1)
		for j := range taskQueues {
			if i != j {
				otherQueues = append(otherQueues, taskQueues[j])
			}
		}
		go worker(i, taskQueues[i], otherQueues, &wg)
	}

	wg.Wait()
	fmt.Println("Todas las tareas han sido completadas")
}

Ventajas:

  • Balancea bien la carga, especialmente con trabajadores "hambrientos" y "saciados".
  • Reduce el tiempo de inactividad de los hilos de trabajo.

Desventajas:

  • Puede aumentar los costos de acceso a colas remotas (competencia por bloqueos).
  • Es más difícil de implementar y depurar comparado con planificadores centralizados.

En Golang, el work stealing ocurre entre P (procesadores) y sus colas locales de goroutines. Cuando un M (hilo del sistema operativo), asociado a P, termina de procesar sus goroutines locales, intenta robar goroutines de la cola global o de las colas locales de otros P.