
여러 쓰레드가 동시에 변수에 접근하면 기대와 다른 결과가 나올 수 있어요.
class Counter {
var count = 0
func increment() { count += 1 }
}
let c = Counter()
DispatchQueue.concurrentPerform(iterations: 1000) { _ in
c.increment()
}
print(c.count) // 기대: 1000, 실제: 600~900처럼 작아짐
value += 1은 사실 load, increment, store 3단계로 구성되어 있습니다.
그래서 여러 스레드가 동시에 접근하면:
1. 스레드 A가 value를 읽음 (0)
2. 스레드 B도 읽음 (0)
3. A가 1을 더해 1을 쓰고
4. B도 1을 더해 1을 쓰면 -> 결국 value = 1
-> 즉, 증가 작업 2번이 하나로 처리되어버림.
이렇게 순서와 충돌 떄문에 값이 틀리게 나옵니다.
이를 방지하기 위해 락(lock) 또는 Swift Actor를 사용합니다.
class SafeCounter {
private var value = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
func getValue() -> Int {
lock.lock()
defer { lock.unlock() }
return value
}
}
actor CounterActor {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
return value
}
}
let actor = CounterActor()
Task {
await withTaskGroup(of: Void.self) { group in
for _ in 0..<1_000 {
group.addTask { await actor.increment() }
}
}
let final = await actor.getValue()
print("🔢 final actor value: \(final)") // 1000 → 안전하게 잘 증가됨
}
actor SafeCounter {
private var count = 0
func increment() { count += 1 }
func get() -> Int { count }
}
let a = SafeCounter()
Task {
await withTaskGroup(of: Void.self) { group in
for _ in 0..<1000 {
group.addTask { await a.increment() }
}
}
print(await a.get()) // 결과: 항상 1000
}
struct MyData: Sendable {
let name: String
}
@MainActor
class ViewModel: ObservableObject {
@Published var text: String = "Hello"
}
@MainActor
class MyViewModel {
let name: String
nonisolated init(name: String = "") {
self.name = name
}
func update() { /* 메인 스레드에서 실행됨 */ }
}
| 개념 | 설명 |
|---|---|
| race condition | 여러 스레드가 동시에 같은 데이터를 읽고 쓰며 충돌이 일어나는 상황 |
| actor | Swift에서 race condition을 막기 위한 타입. 내부 상태를 직렬 접근하도록 보장 |
| isolation | actor 내부는 한 번에 하나의 Task만 상태에 접근. 이를 통해 동시성 안전을 확보 |
| Sendable | 타입이 다른 Task/쓰레드로 안전하게 전달될 수 있는지 컴파일러가 확인 |
| @MainActor | UI 업데이트처럼 반드시 메인 스레드에서 실행되어야 하는 코드에 적용되는 전역 actor 설명1 설명2 |
| nonisolated | @MainActor 타입의 특정 함수(예: init)에만 격리 예외를 허용해, 격리되지 않은(동기) 컨텍스트에서도 안전하게 호출할 수 있게 해줌 |
init과 같은 초기화 함수는 격리 예외가 필요한데, 이때 nonisolated가 바로 그 고민을 깔끔하게 해결해 줍니다.이 요약만 잘 기억해도, Swift 동시성 모델의 핵심을 잡고 꼼꼼한 코드 구조 설계가 가능해집니다!