Как сделать слайс потокобезопасным в Go?

Ответ

Для обеспечения потокобезопасности слайса в Go используются примитивы синхронизации, такие как sync.Mutex или sync.RWMutex. sync.RWMutex предпочтительнее для сценариев с частыми операциями чтения и редкими операциями записи, так как он позволяет множественным горутинам читать данные одновременно.

Пример реализации потокобезопасного слайса:

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

// NewSafeSlice создает новый потокобезопасный слайс.
func NewSafeSlice() *SafeSlice {
    return &SafeSlice{
        items: make([]string, 0),
    }
}

// Append добавляет элемент в слайс, защищая операцию мьютексом.
func (s *SafeSlice) Append(item string) {
    s.mu.Lock() // Захватываем блокировку для записи
    defer s.mu.Unlock() // Освобождаем блокировку после завершения операции
    s.items = append(s.items, item)
}

// Get возвращает элемент по индексу, защищая операцию мьютексом чтения.
// Возвращает элемент и булево значение, указывающее на успешность операции (если индекс в пределах).
func (s *SafeSlice) Get(index int) (string, bool) {
    s.mu.RLock() // Захватываем блокировку для чтения
    defer s.mu.RUnlock() // Освобождаем блокировку чтения
    if index < 0 || index >= len(s.items) {
        return "", false // Индекс вне диапазона
    }
    return s.items[index], true
}

// Len возвращает текущую длину слайса.
func (s *SafeSlice) Len() int {
    s.mu.RLock()
    defer s.mu.RUnlock()
    return len(s.items)
}

// Remove удаляет элемент по индексу.
func (s *SafeSlice) Remove(index int) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    if index < 0 || index >= len(s.items) {
        return false
    }
    s.items = append(s.items[:index], s.items[index+1:]...)
    return true
}

Важные моменты:

  1. Все операции чтения и записи, изменяющие или обращающиеся к слайсу, должны быть защищены соответствующим мьютексом (Lock/Unlock для записи, RLock/RUnlock для чтения).
  2. Используйте defer для гарантированного освобождения блокировки, даже если произойдет паника.
  3. Для более сложных сценариев или других структур данных (например, мапов) рассмотрите использование sync.Map или паттернов на основе каналов, которые могут предложить более идиоматичные или производительные решения для определенных задач.