Как работают каналы Go

Недавно начал изучать Go. На данный момент изучаю каналы. У меня вопрос, почему в этой программе значение в канал передается:

package main

import (
    "fmt"
)

func f (c chan string){
    fmt.Println(<-c)
}

func main() {
    //r := bufio.NewReader(os.Stdin)

    c := make(chan string)

    go f(c)

    //text, _ := r.ReadString('\n')
    //fmt.Println(text)
    fmt.Println("Hello")
    c <- "jhj"
}

А в этой нет

package main

import (
    "bufio"
    "fmt"
    "os"
)

func f (c chan string){
    fmt.Println(<-c)
}

func main() {
    r := bufio.NewReader(os.Stdin)

    c := make(chan string)

    go f(c)

    text, _ := r.ReadString('\n')
    fmt.Println(text)
    //fmt.Println("Hello")
    c <- "text"
}

Ответы (2 шт):

Автор решения: Ainar-G

Ни ваш первый, ни второй вариант не даёт гарантий работы. Всё потому что ваш main не ждёт выполнения работы. Добавьте синхронизацию между f и main. Самое примитивное — добавить в конец функции main код select{}, который заблокирует выполнение main.

→ Ссылка
Автор решения: Pavel Borzenkov

На самом деле значение в канал передается в обоих случаях. То, что вы наблюдаете, является классической гонкой (race condition).

Вы запускаете функцию f в отдельной горутине, которая блокируется на чтение из канала c. Как только вы записываете в канал какую-либо строку (jhj в первом случае и text во втором), функция разблокируется и продолжает выполнение. Проблема в том, что main тоже продолжает выполнение после записи в канал параллельно с функцией f на другом ядре процессора и успевает завершится до того, как выполнится fmt.Println в функции f. Но это никак не гарантируется и поэтому и называется гонкой.

Более того, даже исполнение на одном процессорном ядре не гарантирует, что функция fmt.Println внутри f выполнится раньше, чем завершится main. Как правильно заметил @Ainar-G, вам нужно добавить синхронизацию и дождаться завершения f. Вот один из вариантов как это можно сделать:

package main

import (
    "bufio"
    "fmt"
    "os"
    "sync"
)

var wg sync.WaitGroup

func f(c chan string) {
    fmt.Println(<-c)
    // сигнализируем о завершении функции
    // по сути делаем wg.Add(-1)
    wg.Done()
}

func main() {
    r := bufio.NewReader(os.Stdin)

    c := make(chan string)

    wg.Add(1) // говорим что будем дожидаться завершения одной функции
    go f(c)

    text, _ := r.ReadString('\n')
    fmt.Println(text)
    c <- "text"
    // ждем завершения функции f
    // Wait вернется, когда счетчик внутри wg станет равным 0
    wg.Wait()
}
→ Ссылка