2012-04-16 12 views
5

Pracuję nad ćwiczeniem używania kanałów do implementacji kolejki. W szczególności próbuję użyć rozmiaru kanału, aby ograniczyć liczbę równoczesnych goroutinów. Mianowicie, napisałem poniższy kod:Potrzebujesz pomocy w zrozumieniu, dlaczego opcja {} nie blokuje na zawsze

package main 

import "fmt" 
import "time" 
import "math/rand" 

func runTask (t string, ch *chan bool) { 
     start := time.Now() 
     fmt.Println("starting task", t) 
     time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time 
     fmt.Println("done running task", t, "in", time.Since(start)) 
     <- *ch 
} 

func main() { 
     numWorkers := 3 
     files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} 

     activeWorkers := make(chan bool, numWorkers) 

     for _, f := range files { 
       activeWorkers <- true 
       fmt.Printf("activeWorkers is %d long.\n", len(activeWorkers)) 
       go runTask(f, &activeWorkers) 
     } 
     select{} 
} 

Teraz kod wywala się z:

throw: all goroutines are asleep - deadlock! 

Moje oczekiwanie było, że wezwanie do wyboru będzie blokować zawsze i niech goroutines zakończyć bez impas.

Mam więc dwojakie pytanie: dlaczego nie blokuje się wyboru na zawsze i nie rzuca się w czasie. Funkcja spoczynku() po pętli for, w jaki sposób mogę uniknąć zakleszczeń?

Cheers,

-mtw

Odpowiedz

6

Arlen Cuss napisał już dobrą odpowiedź. Chcę tylko zaproponować inny projekt twojej kolejki pracy. Zamiast ograniczać liczbę wpisów, które Twój kanał może buforować, możesz również odrodzić ograniczoną liczbę goroutinów, które wydają się bardziej naturalne imho. Coś takiego:

package main 

import "fmt" 
import "time" 
import "math/rand" 

func runTask(t string) string { 
    start := time.Now() 
    fmt.Println("starting task", t) 
    time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time 
    fmt.Println("done running task", t, "in", time.Since(start)) 
    return t 
} 

func worker(in chan string, out chan string) { 
    for t := range in { 
     out <- runTask(t) 
    } 
} 

func main() { 
    numWorkers := 3 

    // spawn workers 
    in, out := make(chan string), make(chan string) 
    for i := 0; i < numWorkers; i++ { 
     go worker(in, out) 
    } 

    files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} 

    // schedule tasks 
    go func() { 
     for _, f := range files { 
      in <- f 
     } 
    }() 

    // get results 
    for _ = range files { 
     <-out 
    } 
} 

Można również użyć sync.WaitGroup jeśli chcesz po prostu poczekać, aż wszystkie zadania zostały wykonane, ale stosując out kanał ma tę zaletę, że można zespolić wyniki później. Na przykład, jeśli każde zadanie zwraca liczbę słów w tym pliku, ostatnia pętla może być użyta do podsumowania wszystkich pojedynczych słów.

4

Po pierwsze, nie trzeba przekazać wskaźnik do kanału; kanały, takie jak mapy i inne, are references, co oznacza, że ​​podstawowe dane nie są kopiowane, a jedynie wskaźnik do rzeczywistych danych. Jeśli masz , potrzebujesz samemu wskaźnikiem do chan, będziesz wiedzieć, kiedy ten czas nadejdzie.

Następuje awaria, ponieważ program przechodzi w stan, w którym każda goroutina jest zablokowana. Ten powinien być niemożliwe; jeśli każdy goroutine zostanie zablokowany, wówczas nie będzie mógł przyjść żaden możliwy proces i obudzić kolejną goroutine (a twój program zostanie w konsekwencji zawieszony).

Podstawowa goroutyna pojawia się w select {} - nie czekając na nikogo, po prostu wisząc. Gdy ostatni gorączek runTask kończy się, pozostaje tylko pierwotna goroutyna i nie czeka na nikogo.

Musisz dodać sposób, aby dowiedzieć się, kiedy skończy się każdy glistek; może inny kanał może odbierać zdarzenia końcowe.

To jest trochę brzydkie, ale może być inspiracją.

package main 

import "fmt" 
import "time" 
import "math/rand" 

func runTask(t string, ch chan bool, finishedCh chan bool) { 
    start := time.Now() 
    fmt.Println("starting task", t) 
    time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time 
    fmt.Println("done running task", t, "in", time.Since(start)) 
    <-ch 
    finishedCh <- true 
} 

func main() { 
    numWorkers := 3 
    files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} 

    activeWorkers := make(chan bool, numWorkers) 
    finishedWorkers := make(chan bool) 
    done := make(chan bool) 

    go func() { 
     remaining := len(files) 
     for remaining > 0 { 
      <-finishedWorkers 
      remaining -= 1 
     } 

     done <- true 
    }() 

    for _, f := range files { 
     activeWorkers <- true 
     fmt.Printf("activeWorkers is %d long.\n", len(activeWorkers)) 
     go runTask(f, activeWorkers, finishedWorkers) 
    } 

    <-done 
} 
+0

Byłbym ostrożny z tym; wszystko w ruchu jest przekazywane wartością. Mapy i plasterki są rzeczywiście kopiowane, jednak część skopiowanych danych zawiera wskaźniki do danych podstawowych. http://golang.org/doc/faq#pass_by_value. To subtelne, ale ważne rozróżnienie; plasterki mają inne dane, które nie są wskaźnikami i nie są udostępniane. – Greg

+0

@Greg: "Mapy i plasterki są rzeczywiście kopiowane". Rozłupując włosy, tak myślę. Na podstawie połączonej dokumentacji: "Wartości mapy i wycinka zachowują się jak wskaźniki ... Kopiowanie wartości mapy lub wycinka nie powoduje skopiowania danych, na które wskazuje." Moja terminologia nie jest zgodna z tym, czego używają doktorzy Go, ale semantyka jest w dużej mierze równoważna. – Ashe

+0

@Greg: Czyściłem tam trochę prozę. – Ashe

1

Tux21b opublikował już bardziej idiomatyczne rozwiązanie, ale chciałbym odpowiedzieć na Twoje pytanie w inny sposób. wybierz {} blokuje na zawsze, tak. Zakleszczenie występuje, gdy wszystkie goruteiny są zablokowane. Jeśli skończysz wszystkie inne gorutynki, pozostanie ci tylko zablokowany główny goroutin, który jest impasem.

Zwykle chcesz zrobić coś w swojej głównej goroutine po tym, jak wszyscy inni skończyli, albo używając ich wyników, albo po prostu sprzątali, i dla tego zrobiłbyś to, co zasugerował tux21b.Jeśli naprawdę chcesz tylko, aby główny skończył i zostaw resztę goroutines do wykonania swojej pracy, umieść defer runtime.Goexit() na górze głównej funkcji. Spowoduje to wyjście z niego bez wychodzenia z programu.

Powiązane problemy