0Pricing
Swift Academy · Lekcja

Problem wyścigu danych

Poznanie, dlaczego równoczesna modyfikacja jest niebezpieczna.

Problem wyścigu danych to bezpłatna lekcja Swift Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Swift Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Swift Academy zawiera 4 lekcji w sumie.

Czym jest wyścig danych

Wyścig danych występuje, gdy co najmniej dwa wątki jednocześnie uzyskują dostęp do tej samej lokalizacji pamięci, co najmniej jeden z tych dostępów jest zapisem, a między nimi nie ma synchronizacji.

Rezultatem jest niezdefiniowane zachowanie: uszkodzone wartości, awarie lub błędy ujawniające się tylko pod obciążeniem.

Współdzielony zmienny stan

Źródłem wyścigów danych jest współdzielony zmienny stan. Jeśli wiele zadań może odczytywać i zapisywać tę samą zmienną, kolejność operacji staje się nieprzewidywalna.

Poniższy licznik może gubić inkrementacje, ponieważ count += 1 jest operacją odczyt-modyfikacja-zapis, a nie operacją atomową.

final class Counter {
    var count = 0
    func increment() {
        count += 1 // read, add, write: not atomic
    }
}

Dlaczego inkrementacje są gubione

Instrukcja count += 1 kompiluje się do trzech kroków: odczytania wartości, dodania jedynki i zapisania wyniku.

Jeśli dwa wątki odczytają jednocześnie wartość 5, oba zapiszą 6, a jedna inkrementacja zniknie.

// Thread A loads 5
// Thread B loads 5
// Thread A stores 6
// Thread B stores 6  <-- lost update

Rozrywanie większych wartości

Oprócz gubienia aktualizacji równoczesne zapisy wartości wielosłowowych, takich jak struktura lub wartość 64-bitowa na niektórych platformach, mogą prowadzić do rozerwania: czytelnik zobaczy połowę jednego zapisu i połowę innego.

struct Point { var x: Double; var y: Double }
var p = Point(x: 0, y: 0)
// Concurrent writes may leave x from one write and y from another

Dawne rozwiązanie: blokady

Przed wprowadzeniem Swift Concurrency klasycznym rozwiązaniem była blokada (mutex). W danym momencie tylko jeden wątek posiada blokadę, co szereguje dostęp.

Blokady działają, ale łatwo użyć ich nieprawidłowo: można zapomnieć o ich zwolnieniu, doprowadzić do zakleszczenia lub odwrócenia priorytetów.

import Foundation
final class SafeCounter {
    private let lock = NSLock()
    private var count = 0
    func increment() {
        lock.lock()
        defer { lock.unlock() }
        count += 1
    }
}

Szeregowe kolejki dyspozytora

Innym klasycznym podejściem jest szeregowa kolejka dyspozytora. Wszystkie modyfikacje są kierowane do jednej kolejki, więc nigdy nie zachodzą jednocześnie.

import Foundation
final class QueueCounter {
    private let queue = DispatchQueue(label: "counter")
    private var count = 0
    func increment() {
        queue.async { self.count += 1 }
    }
}

Dlaczego ręczna synchronizacja jest zawodna

Blokady i kolejki opierają się na dyscyplinie. Kompilator nie sprawdza, czy każdy dostęp jest chroniony.

Wystarczy jeden niezabezpieczony odczyt, aby wyścig powrócił. Nie ma gwarancji zapewnianej na etapie kompilacji.

// Nothing stops a careless reader from doing this:
// let value = counter.count  // unsynchronized read = race

Swift Concurrency zmienia reguły gry

Swift Concurrency sprawia, że bezpieczeństwo względem wyścigów danych staje się funkcją języka, a nie tylko przyjętą konwencją.

Współpracują ze sobą trzy narzędzia: actor do ochrony zmiennego stanu, Sendable dla typów bezpiecznych do współdzielenia oraz kompilator, który egzekwuje oba te wymagania.

actor Counter {
    private var count = 0
    func increment() { count += 1 }
}

Serializacja dostępu przez actor

Typ actor gwarantuje, że w danym momencie tylko jedno zadanie wykonuje jego kod modyfikujący. Środowisko wykonawcze automatycznie szereguje dostęp.

Nie trzeba pisać blokady — model actor zapewnia synchronizację.

actor BankAccount {
    private(set) var balance = 0
    func deposit(_ amount: Int) { balance += amount }
}

Weryfikacja na etapie kompilacji

Kompilator nie pozwala uzyskać dostępu do stanu izolowanego przez actor bez przejścia przez aktora. Wywołania między aktorami stają się asynchroniczne (await).

Dzięki temu wyścigi występujące podczas działania programu zamieniają się w błędy kompilacji.

let account = BankAccount()
// Must await: balance is actor-isolated
// let b = await account.balance

Sendable określa, co może przekraczać granice wątków

Protokół Sendable oznacza typy, które można bezpiecznie przekazywać przez granice współbieżności.

Kompilator blokuje przekazywanie zmiennego stanu niezgodnego z Sendable do innego zadania, eliminując wyścig na poziomie typów.

struct Money: Sendable {
    let amount: Int
    let currency: String
}

Szybkie sprawdzenie: wyścigi danych

Sprawdź, czy rozumie Pan/Pani, co powoduje wyścig danych.

Podsumowanie: problem wyścigów danych

Wyścigi danych wynikają ze współdzielonego zmiennego stanu, do którego uzyskuje się jednoczesny dostęp bez synchronizacji, co prowadzi do gubienia aktualizacji, rozrywania wartości i niezdefiniowanego zachowania.

Dawne rozwiązania — blokady i kolejki szeregowe — działają, ale nie są weryfikowane i bywają zawodne. Swift Concurrency zastępuje konwencję mechanizmami wymuszającymi bezpieczeństwo: actor izoluje stan, Sendable określa, co może przekraczać granice, a kompilator sprawdza bezpieczeństwo. W dalszej części kursu przyjrzymy się tym narzędziom szczegółowo.

Często zadawane pytania

Czy lekcja „Problem wyścigu danych” jest bezpłatna?

Tak — pełny tekst „Problem wyścigu danych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Swift Academy, przejdź na CoddyKit PRO. Kurs Swift Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Problem wyścigu danych”?

Poznanie, dlaczego równoczesna modyfikacja jest niebezpieczna. Ćwiczysz Swift Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Swift Academy?

Nie wymagamy żadnego doświadczenia. Swift Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Problem wyścigu danych”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Swift Academy?

Tak. Każda lekcja Swift Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Problem wyścigu danych
  2. Protokół Sendable
  3. Izolacja aktorów i nonisolated
  4. Migracja do ścisłej współbieżności
← Powrót do Swift Academy