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

Ответ

Принцип работы с инициализированными глобальными переменными (хранящимися в сегменте .data) абсолютно идентичен работе с неинициализированными (из .bss). Все горутины разделяют общее адресное пространство, и доступ к этим переменным должен быть синхронизирован.

  • Сегмент .data содержит глобальные и статические переменные, которым присвоены начальные значения в коде.
  • Общий доступ: Все горутины имеют доступ к этим переменным.
  • Риск состояния гонки: Одновременная несинхронизированная запись или смешанное чтение/запись из нескольких горутин приведет к состоянию гонки.

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

package main

import (
    "fmt"
    "sync"
)

// Переменная инициализирована значением 100
var sharedData = 100 // Находится в сегменте .data

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
        defer wg.Done()
        sharedData = 200 // Запись из первой горутины
    }()

    go func() {
        defer wg.Done()
        sharedData = 300 // Запись из второй горутины
    }()

    wg.Wait()
    // Результат непредсказуем: может быть 200 или 300
    fmt.Println(sharedData)
}

Пример (решение с sync.Mutex):

package main

import (
    "fmt"
    "sync"
)

var sharedData = 100
var mu sync.Mutex // Мьютекс для защиты sharedData

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
        defer wg.Done()
        mu.Lock()
        sharedData = 200
        mu.Unlock()
    }()

    go func() {
        defer wg.Done()
        mu.Lock()
        sharedData = 300
        mu.Unlock()
    }()

    wg.Wait()
    // Результат предсказуем и зависит от того, какая горутина первой захватит мьютекс
    fmt.Println(sharedData)
}

Вывод: Неважно, была ли глобальная переменная инициализирована или нет. Если она изменяется несколькими горутинами, доступ к ней обязательно должен быть защищен примитивами синхронизации.