Порядок блокировки канала

Я пытаюсь понять, как работают каналы в golang. Код у меня очень простой, но результат удивителен.

Как указано в документации: чтение и запись из/в канал блокирует текущую горутину, поэтому я думал, что запись в канал будет блокировать канал до тех пор, пока основная процедура не даст результатов.

package main

func rtn(messages chan<- string) {
    defer close(messages)

    println("p1")
    messages <- "ping1"

    //for i := 0; i < 10000000; i++ { }

    println("p2")
    messages <- "ping2"
}

func main() {
    messages := make(chan string)
    go rtn(messages)

    for msg := range messages {
        println(msg)
    }
}

Я думал, что это будет печатать

p1
ping1
p2
ping2

а на самом деле печатает

p1
p2
ping1
ping2

Все работает так, как ожидалось. Ознакомьтесь с параллелизмом Golang. (например, здесь)

dmkvl 30.05.2019 15:56

Большинство ожиданий относительно порядка параллельных операций необоснованны.

Adrian 30.05.2019 16:06
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
1
2
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

В этом случае вы знаете только то, что когда вторая горутина здесь messages <- "ping1", основная находится на линии for msg := range messages. Таким образом, нет гарантии, что основной цикл сразу достигнет println(msg). То есть за это время вторая горутина могла двигаться дальше и достигать строк println("p2") и messages <- "ping2".

В качестве контрпримера я добавляю канал только для обеспечения полной синхронизации между отпечатками.

package main

func rtn(messages chan<- string, syncChan chan struct{}) {
    defer close(messages)

    println("p1")
    messages <- "ping1"

    //Wait for main goroutine to print its message
    <-syncChan

    //for i := 0; i < 10000000; i++ { }

    println("p2")
    messages <- "ping2"

    //Wait for main goroutine to print its message
    <-syncChan
}

func main() {
    messages := make(chan string)
    syncChan := make(chan struct{})
    go rtn(messages, syncChan)

    for msg := range messages {
        println(msg)
        //Notify the second goroutine that is free to go
        syncChan <- struct{}{}
    }
}

Который печатает ожидаемый результат:

p1
ping1
p2
ping2

Вот еще один пример, который дает результат, который вы ищете. В этом случае основная горутина принудительно блокируется time.Sleep(). Это сделает вторую горутину готовой к отправке до того, как получатель будет готов к приему. Поэтому отправитель фактически заблокирует операцию отправки.

package main

import (
    "time"
)

func rtn(messages chan<- string) {
    defer close(messages)

    println("p1")
    messages <- "ping1"

    //for i := 0; i < 10000000; i++ { }

    println("p2")
    messages <- "ping2"
}

func main() {
    messages := make(chan string)
    go rtn(messages)

    //Put main goroutine to sleep. This will make the
    //sender goroutine ready before the receiver. 
    //Therefore it will have to actually block!
    time.Sleep(time.Millisecond * 500)

    for msg := range messages {
        println(msg)
    }
}

поправьте меня, если я ошибаюсь, вы говорите, что вторая процедура на самом деле работает между for msg := range messages и println(msg)? Разве это не должно быть заблокировано?

Ratamir 30.05.2019 18:38

Почему он должен быть заблокирован? Операция блокировки — это только for msg := range messages. Более того, для таких случаев есть оптимизации, которые делают каналы. То есть, когда есть горутина, заблокированная для чтения канала, и другая горутина отправляет новое сообщение, которое разблокирует первое, тогда отправитель напрямую копирует значение в заблокированную горутину. На самом деле не помещая сообщение в очередь канала (которая даже не существует в случае небуферизованных каналов). Таким образом, горутину отправителя не нужно блокировать.

Giulio Micheloni 30.05.2019 18:50

прежде чем идти дальше, спасибо, что нашли время, чтобы объяснить эти вещи. Я получил эту идею из [gobyexample.com/каналы], где было сказано По умолчанию отправка и получение блокируются до тех пор, пока отправитель и получатель не будут готовы. , поэтому я подумал, что как только я напишу в канал, основная процедура подхватит его и будет продолжать работать, пока что-то не заблокируется (а затем подхватит второй)

Ratamir 30.05.2019 19:07

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

Giulio Micheloni 30.05.2019 19:28

Другие вопросы по теме