Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Concurrency]を読んで #541

Merged
merged 5 commits into from
Apr 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 11 additions & 35 deletions language-guide/concurrency.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 同時並行処理\(Concurrency\)

最終更新日: 2024/03/2
最終更新日: 2024/04/13
原文: https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html

非同期操作を行う。
Expand Down Expand Up @@ -86,9 +86,9 @@ func generateSlideshow(forGallery gallery: String) async {
}
```

ビデオをレンダリングするコードが同期的であると仮定した場合、中断ポイントは含まれていません。ビデオをレンダリングする作業にも時間がかかる可能性があります。しかし、明示的な中断ポイントを追加するために `Task.yield` を定期的に呼び出すことができます。このように長時間実行するコードを構造化することで Swift はこのタスクの進捗と、プログラム内の他のタスクの進捗のバランスをとることができます。
ビデオをレンダリングするコードが同期的であると仮定した場合、中断ポイントは含まれていません。ビデオをレンダリングする作業にも時間がかかる可能性があります。しかし、明示的な中断ポイントを追加するために `Task.yield()` を定期的に呼び出すことができます。このように長時間実行するコードを構造化することで Swift はこのタスクの進捗と、プログラム内の他のタスクの進捗のバランスをとることができます。

[`Task.sleep(for:tolerance:clock:)`](https://developer.apple.com/documentation/swift/task/sleep(for:tolerance:clock:))メソッド並行処理がどのように機能するかを学ぶためにシンプルなコードを書くのに便利です。このメソッドは、少なくとも指定し時間だけ現在のタスクを中断します。以下は `listPhotos(inGallery:)` 関数で、`sleep(for:tolerance:clock:)` を使ってネットワーク操作の待ち時間をシミュレートしています:
[`Task.sleep(for:tolerance:clock:)`](https://developer.apple.com/documentation/swift/task/sleep(for:tolerance:clock:))メソッドは並行処理がどのように機能するかを学ぶためにシンプルなコードを書くのに便利です。このメソッドは、少なくとも指定した時間だけ現在のタスクを中断します。以下は `listPhotos(inGallery:)` 関数で、`sleep(for:tolerance:clock:)` を使ってネットワーク操作の待ち時間をシミュレートしています:

```swift
func listPhotos(inGallery name: String) async throws -> [String] {
Expand All @@ -105,7 +105,7 @@ let photos = try await listPhotos(inGallery: "雨の週末")

非同期関数は throwing 関数といくつか似ている点があります。
非同期や throwing 関数を定義する際は、`async` と `throws` を付け、呼び出し側は `await` と `try` を付けます。
throwing 関数は他の throwing 関数を呼び出すことができるのと同じように、非同期関数は他の非同期関数を呼び出すことができ、
throwing 関数は他の throwing 関数を呼び出すことができるのと同じように、非同期関数は他の非同期関数を呼び出すことができます。

しかし、重要な違いがあります。エラーをハンドリングをするために、エラーをスローするコードを `do-catch` ブロックで囲めたり、他の場所でエラーをハンドリングするために、コードで起こったエラー保持するために `Result` を使うことができます。
これらのアプローチは、エラーをスローしない関数から throwing 関数を呼び出せるようにします。
Expand All @@ -121,19 +121,9 @@ func availableRainyWeekendPhotos() -> Result<[String], Error> {

対照的に、同期コードから非同期コードを呼び出して結果を待つことができるように、非同期コードをラップする安全な方法はありません。Swift の標準ライブラリは、意図的にこの安全でない機能を省略しています。なぜなら、自分で実装しようとすると、わかりづらいデータ競合やスレッド問題、デッドロックのような問題につながる可能性があるからです。既存のプロジェクトに Concurrency コードを追加するときは、トップダウンで作業します。具体的には、コードの最上位レイヤーを並行処理に変換することから始め、そのコードが呼び出す関数やメソッドの変換を開始し、プロジェクトのアーキテクチャーを一度に 1 レイヤーずつ変換していきます。ボトムアップのアプローチは、これまでのところ同期コードが非同期コードを呼び出すことができないためできません。

> NOTE
> [`Task.sleep(until:tolerance:clock:)`](https://developer.apple.com/documentation/swift/task/sleep(until:tolerance:clock:)) メソッドは、同時並行処理が機能する方法を学ぶために簡単なコードを書くときに役立ちます。このメソッドは何もしませんが、それがリターンする前に少なくとも指定されたナノ秒数処理を待ちます。下記は、ネットワーク操作の待機をシミュレートするために `sleep(nanoseconds:)` を使用する `listPhotos(inGallery:)` 関数のバージョンです。
>
> ```swift
> func listPhotos(inGallery name: String) async throws -> [String] {
> try await Task.sleep(for: .seconds(2))
> return ["IMG001", "IMG99", "IMG0404"]
> }
> ```

## 非同期シーケンス\(Asynchronous Sequences\)

前のセクションの `listPhotos(inGallery:)` は、非同期に配列全体を一度にまとめて返し、全ての配列の要素は既に取得できています。もう 1 つの方法として、非同期シーケンス\(_asynchronous sequence_\)を使用して、コレクションの要素を 1 つずつ待機することができます。下記では非同期シーケンスを使って配列の要素を 1 つ 1 つ取得しています:
前のセクションの `listPhotos(inGallery:)` は、非同期に、全ての配列の要素が取得できてから配列全体を一度にまとめて返します。もう 1 つの方法として、非同期シーケンス\(_asynchronous sequence_\)を使用して、コレクションの要素を 1 つずつ待機することができます。下記では非同期シーケンスを使って配列の要素を 1 つ 1 つ取得しています:

```swift
import Foundation
Expand Down Expand Up @@ -161,7 +151,7 @@ let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)
```

しかし、このアプローチには重要な欠点があります: ダウンロードは非同期で、その実行中に他のタスクが実行できますが、`downloadPhoto(named:)` の呼び出しは一度に 1 つの呼び出ししか実行されません。つまり、次の写真のダウンロードを開始する前に、各写真のダウンロードの完了を待ちます。しかし、これらの操作を待機する必要はありません。各写真の処理は独立して、同時にダウンロードできます。
しかし、このアプローチには重要な欠点があります: ダウンロードは非同期で、その実行中に他のタスクが実行できますが、`downloadPhoto(named:)` の呼び出しは一度に 1 つの呼び出ししか実行されません。つまり、次の写真のダウンロードを開始する前に、前の写真のダウンロードの完了を待ちます。しかし、これらの操作を待機する必要はありません。各写真の処理は独立して、同時にダウンロードできます。

非同期関数を同時並行に実行するには、`let` の前に `async` を書き、この定数を使用する度に `await` を書きます。

Expand Down Expand Up @@ -202,8 +192,6 @@ show(photos)

以下は、任意の枚数の写真を処理する、写真をダウンロードするコードの別バージョンです:

処理の正しさを保つためのいくつかの責務は開発者にありますが、この明示的な親子関係によって、Swift は、キャンセルの伝播のような行動を処理したり、コンパイル時にいくつかのエラーを検出することができます。

```swift
await withTaskGroup(of: Data.self) { group in
let photoNames = await listPhotos(inGallery: "夏休み")
Expand Down Expand Up @@ -310,18 +298,6 @@ let result = await handle.value

切り離されたタスクの管理の詳細については、[Task](https://developer.apple.com/documentation/swift/task)を参照ください。

### タスクのキャンセル\(Task Cancellation\)

Swift の同時並行処理は協調キャンセルモデル\(_cooperative cancellation model_\)を使用しています。各タスクは、実行中の適切な時点でキャンセルされたかどうかを確認し、適切ないかなる方法でもキャンセルに応答します。実行しているタスクに応じて、通常次のいずれかを意味します:

* `CancellationError` のようなエラーをスローする
* `nil` または空のコレクションを返す
* 部分的に完了したタスクを返す

タスクがキャンセルされたかどうかをチェックするには、キャンセルされていた場合に `CancellationError` をスローする[Task.checkCancellation\(\)](https://developer.apple.com/documentation/swift/task/3814826-checkcancellation)を呼び出すか、[Task.isCancelled](https://developer.apple.com/documentation/swift/task/3814832-iscancelled)の値を確認し、自分でコードのキャンセルを処理します。例えば、ギャラリから写真をダウンロードしているタスクは、一部だけダウンロードされたデータを削除してネットワークを切断する必要があるかもしれません。

キャンセルを手動で伝播するには、[Task.cancel\(\)](https://developer.apple.com/documentation/swift/task/3851218-cancel) を呼び出します。

## アクター\(Actors\)

タスクは、プログラムを分離された同時並行処理の断片に分割するために使用できます。タスクは互いに分離されているので同時並行に実行しても安全ですが、タスク間で何らかの情報を共有したい場合もあります。アクター\(_actors_\)を使用すると、同時並行処理をするコード間で安全に情報を共有することができます。
Expand All @@ -342,7 +318,7 @@ actor TemperatureLogger {
}
```

中括弧ペア\(`{}`\)の定義の前に `actor` キーワードを付けてアクターを導入します `TemperatureLogger` アクターには、外部の他のコードがアクセスできるプロパティがあり、`max` プロパティはアクター内のコードのみが最大値を更新できるように制限されています。
中括弧ペア\(`{}`\)の定義の前に `actor` キーワードを付けてアクターを導入します`TemperatureLogger` アクターには、外部の他のコードがアクセスできるプロパティがあり、`max` プロパティはアクター内のコードのみが最大値を更新できるように制限されています。

構造体やクラスと同じイニシャライザの構文を使用して、アクターのインスタンスを作成します。アクターのプロパティやメソッドにアクセスするとき、中断する可能性のあるポイントを示すために `await` を使用します。例えば:

Expand Down Expand Up @@ -381,13 +357,13 @@ extension TemperatureLogger {
print(logger.max) // エラー
```

アクターのプロパティはそのアクターの独立したローカル状態の一部のため、`await` を書かないと `logger.max` へのアクセスは失敗します。このプロパティにアクセスするコードは、アクタの一部として実行する必要があり、これは非同期操作であり、`await` を記述する必要があります。SSwift は、実行されているコードのみがアクターに分離されたローカルの状態にアクセスできることを保証します。この保証はアクター分離\(_actor isolation_\)と呼ばれています。
アクターのプロパティはそのアクターの独立したローカル状態の一部のため、`await` を書かないと `logger.max` へのアクセスは失敗します。このプロパティにアクセスするコードは、アクタの一部として実行する必要があり、これは非同期操作であり、`await` を記述する必要があります。Swift は、実行されているコードのみがアクターに分離されたローカルの状態にアクセスできることを保証します。この保証はアクター分離\(_actor isolation_\)と呼ばれています。

Swift の並行処理モデルの以下の側面は、共有された可変状態について推論しやすくするために一緒に機能します:

- 中断する可能性のあるポイントの間のコードは、他の並行処理コードから中断される可能性はなく、順次実行されます
- アクターのローカル状態と相互作用するコードは、そのアクター上でのみ実行されます
- アクタは一度に 1 つのコードだけを実行します
- アクターは一度に 1 つのコードだけを実行します

このような保証があるため、アクターの内部にあるコードは `await` を含まず、プログラム内の他の場所で一時的に不正な状態になるリスクなしに更新できます。例えば、以下のコードは測定された温度を華氏から摂氏に変換します:

Expand All @@ -401,9 +377,9 @@ extension TemperatureLogger {
}
```

上のコードでは、測定値の配列を一度に 1 つずつ変換しています。map 操作の進行中、いくつかの温度は華氏で、他の温度は摂氏です。しかし、どのコードにも `await` が含まれていないため、このメソッドには潜在的な中断ポイントがありません。このメソッドが変更する状態はアクターに属し、そのコードがアクター上で実行される場合を除き、それを読んだり変更したりするコードから保護されています。つまり、単位変換の進行中に、他のコードが部分的に変換された温度のリストを読む方法がないということです。
上のコードでは、測定値の配列を一度に 1 つずつ変換しています。map 操作の進行中、いくつかの温度は華氏で、他の温度は摂氏です。しかし、どのコードにも `await` が含まれていないため、このメソッドには潜在的な中断ポイントがありません。このメソッドが変更する状態はアクターに属し、そのアクター上ではないコードが読んだり変更することから保護されています。つまり、単位変換の進行中に、他のコードが部分的に変換された温度のリストを読む方法がないということです。

潜在的な中断ポイントをなくすことで一時的な不正状態から保護されているアクター内にコードを書くことに加えて、そのコードを同期メソッドに移すことができます。上記の `convertFahrenheitToCelsius()` メソッドは同期メソッドなので、潜在的な中断ポイントを含まないことが保証されています。この関数は、一時的にデータモデルの一貫性を失わせるコードをカプセル化しその作業を完了させることで、データの一貫性を取り戻す前に他のコードが実行できないことを、コードを読んだ人が簡単に認識できるようにします。将来、この関数に並行コードを追加して、潜在的な中断ポイントを導入しようとすると、バグを導入する代わりにコンパイル時エラーが発生します。
潜在的な中断ポイントをなくすことで一時的な不正状態から保護されているアクター内にコードを書くことに加えて、そのコードを同期メソッドに移すことができます。上記の `convertFahrenheitToCelsius()` メソッドは同期メソッドなので、潜在的な中断ポイントを含まないことが保証されています。この関数は、一時的にデータモデルの一貫性を失わせるコードをカプセル化し、データの一貫性を取り戻す前に他のコードが実行できないようにして作業を完了させることを、コードを読んだ人が簡単に認識できるようにします。将来、この関数に並行コードを追加して、潜在的な中断ポイントを導入しようとすると、バグを導入する代わりにコンパイル時エラーが発生します。

## <a id="sendable-types">`Sendable` 型\(Sendable Types\)</a>

Expand Down