Go не все функции асинхронные

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() — создаёт.


Report Page