← Назад к вопросам

Можно ли писать в слайс конкурентно?

2.3 Middle🔥 172 комментариев
#Основы Go

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Конкурентная запись в слайсы в Go

Да, технически писать в слайс конкурентно можно, но это крайне опасная операция, которая приводит к неопределённому поведению и является классической ошибкой в многопоточных программах на Go. Без использования механизмов синхронизации это приведёт к гонкам данных (data races) и потенциально к падению программы.

Почему это опасно: внутренняя структура слайса

Слайс в Go — это не массив, а структура данных, состоящая из трёх компонентов:

  • Указатель (pointer) на базовый массив
  • Длина (length) — количество элементов в слайсе
  • Ёмкость (capacity) — максимальное количество элементов без реаллокации
// Пример структуры слайса (реализация в runtime)
type sliceHeader struct {
    ptr unsafe.Pointer
    len int
    cap int
}

Когда несколько горутин пытаются одновременно модифицировать слайс (например, добавлять элементы через append() или изменять существующие), они одновременно пытаются изменять эту внутреннюю структуру. Это приводит к:

  1. Гонкам при изменении длины и ёмкости: Операция append() может потребовать реаллокации массива, если ёмкость недостаточна. Две горутины могут начать этот процесс одновременно, что приводит к утерянным данным или повреждению памяти.
  2. Гонкам при изменении элементов: Параллельное изменение элементов по одному индексу — классическая гонка данных.
  3. Неконтролируемому изменению указателя: При реаллокации базового массива указатель изменяется. Одна горутина может работать с новым массивом, другая — со старым.

Пример опасного конкурентного доступа

func dangerousExample() {
    var slice []int

    // 10 горутин пытаются одновременно добавлять элементы
    for i := 0; i < 10; i++ {
        go func() {
            slice = append(slice, 1) // Гонка данных!
        }()
    }

    time.Sleep(time.Second)
    fmt.Println(len(slice)) // Результат непредсказуем: может быть от 0 до 10
}

Программа выше не имеет никакой синхронизации, поэтому:

  • Количество элементов в итоговом слайсе непредсказуемо
  • Возможны падения программы (panic) из-за повреждения памяти
  • Результат зависит от порядка выполнения горутин

Как безопасно работать со слайсами конкурентно

Для безопасного конкурентного доступа к слайсу необходимо использовать механизмы синхронизации:

1. Использование мьютексов (sync.Mutex или sync.RWMutex)

func safeExampleWithMutex() {
    var (
        slice []int
        mu    sync.Mutex
    )

    for i := 0; i < 10; i++ {
        go func() {
            mu.Lock()
            slice = append(slice, 1)
            mu.Unlock()
        }()
    }

    time.Sleep(time.Second)
    mu.Lock()
    fmt.Println(len(slice)) // Гарантированно 10
    mu.Unlock()
}

2. Использование каналов для координации

func safeExampleWithChannels() {
    var slice []int
    done := make(chan bool)

    for i := 0; i < 10; i++ {
        go func() {
            // Запись через канал в главную горутину
            done <- true
        }()
    }

    for i := 0; i < 10; i++ {
        <-done
        slice = append(slice, 1) // Запись только в одной горутине
    }

    fmt.Println(len(slice)) // Гарантированно 10
}

3. Использование атомарных операций для отдельных элементов (если слайс уже создан)

Если размер слайса фиксирован и заравест известен, можно безопасно менять элементы по разным индексам конкурентно:

func safeConcurrentElementModification() {
    slice := make([]int, 100)

    // Можем безопасно заполнять разные индексы конкурентно
    for i := 0; i < 100; i++ {
        go func(index int) {
            slice[index] = index // Нет гонки, если индексы уникальны
        }(i)
    }

    time.Sleep(time.Second)
}

Практические рекомендации

  • Никогда не используйте append() конкурентно без синхронизации.
  • Для слайсов, которые читаются и изменяются разными горутинами, всегда используйте мьютексы.
  • Если требуется высокопроизводительная конкурентная работа с коллекциями, рассмотрите специализированные структуры (sync.Map для мапов, каналы или пулы горутин для слайсов).
  • Используйте инструменты для обнаружения гонок: go run -race или go test -race.

Вывод

Писать в слайс конкурентно можно, но только при корректной синхронизации. Без нее это приводит к гонкам данных, неопределённому поведению и потенциальным падениям программы. Go, как язык с сильной поддержкой конкурентности, предоставляет все необходимые механизмы (sync.Mutex, каналы), чтобы делать это безопасно. Игнорирование синхронизации при работе с общими слайсами — одна из самых частых ошибок начинающих Go разработчиков.