Skip to content

アクターモデルの導入

「可変状態を安全に共有する」という問題はSwiftに限らず考えられてきました。そして、その基礎研究により生まれたのがアクターモデルです。1970年代から並行処理の研究で使われてきたモデルであり、Swift Concurrencyではこれを採用しました。

Wikipediaのアクターモデルの説明を日本語にすると、次のようになります。

アクターは受け取ったメッセージに応じて、内部で判断を下し、アクターをさらに作り、メッセージを送り、次のメッセージへの応答方法を決められる。アクターは自身の非公開の状態を変更できるが、ほかのアクターに影響を与えられるのはメッセージを通じてだけである。

つまり、アクターモデルでは並行して動く処理を、それぞれが自分の状態を持つ主体として捉えます。この主体をアクターと呼びます。

アクターには、次の特徴があります。

  • 自分が管理する状態を持つ
  • 受け取ったメッセージに応じて処理する
  • 自分の状態を変更できる
  • ほかのアクターへメッセージを送れる

アクターの内部状態を変更するのは、そのアクター自身です。在庫を減らしたいときは、在庫を管理するアクターへ「商品を一つ購入する」という処理を依頼します。

Inventory actor。Task Aの購入とTask Bの在庫取得は依頼として届き、actorがstockとpurchase()を一つずつ処理する。

二つのTaskが在庫の値を直接変更するのではなく、在庫を管理するアクターへ処理を依頼する。アクターは受け取った依頼を一つずつ処理し、自分の状態を変更する。

状態と、その状態を扱う処理を一つの主体へまとめることで、データ競合が防がれます。Swiftは、このアクターモデルの考え方を言語機能として取り入れています。

ではここからは、Swift Concurrencyのactorを解説します。

Swift Concurrencyでは、このアクターをactorという型で表現します。管理する状態はプロパティ、状態を更新はメソッドとして実装します。 実際に、在庫をアクターモデルで表現したものが以下です。

actor Inventory {
var stock = 1
func purchase() -> Int {
stock = stock - 1
return stock
}
}

stockは、このInventoryが管理する在庫数です。在庫を減らす処理はpurchase()メソッドで実装されています。

そして、アクターの外からメソッドを通さず、状態を更新することはできません。

let inventory = Inventory()
inventory.stock = 0 // コンパイルエラー

状態を変えたいときは、purchase()を呼ぶ必要があります。つまり、アクターの状態を更新するためには、そのアクターを通す必要があります。そして、アクターは一度に一つの処理しか行いません。なので、アクターの状態を更新したい外側のプログラムは、他にもアクターを操作したいプログラムと同期する必要があります。つまり、一定期間呼び出しもとはアクターを操作するのを待たされる可能性があります。 そこで、Swiftではアクターの外からアクターを触る場合は、awaitをつけて操作する必要があります。

func buy(from inventory: Inventory) async {
let remaining = await inventory.purchase()
print(remaining)
}

呼び出す時にはawaitが必要ですが、purchase()の宣言にasyncはありません。アクターの中からアクターの状態を更新することに同期は必要ないため、actorの定義の中ではasyncをつける必要はありません。外側から状態に触る時のみ、awaitが必要になります。