Grand Central Dispatch Queues는 thread들을 관리해줍니다. 시스템은 몇 가지 global queues를 제공합니다. main queue는 UI 업데이트에 자주 사용되는 직렬 스레드이며 하나만 존재합니다.

DispatchQueue.main
DispatchQueue.global(qos: .userInteractive)
DispatchQueue.global(qos: .userInitiated)
DispatchQueue.global(qos: .utility)
DispatchQueue.global(qos: .background)
DispatchQueue.global() // .default qos
DispatchQueue.global(qos: .unspecified)

하지만 다른 global queue들은 concurrent하여 하나 이상의 스레드를 가질 수 있습니다. 각 queue는 Quality of Service (QoS)라는 다른 우선순위 수준을 가지고 실행됩니다. Global queue에 dispatch할 때 작업은 목적을 가질 수 있으며 스케줄러로 하여금 스레드별로 실행의 우선순위를 결정할 수 있게끔 할 수 있습니다. concurrent global queue를 타입 메서드인 DispatchQueue.global의 Quality of Service 열거형 케이스를 통해 우선순위를 알려줄 수 있습니다.

  • userInteractive는 아주 드물게 사용되며 사용자가 드래그하거나 핀칭하는 등 실질적으로 main queue만큼 빠르게 처리되어야 할 때 사용합니다. main queue에서 실행되지는 않지만 터치 제스처할 동안 main queue에 바로 반환되어야하는 경우입니다.
  • userInitiated는 버튼을 탭하는 등 사용자가 UI를 통해 작업을 시작시킨 경우에 사용할 수 있습니다. 사용자는 저장된 문서를 여는 것과 같이 즉시 작업이 수행되기를 원하는 경우를 예로 들 수 있습니다.
  • utility는 연산, 입출력, 네트워킹과 같이 progress indicator를 나타냄과 동시에 수행되는 긴 작업을 수행하는데 사용할 수 있습니다. 시스템은 반응성과 성능, 에너지 효율성 간의 균형을 잡으면서 작업을 수행하게 됩니다.
  • background는 사용자가 직접적으로 알지 못하는 경우에 사용할 수 있습니다. 사용자와의 상호작용을 필요로하지 않고 수행 시간에 민감하지 않으며, prefetching, database maintenenace, 원격 서비스 또는 백업과 동기화하기와 같은 네트워킹 작업을 일부 포함할 수 있습니다. 시스템은 에너지 효율성을 목표로하며 작업을 수행합니다.
  • QoS 값을 지정하지 않으면 기본(default) queue에 할당됩니다.
  • unspecified는 레거시 API들과 같이 qos를 통해 스레드를 선택할 수 있는 경우에 사용합니다.

아래와 같이 레이블을 붙여 private queue를 만들어 사용하는 것도 가능합니다.

DispatchQueue(label: "com.ryan-son.serial")
DispatchQueue(label: "com.ryan-son.worker", qos: .utility, attributes: .concurrent)

private queue는 손쉽게 공유되는 리소스를 보호할 수 있으므로 기본적으로 serial인데, concurrent 속성을 이용해 concurrent queue를 만들 수도 있습니다.
QoS를 지정하지 않으면 시스템은 작업이 생성되거나 dispatch될 때의 맥락을 토대로 qos를 추론합니다. main queue에서 dispatch 되었다면 user initiated의 qos를 적용합니다.
위의 코드 예시와 같이 private queue에 utility라는 qos를 부여하여도 시스템은 더 높은 qos를 추가할 경우 이를 상승시킵니다. 예를 들어, main queue로부터 utility queue로 작업을 할당한 경우에 qos는 user initiated 수준으로 상승합니다. 미래에 작업이 발생하여도 qos는 상승된 상태입니다.

아래와 같이 UI와 관련되지 않은 작업을 serial queue에서 작업하면 어떤 경우에도 thread safe함을 보장할 수 있지만 이전에 할당된 작업을 끝낸 이후에 새로운 작업을 수행할 수 있을 것입니다.

반면에 concurrent queue에 이러한 작업을 할당한다면 concurrent queue는 즉시 별도의 스레드를 생성하여 비동기적으로 작업을 처리할 것입니다.

위와 같이 작업을 동기적 또는 비동기적으로 처리하도록 현재 queue에서 다른 queue로 dispatch할 수 있습니다. 비동기적인 방식으로 작업을 dispatch하면 현재 queue는 지속적으로 다음 작업을 수행할 수 있는 반면, serial queue에 작업을 dispatch하면 현재 queue는 dispatch된 작업이 끝날 때까지 다른 작업을 수행할 수 없습니다. 이렇게 serial queue에 dispatch하는 경우는 한 시점에 하나의 작업만이 리소스를 변경할 수 있도록 다음 작업이 동일한 리소스에 접근할 때 적용할 수 있습니다. 하지만 현재 queue가 main queue인 경우에는 아주 나쁜 사용자 경험을 보여줄 수 있겠죠. UI가 응답하지 않게 되니까요.

하지만 concurrent queue에서 동기적인 작업을 dispatch한다면 다른 스레드에서 또 다른 작업을 수행할 수 있습니다. concurrent queue에 다른 작업이 도착하면 다른 스레드를 만들거나 쉬고 있는 스레드를 이용해서 작업을 수행하는 것이죠.

주의할 점은 concurrent는 비동기(asynchronous)와 다르다는 점입니다. 프로그래머는 하나의 serial 또는 concurrent queue과 또 다른 serial 또는 concurrent queue를 이용하여 작업을 수행하게 함으로써 동기적 또는 비동기적으로 작업을 수행할 수 있습니다.

동기냐 비동기냐는 현재 queue가 작업이 완료되기까지 대기하여야 하는지에 따라 결정되며 serial과 concurrent는 단지 스레드가 하나인지 아닌지를 의미합니다. 다시 말하면 동시에 작업을 수행할 수 있는지에 따라 serial과 concurrent가 결정된다는 것입니다.

DispatchQueue가 가장 많이 사용되는 경우는 UI와 관련되지 않은 작업들이 concurrent queue를 통해 비동기적으로 처리되어 main queue에 결과를 반환하는 경우입니다.

serial queue를 통해 동기적으로 작업이 처리되는 흔한 경우로는 동일한 리소스에 대해 read가 이루어지는 경우가 있습니다. 이후 serial queue에 비동기적으로 dispatch하여 공유된 리소스의 값을 write합니다.

queue에 작업들을 추가하는 가장 빠른 방법은 async 또는 sync 메서드를 통해 클로저에 작업을 제공하는 것입니다.

Summary

  • 하나의 serial queue는 하나의 스레드만 가진다.
  • concurrent queue는 필요할 경우 여러 개의 스레드를 만들어 사용할 수 있다.
  • 작업을 현재 스레드에서 다른 스레드로 dispatch하여 비동기적으로 작업하게 하면 현재 스레드는 계속해서 작업을 수행할 수 있다. background queue에서 작업이 완료되면 결과물을 가져와 main queue에서 UI 관련 작업을 진행하면 된다.
  • main queue가 아닌 serial queue에서 비동기적으로 dispatch하는 것은 리소스에 접근해 제어하는 유용한 방법이다.
  • 원하는 QoS global queue에 접근하여 작업하게 함으로써 시스템에게 작업의 우선순위를 알려줄 수 있다.
  • private queue를 만들 수 있으며, serial, concurrent 속성을 지정할 수 있다.
  • DispatchWorkItem을 통해 간단한 dependencies를 지정하거나 작업을 취소할 수 있다.
profile
합리적인 해법 찾기를 좋아합니다.

0개의 댓글