Go не все функции асинхронные
Нет, в Go не “все функции асинхронные”. По умолчанию вызов функции в Go — синхронный: выполняется в текущей горутине и возвращает управление только после return.
Асинхронность/конкурентность в Go появляется только тогда, когда вы явно запускаете новую горутину оператором go (или когда сама вызываемая функция внутри себя запускает горутины — но это уже деталь реализации конкретной функции/библиотеки, а не свойство “всех функций”).
1) Что реально делает обычный вызов
Обычный вызов:
f()
fmt.Println("после f")
означает: в этой же горутине выполнить f целиком, и только потом печатать "после f".
Доказательство на примере (порядок гарантирован)
package main
import (
"fmt"
"time"
)
func f() {
fmt.Println("f: start")
time.Sleep(200 * time.Millisecond)
fmt.Println("f: end")
}
func main() {
fmt.Println("main: before f")
f()
fmt.Println("main: after f")
}
Ожидаемый порядок (он будет всегда таким):
main: before f f: start f: end main: after f
Если бы “функции были асинхронными, а вызов по умолчанию их ожидал”, то в языке должна быть сущность “асинхронная функция” и механизм ожидания (как async/await). В Go этого нет: f() — просто обычное выполнение.
2) Что делает go f() на самом деле
go f() не “вызывает асинхронную функцию”, а создаёт новую горутину и запускает выполнение f параллельно (конкурентно) с текущей горутиной. Текущая горутина не ждёт, она продолжает дальше.
Пример: порядок уже не гарантирован
package main
import (
"fmt"
"time"
)
func f() {
fmt.Println("f: start")
time.Sleep(200 * time.Millisecond)
fmt.Println("f: end")
}
func main() {
fmt.Println("main: before go f")
go f()
fmt.Println("main: after go f")
time.Sleep(300 * time.Millisecond) // даём f успеть
}
Возможные варианты вывода (зависит от планировщика):
Вариант A:
main: before go f main: after go f f: start f: end
Вариант B:
main: before go f f: start main: after go f f: end
Это и есть конкурентность: go создаёт отдельный поток выполнения (горутину). Но важно: если не подождать, программа может завершиться раньше, чем горутина успеет что-то сделать.
3) Ключевая разница: у go f() нет “результата”, который можно “подождать”
Если бы логика была “все функции async, а f() — это await, а go f() — no-await”, тогда должен существовать способ сделать примерно так:
x := go f() // и получить future/promise
Но в Go так нельзя. Оператор go:
- не возвращает значения
- не даёт “хэндла”/future на результат
- “ожидание результата” делается вручную: каналами,
sync.WaitGroup, mutex и т.п.
Пример “дождаться результата” через канал
package main
import (
"fmt"
)
func f() int {
return 42
}
func main() {
ch := make(chan int)
go func() {
ch <- f()
}()
x := <-ch // это явное ожидание
fmt.Println(x)
}
Здесь ожидание делаете не вызыванием f(), а операцией чтения из канала.
4) Частая причина путаницы: I/O может быть “неблокирующим внутри”, но семантика вызова всё равно синхронная
Например, net.Conn.Read может быть реализован через внутренний сетевой poller (runtime может “усыпить” горутину и выполнять другие), но для кода пользователя вызов:
n, err := conn.Read(buf)
всё равно блокирует текущую горутину, пока данные не будут прочитаны (или пока не случится ошибка/таймаут). Это не “асинхронная функция”, а просто оптимизированная реализация блокирующей операции.
Вывод
- В Go функции не являются асинхронными по умолчанию.
- Обычный вызов
f()— синхронный, выполняется в текущей горутине. go f()— это явный запускfв новой горутине, без ожидания.- “Ждать результат” в Go нужно явно: каналами,
WaitGroupи т.д. Встроенногоasync/awaitи “await по умолчанию” нет.
Если хотите, могу показать ещё один “железобетонный” пример с runtime.NumGoroutine()/стеком, чтобы наглядно увидеть, что f() не создаёт новую горутину, а go f() — создаёт.