Sobes.tech
Middle+

Jak bezpieczne jest używanie slices w kontekście wykonywania równoległego (Concurrency)?

sobes.tech AI

Odpowiedź od AI

Użycie slices nie jest bezpieczne w kontekście dostępu współbieżnego (Concurrency) bez dodatkowych mechanizmów synchronizacji. Wielokrotny jednoczesny dostęp do odczytu i zapisu lub dwa jednoczesne dostępy do zapisu do tego samego obszaru pamięci (którym jest podstawowa tablica slice'a) prowadzą do wyścigu danych (data race).

Przyczyny niebezpieczeństwa:

  1. Zmiana podstawowej tablicy: Slice jest reprezentacją podstawowej tablicy. Operacje dodawania elementów (append), szczególnie jeśli przekraczają obecną pojemność, mogą prowadzić do re-alokacji i kopiowania danych do nowej podstawowej tablicy. Jeśli w tym momencie inna gorutyna próbuje czytać lub pisać w starej lub nowej podstawowej tablicy, dochodzi do wyścigu danych.
  2. Zmiana nagłówka slice'a: Sam nagłówek slice'a (struktura zawierająca wskaźnik na podstawową tablicę, długość i pojemność) może być zmieniany podczas operacji takich jak append. Jednoczesne odczyty i zapisy tego nagłówka bez blokad prowadzą do nieokreślonego zachowania.

Mechanizmy zapewniające bezpieczeństwo:

  • sync.Mutex / sync.RWMutex: Blokuje dostęp do slice'a przed każdą operacją odczytu lub zapisu. sync.RWMutex jest preferowany w scenariuszach z częstym odczytem i rzadkim zapisem.

    import "sync"
    
    type SafeSlice struct {
        mu    sync.RWMutex
        items []int
    }
    
    func (s *SafeSlice) Append(item int) {
        s.mu.Lock() // Blokada do zapisu
        defer s.mu.Unlock()
        s.items = append(s.items, item)
    }
    
    func (s *SafeSlice) Get(index int) (int, bool) {
        s.mu.RLock() // Blokada do odczytu
        defer s.mu.RUnlock()
        if index < 0 || index >= len(s.items) {
            return 0, false
        }
        return s.items[index], true
    }
    
  • Kanały (Channels): Przekazywanie własności danych przez kanały. Jedna gorutyna zapisuje dane, inna odczytuje je z kanału. To jest odpowiednie dla scenariuszy strumieniowego przetwarzania danych.

    // Przykład użycia kanału do przekazywania elementów
    // w scenariuszu, gdzie jedna gorutyna produkuje, a druga konsumuje
    
  • sync.Map: Odpowiedni, jeśli slice jest używany jako podstawa do mapowania kluczy na wartości lub w scenariuszach, gdzie kolejność nie ma znaczenia, ale krytyczne jest bezpieczeństwo współbieżne.

Wniosek: Użycie slice'ów w scenariuszach współbieżnych wymaga jawnego zarządzania synchronizacją, aby zapobiec wyścigom danych. Prosty dostęp do slice'a z wielu gorutyn jednocześnie nie jest bezpieczny.