0Pricing
Swift Academy · レッスン

データ競合の問題

並行した変更が安全でない理由を理解します。

「データ競合の問題」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全4レッスンが含まれています。

データ競合とは

データ競合とは、2つ以上のスレッドが同じメモリ位置に同時にアクセスし、そのうち少なくとも1つが書き込みで、さらにアクセス間の同期がない状態です。

結果は未定義動作となり、値の破損、クラッシュ、負荷がかかったときだけ現れるバグなどが発生します。

共有された可変状態

データ競合の根本原因は共有された可変状態です。複数のタスクが同じ変数を読み書きできると、処理の順序が予測できなくなります。

下のカウンターでは、count += 1がアトミックな操作ではなく、読み取り・変更・書き込みの処理であるため、インクリメントが失われる可能性があります。

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

インクリメントが失われる理由

count += 1という文は、値を読み込み、1を加え、結果を書き戻すという3つのステップにコンパイルされます。

2つのスレッドが同時に5を読み込むと、どちらも6を書き戻すため、一方のインクリメントが失われます。

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

大きな値のティアリング

更新の消失だけでなく、複数ワードで構成される値(構造体や、一部のプラットフォームにおける64ビット値など)への同時書き込みではティアリングが発生することがあります。つまり、読み取り側が一方の書き込みの前半と、別の書き込みの後半を読み取ってしまいます。

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

従来の解決策:ロック

Swift Concurrency以前の従来の解決策はロック(ミューテックス)でした。一度に1つのスレッドだけがロックを保持し、アクセスを直列化します。

ロックは機能しますが、ロック解除のし忘れ、デッドロック、優先度逆転など、誤用しやすい問題があります。

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

直列ディスパッチキュー

もう1つの従来の方法は直列ディスパッチキューです。すべての変更処理を1つのキューに集約するため、処理が重なることはありません。

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

手動の同期が脆弱な理由

ロックとキューは、開発者の規律に依存します。コンパイラーは、すべてのアクセスが保護されているかどうかをチェックしません。

保護されていない読み取りが1つでも入り込むと、競合が再び発生します。コンパイル時の保証はありません。

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

Swift Concurrencyによる大きな変化

Swift Concurrencyは、データ競合の安全性を慣習ではなく言語機能にします。

3つのツールが連携します。保護された可変状態にはactor、安全に共有できる型にはSendable、そして両方を強制するコンパイラーを使います。

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

アクターによるアクセスの直列化

アクターは、1つのタスクだけがその変更処理を一度に実行することを保証します。ランタイムが自動的にアクセスを直列化します。

ロックを自分で記述する必要はありません。アクターモデルが同期を提供します。

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

コンパイル時の強制

コンパイラーは、アクターを経由せずにアクター隔離された状態へアクセスすることを許可しません。アクター間の呼び出しは非同期(await)になります。

これにより、実行時の競合がコンパイル時エラーになります。

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

Sendableがスレッド間で渡せるものを制限する

Sendableプロトコルは、並行処理の境界を越えて安全に渡せる型を示します。

コンパイラーは、Sendableではない可変状態を別のタスクに送ることを阻止し、型レベルで競合を防ぎます。

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

理解度チェック:データ競合

データ競合が発生する原因についての理解度を確認しましょう。

まとめ:データ競合の問題

データ競合は、共有された可変状態に同期なしで同時にアクセスすることで発生し、更新の消失、ティアリング、未定義動作を引き起こします。

従来の解決策(ロックや直列キュー)は機能しますが、チェックされないため脆弱です。Swift Concurrencyは慣習を強制に置き換えます。actorが状態を隔離し、Sendableが境界を越えて渡せるものを制限し、コンパイラーが安全性を検証します。このコースの残りでは、これらのツールを詳しく見ていきます。

よくある質問

「データ競合の問題」レッスンは無料ですか?

はい。「データ競合の問題」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全4レッスンが含まれています。

「データ競合の問題」で何を学びますか?

並行した変更が安全でない理由を理解します。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Swift Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「データ競合の問題」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSwift Academyレッスンでコードを書いて実行できますか?

はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. データ競合の問題
  2. Sendableプロトコル
  3. Actorの分離とnonisolated
  4. 厳密な並行性チェックへの移行
← Swift Academyに戻る