Перейти к содержимому

Пост канала

Библиотека Go (Golang) разработчика
  1. 🧠 Go Memory Model: Почему ваша горутина слепая (и что такое Happens-Before) Напишем простейший код. Одна горутина меняет флаг, другая ждет этого изменения: var done bool var msg string func setup() { msg = "hello, world" done = true } func main() { go setup() // Ждем, пока setup() закончит работу for !done { } fmt.Println(msg) } Джун посмотрит на этот код и скажет: "Ну, он выведет `hello, world`". Сеньор посмотрит на этот код и скажет: "Этот код может уйти в бесконечный цикл, может вывести пустую строку, а может работать нормально. Это лотерея". Добро пожаловать в жестокий мир Go Memory Model. Иллюзия мгновенной памяти Мы привыкли думать, что память - это единый монолит. Записал переменную — она тут же стала доступна всем. Но в реальности код выполняется на современных многоядерных процессорах. У каждого ядра есть свой сверхбыстрый локальный кэш (L1/L2). Когда горутина setup пишет done = true, процессор может положить это значение в свой локальный кэш и не сбрасывать его в общую оперативную память (RAM) еще миллисекунды. А горутина main, работающая на другом ядре, будет бесконечно читать старое значение done == false из своего собственного кэша. Но и это не всё! Компилятор Go невероятно умен. Он смотрит на цикл for !done {} и думает: "Внутри этого цикла переменная done не меняется. Зачем мне читать её из памяти на каждой итерации? Прочитаю один раз, сохраню в регистр процессора и буду проверять его". Итог - вечный цикл (Infinite Loop). Святой Грааль: Happens-Before (Происходит-До) Чтобы навести порядок в этом хаосе, создатели Go написали формальный документ - Модель Памяти (Memory Model). В её основе лежит концепция Happens-Before. > Если событие A происходит до события B, то Модель Памяти гарантирует, что горутина, выполняющая B, абсолютно точно увидит изменения, сделанные в A. Если между действиями нет связи Happens-Before, компилятор и процессор имеют право переупорядочивать инструкции как им вздумается, а кэши могут не синхронизироваться. Как создать связь Happens-Before? Вам нужны примитивы синхронизации. Они работают как жесткие барьеры памяти (Memory Barriers), заставляя процессоры сбросить кэши, а компилятор - прекратить умничать с перестановкой строк кода. ✅ 1. Каналы (Channels) Отправка данных в канал происходит до завершения чтения из этого канала в другой горутине. c := make(chan struct{}) go func() { msg = "hello" c <- struct{}{} // Happens-Before }() <-c // Ждем fmt.Println(msg) // 100% увидит "hello" ✅ 2. Мьютексы (Mutexes) Снятие блокировки mu.Unlock() происходит до успешного захвата mu.Lock() другой горутиной. ✅ 3. Атомики (sync/atomic) Начиная с Go 1.19, в Модель Памяти официально добавили пакет atomic. Атомарные операции теперь гарантированно создают связь Happens-Before. ✅ 4. Жизненный цикл горутины Создание горутины (go func()) происходит до начала выполнения её кода. А вот завершение горутины не имеет гарантий! (Именно поэтому нам нужен sync.WaitGroup, чтобы дождаться её финала). 🔥 Senior Tip: Не пытайтесь обмануть систему Существует соблазн написать код, который работает "без блокировок", опираясь на задержки: time.Sleep(time.Millisecond). Это называется data race (состояние гонки). Даже если на вашей машине с процессором Intel код работает идеально, завтра его скомпилируют под ARM-архитектуру (Apple Silicon или AWS Graviton), у которой гораздо более слабая модель памяти процессора, и ваш код рассыплется в пыль. Всегда используйте флаг -race во время локальной разработки и CI: go test -race ./.... Он ловит отсутствие связей Happens-Before с хирургической точностью. #golang #underhood #architecture #performance #memorymodel 👉 @golang_lib

    Открыть в MAX
    Просмотры
    533
    ER
    1,1%
    Реакции
    6