Ответ
Да, слайс можно передавать в разные горутины, но это требует особой осторожности, так как может привести к гонке данных (data race).
Слайс в Go — это не сам массив данных, а структура-дескриптор, содержащая:
- Указатель на базовый массив в памяти.
- Длину (
len
). - Ёмкость (
cap
).
Когда вы передаете слайс в горутину, вы копируете этот дескриптор. Однако указатель в скопированном дескрипторе будет указывать на тот же самый базовый массив. Если несколько горутин одновременно изменяют данные в этом массиве, возникает состояние гонки.
Пример с проблемой (гонка данных):
В этом коде есть две проблемы: гонка данных при записи в s[i]
и некорректное использование переменной цикла i
в замыкании.
func main() {
s := []int{1, 2, 3, 4, 5}
var wg sync.WaitGroup
wg.Add(len(s))
for i := range s {
go func() {
// ОШИБКА: Небезопасная запись в общий слайс.
// К тому же, переменная `i` будет иметь последнее значение из цикла.
s[i] = s[i] * 2
wg.Done()
}()
}
wg.Wait()
fmt.Println(s) // Результат непредсказуем
}
Способы решения
-
Использование мьютексов (
sync.Mutex
) Это классический способ синхронизации доступа к общим данным. Каждая горутина блокирует мьютекс перед доступом к слайсу и разблокирует после.func main() { s := []int{1, 2, 3, 4, 5} var wg sync.WaitGroup var mu sync.Mutex wg.Add(len(s)) for i := 0; i < len(s); i++ { go func(idx int) { defer wg.Done() mu.Lock() // Блокируем доступ s[idx] *= 2 // Безопасно изменяем mu.Unlock() // Разблокируем }(i) // Передаем копию индекса в горутину } wg.Wait() fmt.Println(s) // [2 4 6 8 10] (порядок может отличаться) }
-
Передача данных по значению (копирование) Если горутине не нужно изменять исходный слайс, а только читать данные, безопаснее передать ей копию элемента.
func main() { s := []int{1, 2, 3} var wg sync.WaitGroup for i := range s { wg.Add(1) // Передаем значение s[i], а не индекс go func(val int) { defer wg.Done() // Работаем с локальной копией `val` fmt.Println(val * 2) }(s[i]) } wg.Wait() }
-
Использование каналов Каналы — идиоматичный способ для безопасного обмена данными между горутинами в Go. Можно отправлять данные или результаты обработки через каналы, избегая прямого доступа к общей памяти.