2012-09-27 19 views
6

He probado el tour Ir exercise #71golang: goroute con ciertas no se detiene a menos he añadido un fmt.Print()

Si se ejecuta como go run 71_hang.go ok, funciona bien.

Sin embargo, si usa go run 71_hang.go nogood, se ejecutará para siempre.

La única diferencia es la fmt.Print("") adicional en el default en la declaración select.

No estoy seguro, pero sospecho que hay algún tipo de ciclo infinito y condición de carrera. Y aquí está mi solución.

Nota: No es como Go punto muerto no lo hizo throw: all goroutines are asleep - deadlock!

package main 

import (
    "fmt" 
    "os" 
) 

type Fetcher interface { 
    // Fetch returns the body of URL and 
    // a slice of URLs found on that page. 
    Fetch(url string) (body string, urls []string, err error) 
} 

func crawl(todo Todo, fetcher Fetcher, 
    todoList chan Todo, done chan bool) { 
    body, urls, err := fetcher.Fetch(todo.url) 
    if err != nil { 
     fmt.Println(err) 
    } else { 
     fmt.Printf("found: %s %q\n", todo.url, body) 
     for _, u := range urls { 
      todoList <- Todo{u, todo.depth - 1} 
     } 
    } 
    done <- true 
    return 
} 

type Todo struct { 
    url string 
    depth int 
} 

// Crawl uses fetcher to recursively crawl 
// pages starting with url, to a maximum of depth. 
func Crawl(url string, depth int, fetcher Fetcher) { 
    visited := make(map[string]bool) 
    doneCrawling := make(chan bool, 100) 
    toDoList := make(chan Todo, 100) 
    toDoList <- Todo{url, depth} 

    crawling := 0 
    for { 
     select { 
     case todo := <-toDoList: 
      if todo.depth > 0 && !visited[todo.url] { 
       crawling++ 
       visited[todo.url] = true 
       go crawl(todo, fetcher, toDoList, doneCrawling) 
      } 
     case <-doneCrawling: 
      crawling-- 
     default: 
      if os.Args[1]=="ok" { // * 
       fmt.Print("") 
      } 
      if crawling == 0 { 
       goto END 
      } 
     } 
    } 
END: 
    return 
} 

func main() { 
    Crawl("http://golang.org/", 4, fetcher) 
} 

// fakeFetcher is Fetcher that returns canned results. 
type fakeFetcher map[string]*fakeResult 

type fakeResult struct { 
    body string 
    urls []string 
} 

func (f *fakeFetcher) Fetch(url string) (string, []string, error) { 
    if res, ok := (*f)[url]; ok { 
     return res.body, res.urls, nil 
    } 
    return "", nil, fmt.Errorf("not found: %s", url) 
} 

// fetcher is a populated fakeFetcher. 
var fetcher = &fakeFetcher{ 
    "http://golang.org/": &fakeResult{ 
     "The Go Programming Language", 
     []string{ 
      "http://golang.org/pkg/", 
      "http://golang.org/cmd/", 
     }, 
    }, 
    "http://golang.org/pkg/": &fakeResult{ 
     "Packages", 
     []string{ 
      "http://golang.org/", 
      "http://golang.org/cmd/", 
      "http://golang.org/pkg/fmt/", 
      "http://golang.org/pkg/os/", 
     }, 
    }, 
    "http://golang.org/pkg/fmt/": &fakeResult{ 
     "Package fmt", 
     []string{ 
      "http://golang.org/", 
      "http://golang.org/pkg/", 
     }, 
    }, 
    "http://golang.org/pkg/os/": &fakeResult{ 
     "Package os", 
     []string{ 
      "http://golang.org/", 
      "http://golang.org/pkg/", 
     }, 
    }, 
} 

Respuesta

15

Poner un comunicado en su defaultselect cambia la forma en obras selectas. Sin una instrucción predeterminada, la selección bloqueará la espera de mensajes en los canales. Con una instrucción predeterminada, select ejecutará la declaración predeterminada cada vez que no haya nada que leer de los canales. En tu código, creo que esto crea un ciclo infinito. Al poner la instrucción fmt.Print, el planificador puede programar otras rutinas.

Si cambia su código de esta manera, entonces funciona correctamente, usando select de una manera no bloqueante que permite que las otras rutinas funcionen correctamente.

for { 
     select { 
     case todo := <-toDoList: 
      if todo.depth > 0 && !visited[todo.url] { 
       crawling++ 
       visited[todo.url] = true 
       go crawl(todo, fetcher, toDoList, doneCrawling) 
      } 
     case <-doneCrawling: 
      crawling-- 
     } 
     if crawling == 0 { 
      break 
     } 
    } 

Usted puede hacer su trabajo código original si utiliza GOMAXPROCS = 2, que es otro indicio de que el programador está ocupado en un bucle infinito.

Tenga en cuenta que los goroutines están programados de forma cooperativa. Lo que no entiendo completamente acerca de su problema es que select es un punto donde la rutina debería ceder: espero que alguien más pueda explicar por qué no está en su ejemplo.

+1

seleccione no produce debido * * de la sentencia default .Aunque no estoy seguro de si eso es lo que no entiendes del todo, porque tienes las explicaciones "predeterminadas" y GOMAXPROCS. – mna

+1

Eso es exactamente lo que no sabía, ¡gracias! –

+0

"select no cede debido a la declaración predeterminada". es lo que no sé Gracias. – Sungam

5

Tiene una carga de CPU del 100% porque casi todas las veces se ejecutará el caso predeterminado, lo que resulta en un bucle infinito porque se ejecuta una y otra vez. En esta situación, el planificador de Go no controla manualmente a otro goroutine, por diseño. Por lo tanto, cualquier otra rutina de gimnasia nunca tendrá la oportunidad de establecer crawling != 0 y tendrá su bucle infinito.

En mi opinión, debe eliminar el caso predeterminado y en su lugar crear otro canal si desea jugar con la instrucción de selección.

De lo contrario el paquete runtime le ayuda a seguir el camino sucio:

  • runtime.GOMAXPROCS(2) va a funcionar (o exportación GOMAXPROCS = 2), de esta manera usted tendrá más de una hebra de OS de la ejecución
  • llamada runtime.Gosched() dentro de Rastrear de vez en cuando. Aunque la carga de la CPU es del 100%, esto pasará explícitamente el control a otro Goroutine.

Editar: Sí, y la razón por fmt.Printf hace una diferencia: porque pasa explícitamente el control de algunas cosas syscall ...;)

+0

"En esta situación, el programador Go no controla manualmente otra goroutine, por diseño". No del todo cierto. El programador Go 1.0 (?) Hace esto, pero es un programador imperfecto. Las soluciones provisionales que enumeró (o haciendo un llamado al sistema 'fmt.Println()') despertarán el programador. Consulte http://golang.org/doc/go1.2#preemption para obtener detalles sobre estas mejoras en Go 1.2. – ayke

Cuestiones relacionadas