스위프트 동시성 로드맵에 대하여
About Swift Concurrency Roadmap

Jung Kim
9 min readNov 8, 2020

--

지난 2주동안 스위프트 포럼에서 가장 뜨거운 것은 바로 스위프트 동시성 로드맵이었습니다.

자연스럽게 애플 코어팀에서 주도하고 있으며, 크리스 레트너가 거의 3년전에 작성한 선언문(manifesto)보다 작은 범위에 집중하고 있습니다.
코어 내부에서 꽤 구체적으로 구현 방향을 잡고 시작했다는 의미입니다. 당장은 방향성에 대한 문서와 테스트 코드부터 PR이 올라왔을 뿐입니다.
컨트리뷰터들이 댓글로 다양한 관점에서 꽤 깊이있는 제안을 주고 받고 있습니다.

이번 로드맵의 목표는 다음과 같습니다.

  • 비동기 프로그래밍 표현을 사용할 때 쉽고 편리하게 하며
  • 스위프트 개발자가 활용할 수 있도록 언어 수준에서 도구와 기술을 제공하고
  • 컴파일 시점에 비동기 코드의 성능 향상을 도모할 수 있고
  • 불안정한 메모리 관리 방식처럼 데이터 레이스와 데드락을 제거하기

위의 작업은 두 단계로 진행될꺼라고 합니다. 우선 1단계에서는 async 문법과 actor 타입을 지원하도록 구현해서 코드를 개선할 수 있도록 합니다. 그 이후 2단계에서 데이터 레이싱 문제를 해결하기 위해서 actor 분리(isolation) 등 효율성을 개선한다고 하네요.
로드맵 문서상에는 for 반복문에서 병렬 처리처럼 2단계에서 해야할 것은 구체적으로 설명하지 않고 열어놓았습니다. 그래서 댓글에서 논의를 계속하다가 크리스 레트너가 지난 주말에 Protocol-based Actor Isolation 에 대한 제안 글을 올렸습니다.
https://docs.google.com/document/d/1OMHZKWq2dego5mXQtWt1fm-yMca2qeOdCl8YlBG1uwg/edit#
이 부분은 추가적으로 논의가 될 것으로 보입니다.

기본 문법 아이디어

비동기로 동작하는 다음과 같은 함수를 예제 코드로 살펴보죠.

예제 코드에 refreshQueue가 포함된 것처럼, 이번 논의에서 제안한 동시성의 상당 부분은 GCD(libDispatch 오픈소스)와 관련이 있습니다. 직간접적으로 동시성 코드가 DispatchQueue를 활용한다고 가정하고 있습니다.
여기서 refreshPlayers() 함수는 self.players에 값을 넣기 위한 refreshQueue에 async로 넘긴 비동기 함수이며, 끝난 시점을 알기 위해서 completion 핸들러를 호출합니다.

이런 비동기 함수를 앞으로는 이렇게 쓰고 싶다는 겁니다. 반대로 아래처럼 작성하면 위와 같은 코드로 동작하게 된다는 겁니다.

  • refreshPlayers() 함수는 async 함수 타입이 됩니다.
  • allPlayers() 함수도 async 함수 타입이며 completion 핸들러를 호출하는 대신에 값을 리턴합니다.
  • allPlayers() 앞에 await가 명시되어 있어서 완료될 때까지 멈춘다(suspended)는 의미가 됩니다.
  • await는 try처럼 표현식 앞에 한 번만 나타나고 멈출 수 있다는 의미입니다.
  • 클래스 속성에 접근할 때 self. 을 생략할 수 있습니다.
  • allPlayers와 players가 데이터 레이스가 생기지 않습니다.

여기에 덧붙여서 class PlayerRefreshController가 private queue를 가지면서 내부 속성을 해당 큐에서만 접근하도록 하기 위해서 actor 클래스를 제안합니다.

  • 이렇게 actor를 지정하면 클래스 내부에 private queue를 선언해서 모든 내부 상태를 접근할 때 내부 큐를 사용한다고 가정합니다.
  • 메소드 호출하면 큐에서 동작하도록 컴파일러가 동기화 작업을 관리하기 때문에 상태 관리를 빼먹는 불상사(?)를 막아줍니다.
  • 컴파일러가 동기화 최적화를 관리하면 다른 actor가 비동기 함수를 호출하는 것까지 고민하지 않아도 됩니다.

DispatchQueue.main에서 UI를 처리해야 하는 경우는 actor 중에서 특별한 UIActor가 처리하도록 지정할 수도 있습니다.

이렇게 선언하면 컴파일러가 확인해서, 이 클래스를 참조해서 호출하는 경우는 글로벌 UI Actor로 메인 스레드에서 동작하게 됩니다.

기본 동작에 대한 아이디어는 간단한 것 같지만 구현까지는 고려해야 할 사항이 많이 남아있습니다.

1. 위에 설명한 것처럼 coroutine 코루틴 개념 중에 async/await 방식을 제안합니다.

2. Structured Concurrency 원칙을 기반으로 Task를 표준 라이브러리에 구현하자고 제안합니다.

모든 비동기 함수들은 비동기 Task 일부로 동작하고, 필요에 따라서 하위에 child task를 만들어서 동시에 처리하는 개념입니다.
이 부분에서 child task를 지정하기 위해서 async let 문법이 등장합니다.

Structured Concurrency 부분은 꽤 세부적인 내용이 포함되어 있어서 별도 제안과 논의로 이어지고 있습니다.

3. Actor와 Actor Isolation 부분은 매우 중요한 개념인데, 앞서 설명한 것처럼 우선 actor 클래스에 대한 개념부터 구현하고 다음 단계에서 Isolation으로 들어갈 것 같습니다.

제안 내용에는 동시 접근으로 데이터 레이스가 발생할 수 있는 코드가 안전한지 안한지 판단해야 하는 경우를 포함하고 있습니다. 아직 context가 바뀌어야 하는 경우나 Immutable/Mutable 타입과 섞어 쓰는 경우 등 논의할 게 많이 남아있고
크리스 래트너가 제안하는 내용들과 합쳐져서 더 개선할 여지가 있는 것 같습니다.

다른 언어에서 actor 모델을 구현한 사례에 대한 리서치와 비교도 함께 하고 있습니다.

> 1단계 Actor 예시

> 2단계 Full Actor Isolation 예시
2단계에서는 전역 변수 뿐만 아니라 클래스에 변수에 대해서도 immutable하게 접근하는 것처럼 안정성을 보장해주는 것을 목표로 합니다.

4. Objective-C 동시성 방식과 상호 호환성도 브릿지가 가능하도록 고려하고 있습니다.

async 함수가 Objective-C 비동기 함수로 어떻게 해석되는지 설명하고 있습니다.
개인적으로는 이렇게까지 하고 있다는 게 놀랍기도 하고, 양쪽 API를 호환하도록 만들고 있는 애플 입장에서는 당연한 것 같습니다.

이 문서에는 다음과 같은 내용들을 포함하고 있습니다.

  • Objective-C에서 만든 completion 핸들러 메소드가 포함된 비동기 메소드가 스위프트 async 메소드로 번역되는 방법
  • 스위프트에서 선언한 async 메소드에 @objc 를 붙여서 export하는 방법
  • 서로 언어끼리 호환성을 유지하기 위한 방법

이와 같은 Objective-C 함수는 다음과 같은 스위프트 함수로 번역할 수 있습니다.

그리고 앞으로는 다음과 같이 async 함수로 이해할 수 있습니다.

그러면서 다음 버전의 애플 스위프트 라이브러리 중에서 completionHandler를 포함하는 비동기 함수들을 async 함수로 바꾸는 작업을 시작했습니다.
내년부터는 애플 문서에서 async가 붙은 함수/메소드들을 더 많이 보게 되겠네요.

5. 마지막으로 UITableViewDelegate 같은 프로토콜을 채택해서 Notificaion으로 호출되는 메소드들도 비동기 핸들러로 처리하는 Async handlers도 적극 도입할 것으로 보입니다.

여기까지 정리했는데도 새로운 용어과 개념들, 공부할 내용이 무척 많습니다. 아직 모두 구현되지 않았지만 빠르게 진도가 나갈 것 같습니다. 많은 개발자들이 고민해볼 사항을 댓글로 남기면 코어 팀에서 적극적 답변하면서 왜 그런 결정을 했는지 논의하는 모습이 긍정적인 것 같습니다.

댓글을 읽다보면 async를 func 앞에다 붙여달라고 하는 것을 왜 throws와 같은 위치에 넣었는 지 설명하는 것은 애교 수준입니다 ㅎㅎ

--

--