- Xcode 14에서 Swift Concurrency를 시각적으로 디버깅하기 위한 툴이 추가 되었다.
- 현재 실행중인 Task 수, 현재 존재하는 Task 수, 지금까지 만들어진 Task수 등을 파악할 수 있다.
- 현재 Task 구조, 전체 Task의 리소스 소모량을 알 수 있다.
- 의심이 가는 Task는 좀 더 심도 있게 볼 수 있다. → Stack Trace 등
- Swift Concurrency Optimization
- 동시성을 다루면서 주로 마주하는 문제들
- Main actor blocking: 메인액터에서 특정 태스크가 지나치게 오래 돌아가는 현상
- Main Actor는 모든 작업을 메인 스레드에서 돌리도록 보장하는 특수한 Actor
- UI 처리는 모두 여기서만 일어나야 하기 때문에, 특정 작업이 너무 오래 잡고 있지 않고 언제나 가용한 상태여야만 한다. → 안 그러면 앱이 응답없는 상태가 된다.
- 메인스레드에서 접근하지 않아도 되는 데이터와 메소드를 별도 액터로 분리해야 한다.
- actor contention
- actor는 상호 배제적으로 접근되어야 하기 때문에, actor에는 꼭 필요할 때만 접근해야 성능이 좋아진다.(critical section을 축소해야 한다)
- nonisolated로 해놓고, actor 접근이 필요한 부분에서만 내부에서 await를 호출하는 것도 도움이 된다.
- 이 때 내부에서 Task를 만드는 경우는 Task.detached로 만들어야 Actor Context를 이어받지 않아서 진정으로 동시에 돌아갈 수 있다. → 이 경우는 명시적으로 self 캡쳐를 해줘야 한다.
- Thread pool exhaustion
- Swift 동시성은 실행중에 계속해서 진행이 되어야 한다는 제약이 있다.(make forward progress) → 뭔가를 기다려야 한다면, suspend 상태가 되어야한다.
- 그런데 blocking 호출(blocking 파일 호출 및 network I/O) 및 lock을 걸게 되면 이 제약이 깨지게 된다.
- 이 경우 Task는 스레드를 계속 차지하지만 CPU 코어를 쓰지는 않는 상태가 된다.
- 스위프트 동시성은 이 경우 스레드를 새로 만들지 않기 때문에, 코어 일부를 사용할 수 없는 상태가 된다.
- 극단적으로 모든 스레드가 block 상태가 되면, 동시성 런타임이 데드락에 빠지게 된다.
- 그래서 동시성 API에서는 blocking 호출을 피해야 한다.
- 파일 및 네트워크 I/O는 원래의 비동기 API를 사용해야 한다.
- condition 및 semaphore를 피하라.
- lock도 필요하면 잠깐은 걸 수 있지만, 장기적으로 잡지 않도록 한다.
- 정 필요하면 이런 코드는 동시성 바깥에서 수행하라.
- DispatchQueue 같은 것을 쓰면 된다.
- 필요하면 continuation으로 동시성과 연결한다.
- Continuation Misuse
- 원래의 비동기 API를 swift 동시성과 연결하기 위한 도구
- 만들면 현재 Task가 suspend되고, 이를 resume 할 수 있는 callback을 제공한다.
- 이 떄 콜백은 반드시 1번 호출되어야 한다. 0번도 안되고 2번 이상도 안된다.
- 이를 언어에서 잡아줄 수 없기 때문에, 특히 주의해야한다.
- 두번 이상 호출되면 크래시 혹은 오동작하고, 호출이 안되면 resume이 안되면서 Task가 누수된다.
- 성능이 정말 중요하지 않다면 checkedContinuation을 권장한다.
- 그 외 Instrument 템플릿에서 지원하는 기능들
- structured concurrency 시각화
- Task 생성시의 호출 트리
- 어셈블리 레벨 디버깅