2014-09-03 3 views
1

Я хотел попробовать тест FizzBuzz (Why can't programmers program) и использовал Go. Это, в основном, цикл от 1 до 100 и печать «Fizz», когда счетчик циклов делится на 3, «Buzz», когда делится на 5, «FizzBuzz», когда они делятся на оба, а просто распечатывают номер.Почему эта функция golang _not_ работает навсегда?

После этого итеративно и рекурсивно, я хотел сделать это одновременно (или используя каналы). Я придумал следующий код, который работал, к моему удивлению:

func fizzbuzzconc() { 
    // Channels for communication 
    fizzchan := make(chan int) 
    buzzchan := make(chan int) 
    fizzbuzzchan := make(chan int) 
    nonechan := make(chan int) 

    // Start go routine to calculate fizzbuzz challenge 
    go func() { 
     for i := 1; i <= 100; i++ { 
      if i % 3 == 0 && i % 5 == 0 { 
       fizzbuzzchan <- i 
      } else if i % 3 == 0 { 
       fizzchan <- i 
      } else if i % 5 == 0 { 
       buzzchan <- i 
      } else { 
       nonechan <- i 
      } 
     } 
    }() 

    // When or how does this for loop end? 
    for { 
     select { 
     case i := <-fizzchan: 
      fmt.Println(i, "Fizz") 
     case i := <-buzzchan: 
      fmt.Println(i, "Buzz") 
     case i := <-fizzbuzzchan: 
      fmt.Println(i, "FizzBuzz") 
     case i := <-nonechan: 
      fmt.Println(i, i) 
     } 
    } 
} 

Я не могу понять, как и почему цикл останавливается. Не существует условия разрыва или оператора возврата. Почему в конечном итоге он заканчивается?

+0

Добавить 'default' на ваш выбор. Любое изменение? – bishop

+0

Когда я добавляю по умолчанию, выполнение выполняется намного медленнее, и оно не выходит. Теперь у меня есть ожидаемое поведение, спасибо. – Kiril

+1

Да. Причина в том, что весь ваш «случай» вводит системный вызов через «Println». Go проверяет состояние канала во время системных вызовов и видит, когда последний из них опустошает все каналы. В этот момент срабатывает детектор тупиковой ситуации и убивает петлю. Когда вы добавляете пустой 'default', есть ветка, которая не проходит через системные вызовы и не проходит через нее. Наименьшее изменение, которое вы можете сделать, чтобы заставить ваш код выйти из строя, заключается в том, чтобы добавить канал группы ожидания в ваш случай по умолчанию, как показано в этом ответе: http://stackoverflow.com/a/21819794/2908724 – bishop

ответ

1

Это не очень хорошо работает.

Что происходит, через какое-то время происходит изнурение из-за оставшейся очереди, ожидающей канала, где не горит горутин. Итак, у вас есть мертвая блокировка (которая является фатальной ошибкой, заканчивающейся программой), а не чистое окончание.

В целом, это «работает», потому что двигатель go достаточно умен, чтобы обнаружить мертвый замок.

+0

', который является fata error, заканчивающийся программой, но заканчивается без ошибок. Разве это не паникует? – Kiril

+0

@Kiril В моем тесте есть ошибка: http://play.golang.org/p/j-1mLNAAnJ –

+0

Вы правы, есть ошибка. На моей машине (go version go1.3 окна/amd64) нет ошибки. Что нужно сделать, чтобы он работал, или что было бы оптимальным решением проблемы? Просто добавьте еще один канал, который отправляет значение после выполнения процедуры go? – Kiril

0

@dystroy ответил на ваш вопрос очень хорошо, однако вот как вы можете исправить свой код.

Один из способов выхода из игры - использовать канал выхода, который мы сигнализируем, закрывая его. (Сигнализация, закрывая канал, полезна, потому что на него может сразу прослушиваться более чем одна рутина).

Есть и другие способы сделать это, хотя - если у вас есть только один выходной канал, то вы range над ним, чтобы прочитать результат, и close его, когда закончите. Вы можете легко переписать это, чтобы работать таким образом.

Вы можете использовать sync.Waitgroup, чтобы убедиться, что процедура завершения также завершена.

Playground

func main() { 
    // Channels for communication 
    fizzchan := make(chan int) 
    buzzchan := make(chan int) 
    fizzbuzzchan := make(chan int) 
    nonechan := make(chan int) 
    quit := make(chan struct{}) 

    // Start go routine to calculate fizzbuzz challenge 
    go func() { 
     for i := 1; i <= 100; i++ { 
      if i%3 == 0 && i%5 == 0 { 
       fizzbuzzchan <- i 
      } else if i%3 == 0 { 
       fizzchan <- i 
      } else if i%5 == 0 { 
       buzzchan <- i 
      } else { 
       nonechan <- i 
      } 
     } 
     close(quit) 
    }() 

    // When or how does this for loop end? 
OUTER: 
    for { 
     select { 
     case i := <-fizzchan: 
      fmt.Println(i, "Fizz") 
     case i := <-buzzchan: 
      fmt.Println(i, "Buzz") 
     case i := <-fizzbuzzchan: 
      fmt.Println(i, "FizzBuzz") 
     case i := <-nonechan: 
      fmt.Println(i, i) 
     case <-quit: 
      break OUTER 
     } 
    } 
    fmt.Println("All done") 
} 
0

@OneOfOne упоминается метод sync.WaitGroup который я думаю падает большинство в соответствии с тем, как вы могли бы сделать это в Go. Учитывая, что goroutines очень дешевы, и проблема может быть решена параллельно, мы можем создать рутину для каждого входа и отправить результаты на буферизованный канал.

//Size to check 
size := 100 

results := make(chan Result, size) 

// Create the WaitGroup and set the latch size. 
var wg sync.WaitGroup 
wg.Add(size) 

// Create a goroutine for each parallel operation 
for i := 1; i <= size; i++ { 
    i := i //bind value into closure 
    go func() { 
     results <- fizzbuzz(i) 
     wg.Done() //release a latch 
    }() 
} 

//wait for all the goroutines to finish. 
wg.Wait() 

//close the channel so we can exit the below for loop. 
close(results) 

//Range over the results and exit once the channel has closed. 
for x := range results { 
    fmt.Printf("i: %d result: %s\n", x.Nr, x.Val) 
} 

код Детская площадка: http://play.golang.org/p/80UafMax7M