Avatar
Avatar
shiz
swift-async-algorithmsにManagedCriticalStateというstructがあり、 https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/Locking.swift 用途としては、複数のactorとかqueueで状態を共有する場合や、前に他のスレッドで話にあったwithTaskCancellationHandlerのcancelHandlerで利用することが想定されているのですが、 https://github.com/apple/swift-async-algorithms/pull/7 actor内部の値にsyncにアクセスしたい場合にも使えるのかなと思ったのですが、どうなんでしょう?withCriticalRegion内部でawaitできないのでデータ競合は起きないのかなーと思ったのですが… actor A { let state = ManagedCriticalState(0) nonisolated var value: Int { state.withCriticalRegion { $0 } } func update() { state.withCriticalRegion { $0 += 1 } } } func test() async { let a = A() await a.update() print(a.value) // 1 } await test()
omochimetaru 5/25/2022 4:39 AM
考え方はあってると思いますよ。でも、それはActor以前のJavaのsynchronizedのようなインスタンス別のロックベースの並行プログラミングに戻っているだけなので
4:39 AM
そうした方式の既知の問題を踏んでいく事になると思います
4:41 AM
フィールドの読み書きをアトミックにして並行アクセスに安全なオブジェクトを作る事ができるけど
4:41 AM
現実的には「タイミングを待つ」方式が必要になって、
4:42 AM
そこで、 1. 同期の内側でコールバック関数を呼ぶ 2. 同期の外側で非同期なコールバックを投げる のどちらかが必要になります
4:42 AM
1を選択すると、そのコールバック内でさらにそのオブジェクトを触ろうとしたときにデッドロックするという問題が生じます
4:43 AM
リカーシブロックで問題を緩和する事ができますが(Javaはこれ)、結局2つ以上のアクセスフローが絡まったパターンでデッドロックします
4:45 AM
2を選択すると、結局そこで非同期な待機ポイントが挟まるので、 状態遷移の途中状態にアクセス可能なタイミングが生まれて考慮が難しくなります。
4:46 AM
1はいざエンバグした場合にデバッグするのが難しいので
4:46 AM
常に2になるように強制する事でシンプルにするのと、 メッセージボックスによる直列化でフィールド自体のロックのオーバーヘッドを回避したのが、 SwiftのActorです
4:48 AM
クリティカルセクションの問題は、保護する範囲が拡大していってデッドロックしうるパターンに踏み込むのが一番怖い部分なので、
4:49 AM
適切に使う分には、「syncで状態を読めるActor」でバランスを取れると思います。