Middle
Čo je workshopping?
sobes.tech AI
Odpoveď od AI
Work stealing je mechanizmus vyvažovania záťaže v viacvláknových systémoch, kde nečinný vlákno (worker) "kradne" úlohy z fronty iného, zaneprázdneného vlákna.
Kľúčové vlastnosti:
- Decentralizované: Každé vlákno spravuje svoju lokálnu frontu úloh.
- Aktívne: Nečinné vlákno aktívne hľadá prácu u iných.
- Vysoký výkon: Efektívne pre úlohy s nerovnomernou záťažou.
V Golangu sa používa v plánovači (scheduler) na rozdeľovanie gorútin medzi vlákna operačného systému (M). Keď jedno M dokončí svoju aktuálnu prácu (balík gorútin na P), hľadá gorutiny u iných P cez ich lokálne fronty.
// Pseudo-kód demonštrujúci koncept work stealing
package main
import "fmt"
import "sync"
import "time"
type Task struct {
id int
}
// Simulácia fronty úloh
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
}
// Pokus o "ukradnutie" úlohy z inej fronty
func (q *TaskQueue) StealTask() (Task, bool) {
q.mu.Lock()
defer q.mu.Unlock()
if len(q.tasks) < 2 { // Ne "kradneme", ak je málo úloh (optimalizácia)
return Task{}, false
}
// Kradneme zo stredu alebo konca, aby sme sa vyhli konfliktu s lokálnym odobratím
index := len(q.tasks) / 2
task := q.tasks[index]
q.tasks = append(q.tasks[:index], q.tasks[index+1:]...)
return task, true
}
// Simulácia pracovného vlákna
func worker(id int, localQueue *TaskQueue, otherQueues []*TaskQueue, wg *sync.WaitGroup) {
defer wg.Done()
for {
// Pokus o vzatie úlohy z lokálnej fronty
task, ok := localQueue.GetLocalTask()
if ok {
fmt.Printf("Pracovník %d vykonáva úlohu %d lokálne\n", id, task.id)
time.Sleep(100 * time.Millisecond) // Simulácia práce
continue
}
// Ak je lokálna fronta prázdna, pokúsime sa ukradnúť
stolen := false
for _, queue := range otherQueues {
if queue == localQueue {
continue // Nekradneme od seba
}
task, ok := queue.StealTask()
if ok {
fmt.Printf("Pracovník %d ukradol úlohu %d\n", id, task.id)
time.Sleep(100 * time.Millisecond) // Simulácia práce
stolen = true
break // Úspešne ukradnuté, ukončíme krádež
}
}
if !stolen {
// Ak sa nepodarilo ukradnúť, možno už nie sú žiadne úlohy
// V reálnom plánovači sú zložitejšie mechanizmy ukončenia
// Pre demonštráciu jednoducho ukončíme po určitom čase čakania
fmt.Printf("Pracovník %d čaká...\n", id)
time.Sleep(50 * time.Millisecond)
// V reálnom scenári by tu bol mechanizmus ukončenia alebo parkovania vlákna
// return // Pre demonštráciu umožníme ukončenie
break // Pre zjednodušenie simulácie
}
}
}
func main() {
numWorkers := 4
numTasks := 20
taskQueues := make([]*TaskQueue, numWorkers)
for i := range taskQueues {
taskQueues[i] = &TaskQueue{}
}
// Rozdeľujeme úlohy nerovnomerne pre demonštráciu work stealing
for i := 0; i < numTasks; i++ {
queueIndex := i % 2 // Viac úloh má prvé dve vlákna
taskQueues[queueIndex].AddTask(Task{id: i})
}
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
// Odovzdávame odkazy na všetky fronty, aby si pracovník mohol "kradnúť"
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("Všetky úlohy sú vykonané")
}