Поведение Goroutines с каналами - PullRequest
0 голосов
/ 28 января 2019

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

Я попытался понять потокпрограмма, но выполняется оператор после "вызова goroutine", хотя goroutine вызывается, затем выполняются операторы goroutines,

при втором "вызове goroutine" поведение отличается и последовательностьпечати / потока изменений программы.

Ниже приведен код:

    package main

    import "fmt"

    func main() {
        fmt.Println("1")
        done := make(chan string)
        go test(done)
        fmt.Println("7")
        fmt.Println(<-done)
        fmt.Println("8")
        fmt.Println(<-done)
        fmt.Println("9")
        fmt.Println(<-done)
    }
    func test(done chan string) {
        fmt.Println("2")
        done <- "3"
        done <- "10"
        fmt.Println("4")
        done <- "5"
       fmt.Println("6")
    }

Результат приведенного выше кода:

1
7
2
3
8
10
9
4
6
5

Пожалуйста, помогите мне понять, почему икак получается этот результат.

1 Ответ

0 голосов
/ 29 января 2019

Добро пожаловать в переполнение стека.Я надеюсь, что смогу помочь вам немного лучше понять каналы и функции.

Концепция 1: Каналы

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

Запись в небуферизованный канал

Код, который выглядит так, записывает данные в один конец канала.

ch <- value

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

Чтение с небуферизованного канала

Чтение с небуферизованного канала (визуализируйте удаление значения из канала)код для этого выглядит как

[value :=] <-ch

, когда вы читаете документацию по коду [вещи в] квадратных скобках указывают, что то, что внутри них, является необязательным.Выше, без [value: =] вы просто извлекаете значение из канала и не используете его ни для чего.

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

Если в канале еще нет значения NO, он будет ждать записи значения в канал, прежде чем продолжить .Другими словами, поток блокируется до тех пор, пока канал не получит значение для чтения.

Концепция 2: Городуны

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

Ваш вопрос возникает, когдавы пытаетесь выяснить порядок выполнения кода, если у вас есть несколько подпрограмм, выполняющихся одновременно.Это хороший вопрос, и другие правильно заявили, что это зависит .Когда вы создаете две процедуры, порядок выполнения строк кода является произвольным.

Код, указанный ниже, вместе с программой может сначала вывести executing a() или end main().Это связано с тем, что порождение группирования означает, что одновременно происходит два потока (потока) выполнения.В этом случае один поток остается в main(), а другой начинает выполнение первой строки в a().То, как среда выполнения решает выбрать, какой запускать первым, является произвольным.

func main() {
    fmt.Println("start main()")
    go a()
    fmt.Println("end main()")
}

func a() {
    fmt.Println("executing a()")
}

Goroutines + Channels

Теперь давайте воспользуемся каналом, чтобы контролировать порядок выполнения, когда выполняется get.Единственная разница теперь в том, что мы создаем канал, передаем его в процедуру и ждем, пока его значение будет записано, прежде чем продолжить в main.Ранее мы обсуждали, как процедура чтения значения из канала должна ждать, пока в канале не появится значение, прежде чем продолжить.Поскольку executing a() всегда печатается перед записью канала, мы всегда будем ждать чтения значения, введенного в канал, до тех пор, пока не будет напечатано executing a().Поскольку мы читаем с канала (что происходит после записи канала) перед печатью end main(), executing a() всегда будет печатать до end main().Я создал эту игровую площадку , чтобы вы могли сами ее запустить.

func main() {
    fmt.Println("start main()")
    ch := make(chan int)
    go a(ch)
    <-ch
    fmt.Println("end main()")
}

func a(ch chan int) {
    fmt.Println("executing a()")
    ch <- 0
}

Ваш пример

Я думаю, что в этот момент вы могли бы выяснить, что происходит, когда и что может происходить в другом порядке.Моя первая попытка была неудачной, когда я прошел через это в своей голове (см. Историю изменений).Ты должен быть осторожен!Я не дам правильного ответа после редактирования, так как понял, что это может быть домашнее задание.

РЕДАКТИРОВАТЬ: больше семантики о <-done

На моемВначале я забыл упомянуть, что fmt.Println(<-done) концептуально совпадает со следующим:

value := <-done
fmt.Println(value)

Это важно, потому что это помогает увидеть, когда поток main() читает из doneканал, он не печатает его одновременно.Это два отдельных этапа выполнения.

Надеюсь, это поможет.Дайте мне знать, если у вас есть вопросы.

...