Как горутины в Go работают с неинициализированными глобальными переменными (сегмент .bss)?

Ответ

В Go все горутины работают в едином адресном пространстве. Это означает, что глобальные и статические переменные являются общими для всех горутин.

Сегмент .bss — это деталь реализации компилятора, где хранятся неинициализированные глобальные переменные. Для Go-разработчика важно знать следующее:


  1. Нулевые значения: Все переменные в этом сегменте автоматически инициализируются их нулевыми значениями (0 для чисел, nil для указателей и слайсов, false для bool и т.д.) при запуске программы.



  2. Общий доступ: Любая горутина может читать и изменять эти переменные.



  3. Состояние гонки (Race Condition): Если несколько горутин одновременно обращаются к одной и той же глобальной переменной, и хотя бы одна из них выполняет запись, возникает состояние гонки. Это приводит к непредсказуемому поведению программы.



  4. Синхронизация: Для безопасной работы с общими переменными необходимо использовать примитивы синхронизации, такие как мьютексы (sync.Mutex) или атомарные операции (sync/atomic).


Пример (проблема):

package main

import (
    "fmt"
    "sync"
)

var globalCounter int // Находится в .bss, инициализируется нулем

func main() {
    var wg sync.WaitGroup
    // Запускаем 1000 горутин, которые инкрементируют счетчик
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            globalCounter++ // ОПАСНО: состояние гонки
        }()
    }

    wg.Wait()
    // Результат будет непредсказуемым и почти наверняка < 1000
    fmt.Println("Final counter:", globalCounter)
}

Пример (решение с sync/atomic):

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

var globalCounter int64 // Для атомарных операций лучше использовать int64/uint64

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddInt64(&globalCounter, 1) // БЕЗОПАСНО: атомарная операция
        }()
    }

    wg.Wait()
    // Результат всегда будет 1000
    fmt.Println("Final counter:", globalCounter)
}