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

Можно ли передавать слайс в разные горутины?

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

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

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

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

Безопасность передачи срезов между горутинами

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

Внутреннее устройство слайса и риски

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

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    data := make([]int, 5) // Слайс с базовым массивом из 5 элементов

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(idx int) {
            defer wg.Done()
            data[idx] = idx * 10 // ГОНКА ДАННЫХ: параллельная запись!
        }(i)
    }

    wg.Wait()
    fmt.Println(data) // Результат непредсказуем из-за гонки
}

Способы безопасной работы

1. Передача копии данных (значений)

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

func processChunk(chunk []int) {
    // Работаем с локальной копией фрагмента данных
    for i := range chunk {
        chunk[i] = chunk[i] * 2
    }
}

func main() {
    data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    chunkSize := len(data) / 2
    
    go processChunk(append([]int{}, data[:chunkSize]...)) // Явное копирование
    go processChunk(append([]int{}, data[chunkSize:]...))
    // Исходный data остаётся неизменным
}

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

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

func worker(id int, jobs <-chan []int, results chan<- int) {
    for chunk := range jobs {
        sum := 0
        for _, v := range chunk {
            sum += v
        }
        results <- sum
    }
}

func main() {
    data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    jobs := make(chan []int, 2)
    results := make(chan int, 2)
    
    // Запускаем воркеров
    for w := 1; w <= 2; w++ {
        go worker(w, jobs, results)
    }
    
    // Отправляем задания (каждый воркер получает свой фрагмент)
    jobs <- data[:5]
    jobs <- data[5:]
    close(jobs)
    
    // Собираем результаты
    total := 0
    for i := 0; i < 2; i++ {
        total += <-results
    }
    fmt.Println("Total sum:", total)
}

3. Синхронизация с помощью мьютексов

Если необходимо модифицировать общий слайс из нескольких горутин:

type SafeSlice struct {
    mu    sync.RWMutex
    items []int
}

func (ss *SafeSlice) Append(item int) {
    ss.mu.Lock()
    defer ss.mu.Unlock()
    ss.items = append(ss.items, item)
}

func (ss *SafeSlice) Get(index int) int {
    ss.mu.RLock()
    defer ss.mu.RUnlock()
    return ss.items[index]
}

func main() {
    var wg sync.WaitGroup
    safeSlice := SafeSlice{}
    
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(val int) {
            defer wg.Done()
            safeSlice.Append(val)
        }(i)
    }
    
    wg.Wait()
    fmt.Println("Slice length:", len(safeSlice.items))
}

Важные рекомендации

  • Всегда проверяйте код на гонки данных с помощью go run -race или go test -race
  • При параллельной модификации слайса используйте мьютексы (sync.Mutex или sync.RWMutex) или другие примитивы синхронизации
  • Если возможно, проектируйте программу так, чтобы каждая горутина работала со своей независимой копией данных
  • Помните об изменении ёмкости слайса: операция append может создать новый базовый массив, что приведёт к потере синхронизации с другими горутинами
  • Для высоконагруженных сценариев рассмотрите использование lock-free структур данных или разделение данных по сегментам

Итог: передавать слайс между горутинами можно, но это требует понимания, что фактически разделяется доступ к базовому массиву. Без должной синхронизации такая практика приводит к неопределённому поведению программы и трудноотлавливаемым ошибкам.

Можно ли передавать слайс в разные горутины? | PrepBro