Actor의 등장배경
Actor은 동시성 프로그래밍(concurrency programming)에서 안전한 스레드 관리를 위해서 발생되었다.
Concurrency programming의 가장큰 문제중 하나는 data race이다.
바로 여러스레드가 동시에 하나의 데이터에 접근하게 되는 경우이다.
하지만 이러한 문제를 Structed Concurrency에서도 data race를 방지하기 위해 컴파일러 단위부터 검사해주고 있으며, 그걸 가능하게 하는 것이 바로 Actor다.
class Human {}
을
actor Human {}
으로 사용하면 된다.
즉
actor Human {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
data race를 피하는 방법
data race가 발생하는 경우는, 데이터에 두개 이상의 개별 스레드가 동시에 접근하고 그중 하나 이상이 write를 통해서 데이터를 변경할때 발생할수 있다.
- 데이터가 shared mutable state가 아닌 경우
즉 데이터가 변경되지 않거나, 공유되지 않는경우에는 data race가 발생하지 않는다.
따라서 let의 경우에는 immutable한 데이터값이기 때문에 data race가 발생하지 않는다.
또한 struct의 경우에는 데이터를 변경해줄수 없는데, 이런 경우는 function에 mutating이라는 키워드를 통해서 프로퍼티를 변경해줄수 있다.
struct Counter {
var count: Int = 0
mutating func increment() {
self.count += 1
}
}
하지만 또한, 해당 struct의 인스턴스를 let으로 만들면, 에러가 발생함. (당연하다)
즉 이렇게 immuatable하도록 짜면서도, 내부에서는 값을 변경할수 있도록 할때, 이것을 보장해주는 것이 바로 Actor이다.
Actor
Actor는 위의 shared mutable state에 대한 동기화 메커니즘이다.
Actor는 자신만의 state를 가지며, 이를 접근하기 위해서는 Actor를 무조건 적으로 거쳐야 한다. → 유일한 값에 대한 접근방법
이를 운영체제에서 Actor를 사용하게 되면, Actor의 상태값에 동시에 접근하지 않는것을 보장하게 된다.
Actor의 특징
- Swift의 새로운 타입이다.
- class와 동일한 참조타입
- 프로토콜 준수, extension 가능
- 상속을 지원하지 않음
- Actor는 클래스와 다르게 한번에 하나의 작업만 mutable State에 접근
Actor Isolation
Actor의 mutable state를 보호하는 방법이다.
Actor의 mutable state에 동시에 접근하지 못하도록 보장하는 방법이다.
Actor의
- stored, computed instance properties
- instance methods
- instance subscripts
는 반듯이, 해당 Actor의 self.를 통해서 접근해야한다.
즉 Actor의 가장 큰 대원칙이 모든 Actor의 프로퍼티는 self.를 통해서만 접근이 가능하다는 점이다.
이를 통해서 외부에서 해당 actor의 mutable한 값을 변경해주는것을 통제한다.
예를들어
func transfer(amount: Double, to other: BankAccount) throws {
if amount > self.balance {
throw BankError.insufficientFunds
}
print("Transferring \(amount) from \(accountNumber) to \(other.accountNumber)")
self.balance = balance - amount
other.balance = other.balance + amount 🚨
}
이 코드에서 other.balance에서 에서 balance값을 self.으로 접근하지 않았기 때문에 에러가 발생한다. 즉 참조만 해도 이렇게 에러가 발생한다는 것.
Cross-actor reference
그렇다면 외부에서는 acto의 값을 참조하지 못하는가???????
결론을 이야기 하자면 아니다.
해당 값이 immutable하다면 외부에서 참조가 가능하다.
외부에서의 actor 프로퍼티 참조는 두가지 경우로 나뉜다.
- 동일한 모듈에서 참조할 경우
- 다른 외부 모듈에서 참조할 경우
동일한 모듈에서 참조할 경우
public actor BankAccount {
public let accountNumber: Int
public var balance: Double
public init(accountNumber: Int, initialDeposit: Double) {
self.accountNumber = accountNumber
self.balance = initialDeposit
}
}
// Same module
public class MyStaticLibrary {
public init() {
let account = BankAccount(accountNumber: 1, initialDeposit: 10)
print(account.accountNumber) ✅
}
}
그냥 참조하면 된다.
다른 모듈에서 참조할 경우
위에서 actor의 특징중 가장 마지막 부분이었던, 다르게 한번에 하나의 작업만 mutable State에 접근때문에
actor의 methods는 동기적으로 작동한다.
따라서 actor에 요청이 들어온 경우에는 잠시 대기 했다가, 해당 요청이 가능할때 작동된다. 즉, 코드에 동시성이 없어지는것이다.
이 때문에 외부 모듈에서 해당 값을 참조할 경우에는 비동기 호출로 참조해야한다.
func deposit(amount: Double) async {
assert(amount >= 0)
balance = balance + amount
}
func transfer(amount: Double, to other: BankAccount) async throws {
if amount > self.balance {
throw BankError.insufficientFunds
}
print("Transferring \(amount) from \(accountNumber) to \(other.accountNumber)")
self.balance = balance - amount
await other.deposit(amount: amount) ✅
}
Uploaded by N2T