Ответ
В Go все горутины работают в едином адресном пространстве. Это означает, что глобальные и статические переменные являются общими для всех горутин.
Сегмент .bss
— это деталь реализации компилятора, где хранятся неинициализированные глобальные переменные. Для Go-разработчика важно знать следующее:
Нулевые значения: Все переменные в этом сегменте автоматически инициализируются их нулевыми значениями (0 для чисел,
nil
для указателей и слайсов,false
для bool и т.д.) при запуске программы.Общий доступ: Любая горутина может читать и изменять эти переменные.
Состояние гонки (Race Condition): Если несколько горутин одновременно обращаются к одной и той же глобальной переменной, и хотя бы одна из них выполняет запись, возникает состояние гонки. Это приводит к непредсказуемому поведению программы.
Синхронизация: Для безопасной работы с общими переменными необходимо использовать примитивы синхронизации, такие как мьютексы (
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)
}