перейти к тупиковой ситуации на канале - PullRequest
0 голосов
/ 01 ноября 2018

Я только начал изучать Голанг и не до конца понимал, как возникает тупик. Вот пример, адаптированный из учебника по игровой площадке golang:

 package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q:= <-quit:
            fmt.Println(q)
            return
        }
    }
}
func pp(c chan int, quit chan int){
   for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)
   // here it's good, no deadlock
     go pp(c,quit)    
     fibonacci(c, quit)
   // but if change the order of above two line:
   // fibonacci(c,quit)
   // go pp(c,quit)
   // it will deadlock
}

Почему важен порядок двух строк выше?

Ответы [ 3 ]

0 голосов
/ 01 ноября 2018

Каналы make(chan int) имеет неявный размер ноль (ref: https://golang.org/ref/spec#Making_slices_maps_and_channels)

Канал нулевого размера не буферизован. Канал указанного размера make(chan int, n) буферизуется. Увидеть http://golang.org/ref/spec#Send_statements для обсуждения буферизованных и небуферизованных каналы. Пример на http://play.golang.org/p/VZAiN1V8-P иллюстрирует разницу.

Здесь c := make(chan int) небуферизован.

Если изменить порядок этих двух строк

 go pp(c,quit)    
 fibonacci(c, quit)

К

fibonacci(c,quit)
go pp(c,quit)

Это приведет к блокировке программы. В функции fibonacci посмотрите на оператор select.

select {
    case c <- x:
        x, y = y, x+y
    case q:= <-quit:
        fmt.Println(q)
        return
}

select оператор останется заблокированным, пока один из case не будет заполнен. Как go pp(c,quit) выполняется после fibonacci(c,quit), поэтому нет процесса очистки канала c или отправки сигнала на канал quit. Поэтому функция fibonacci(c,quit) останется заблокированной.

0 голосов
/ 01 ноября 2018

У вас есть две функции, которые должны работать одновременно, чтобы работала связь по каналу - одна должна принимать одновременно с отправкой другой. В этом случае:

 go pp(c,quit)    
 fibonacci(c, quit)

Вы запускаете pp как программу, она запускается, затем вы вызываете fibonacci, так что оба работают, и все работает. Если вы измените его, как вы предложили, на:

 fibonacci(c, quit)
 go pp(c,quit)    

Затем вы вызываете fibonacci как обычную функцию, а не как процедуру, что означает, что следующая строка не будет выполнена, пока не вернется fibonacci. Поскольку fibonacci ожидает, что что-то получает от своего канала, он блокируется до тех пор, пока это не произойдет - чего никогда не происходит, потому что ничего не читает из него одновременно. Отсюда твой тупик.

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

 go fibonacci(c, quit)
 pp(c,quit)    

Будет работать нормально, потому что он вызывает fibonacci одновременно, а затем вызывает pp, который может выполняться одновременно. Вы можете увидеть это в действии здесь: https://play.golang.org/p/4o3T0z5n40X

Если бы вы использовали WaitGroup, вы могли бы даже запускать их как goroutines, и они запускались бы одновременно:

 go fibonacci(c, quit, wg)
 go pp(c,quit, wg)    

Хотя в вашем случае это не является необходимым и добавляет сложности.

0 голосов
/ 01 ноября 2018

Если вы сначала позвоните fibonnaci, он отправит значение на канал, но получатель не готов. В этом причина тупика.

Примечание:

По умолчанию отправляет и получает блок, пока другая сторона не будет готова. Это позволяет goroutines синхронизироваться без явных блокировок или условные переменные.

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

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q:= <-quit:
            fmt.Println(q)
            return
        }
    }
}

func pp(c chan int, quit chan int){
   for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)

    go func(){
       fibonacci(c, quit)
    }()
    pp(c,quit)
}

Рабочий код на Игровая площадка Go

Всегда не забывайте ждать, пока рутина go закончится в таких ситуациях. Но когда вы сначала вызываете fibonnaci, он отправил значение, но получатель не готов, что приводит к тупику.

Edit:

Потому что даже если вы подождете, пока закончится рутина. Это все равно создаст тупик, потому что каналы не синхронизируются как:

основной пакет

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q:= <-quit:
            fmt.Println(q)
            return
        }
    }
}

func pp(c chan int, quit chan int){
   defer wg.Done()
   for i := 0; i < 10; i++ {
            fmt.Println(<-c)
   }
   quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    fibonacci(c, quit)
    wg.Add(1)
    go pp(c,quit)  
    wg.Wait()
}

Выход:

фатальная ошибка: все рутины спят - тупик!

маршрут 1 [выбрать]: main.fibonacci (0x434080, 0x4340c0) /tmp/sandbox779301309/main.go:13 + 0xc0 main.main () /tmp/sandbox779301309/main.go:34 + 0x80

Если вы измените свой код и создадите регистр по умолчанию в цикле выбора for. Тогда он удовлетворит этот случай и вернется, а ваш основной выйдет. Никогда не заканчивая цикл, дайте ему ждать возврата в случае выхода, чтобы он вернулся. Это будет работать:

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case q, ok := <-quit:
            if ok {
                fmt.Println(q)
            }
            return
        default:
            fmt.Println("No value in any of the channel")
            return
        }
    }
}

func pp(c chan int, quit chan int) {
    for i := 0; i < 10; i++ {
        if value, ok := <-c; ok {
            fmt.Println(value)
        }
    }
    quit <- 0
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    fibonacci(c, quit)
    go pp(c, quit)
}

Пример игровой площадки

...