#19.1 golang Горутины и работа с ними - асинхронного выполнение, многопоточность
Primary tabs
Forums:
Ранее мы упоминали горутины — это легковесные потоки выполнения в Go, работающие поверх системных потоков (threads).
Они позволяют запускать функции параллельно (асинхронно). Давайте посмотрим как это делается в коде
Асинхронное выполнение кода
Ты можешь запустить функцию в горутине, добавив перед её вызовом ключевое слово go:
import (
"fmt"
"time"
)
// обычная функция
func say(comment string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(comment, "-интерация", i)
}
}
func main() {
// работает параллельно функции main в горутине
go say("Асинхронный вызов")
// работает внутри горутины main
say("Синхронный вызов")
}
- после запуска получим распечатку вроде:
Асинхронный вызов -интерация 0 Синхронный вызов -интерация 0 Синхронный вызов -интерация 1 Асинхронный вызов -интерация 1 Синхронный вызов -интерация 2 Асинхронный вызов -интерация 2 Асинхронный вызов -интерация 3 Синхронный вызов -интерация 3 Синхронный вызов -интерация 4
- из-за параллельности выводы (вызовы println() основной и вручную запущенной горутин перемешаются.
Уточним:
- Любая функция перед, которой написано клочевое слово go запускается как горутина
- функция main() же всегда запускает главную горутину.
Непредсказуемость выполнения - состояние гонки
Горутины паралельно работают в одном адресном пространстве поэтому для корректного доступа к общим ресурсам требуется явная синхронизация.
Без неё может возникнуть гонка данных (race condition). Такая ситуация показана в примере ниже:
package main
import (
"fmt"
"time"
)
func incIt(a *int) {
*a += 1 // инкрементируем
fmt.Print(*a, "_")
}
func deincIt(a *int) {
*a -= 1 // декрементируем
fmt.Print(*a, "_")
}
func main() {
a := 1
for i := 0; i < 3; i++ {
go incIt(&a)
go incIt(&a)
go incIt(&a)
go deincIt(&a)
go deincIt(&a)
go deincIt(&a)
// Ждем завершения горутин
time.Sleep(500 * time.Millisecond) // полсекунды должно хватить
fmt.Println() // переносим строчку
}
}
-- здесь мы три раза инкементируем и три раза декрементируем значение (передаваемое "по ссылке" - через указатель), из-за паралельности выполнения мы не можем предсказать на самом деле ни то в каком именно порядке будут проходить изменения значения переменной a, ни финальное её состояние.
Распечатка этой программы, если её запустить будет выглядть +- случайным образом, но главное что строки будут отличаться одна от другой:
0_1_0_1_-1_0_ 0_0_0_1_1_1_ 0_1_2_3_2_1_
- это происходит как раз из-за состояния гонки - мы не знаем какой именно поток быстрее доберется до цели (в данном случае до значения переменной "а")
Завершение фоновых горутин, после выхода из main()
Выше в коде main() мы использовали sleep() чтобы дождаться завершения фоновых горутин, которые иначе могли бы просто не успеить выполнится.
Итоги
- Многопоточность в Go реализована через горутины.
- Доступ нескольких горутин к общим ресурсам в отсутствие сихронизации приведет к гонке данных.
Что дальше
Далее в уроках рассмотрим вопрос синхронизации работы горутин
- Log in to post comments
- 103 reads
vedro-compota
Thu, 04/16/2026 - 20:51
Permalink
переписать иллюстрацию
переписать иллюстрацию состояния гонки - чтобы финальные результаты разнились
_____________
матфак вгу и остальная классика =)