thread와 dispatch queue와의 관계

thread와 dispatch queue와의 관계

test case를 작성하던 중 수행하는 코드가 main thread에서 동작하는지 확인해야하는 상황이 있었습니다. 그 상황을 만들기 위해서 global queue에서 main queue를 동작시켜 해당 코드가 main thread인지를 확인하는 코드를 작성했습니다.
그런데 그 test case는 통과하지 못하고 계속 실패했습니다. global queue가 동작한 thread는 background thread였는데 그 곳에서 dispatch_sync로 main queue를 동작시키면 main thread가 아닌 background thread에서 main queue를 동작시켜서 문제가 발생했습니다. main queue는 무조건 main thread에서 동작한다고 알고 있어서 dispatch로 하면 당연히 main thread라고 생각했습니다. 그런데 sync로 동작할 때는 제 예상하고 달랐습니다. test case에서 async로 동작을 시켰더니 test case를 통과했습니다. sync와 async 사이에 차이가 단순히 동기화만의 문제가 아닌 다른 차이가 있다는 생각이 들던찰라에 xcode 업데이트 후에 다시 위의 상황에서 dispatch_sync로 main_queue를 실행했더니 이번에는 main thread에서 동작했습니다. Xcode버그이거나 simulator가 오작동을 일으켰던 것 같습니다.
이번일을 겪고 나서 dispatch_queue와 thread의 관계에 대해서 알아보고 싶어서 이 글을 쓰게 되었습니다.



먼저, thread에 대해서 알아보겠습니다.

thread는 어플리케이션에서 다른 실행경로를 만드는 역할을 합니다. 프로세스에 할당된 메모리를 공유하며 여러개의 thread가 동시에 실행될 수 도 있습니다.
프로세스와 다른 점은 프로세스 끼리는 메모리를 공유하지 않지만 같은 프로세스 안에 있는 thread끼리는 메모리를 공유합니다. 한 프로세스는 여러개의 thread를 가질 수 있지만 thread는 프로세스 없이 독립적으로 실행될 수 없습니다.
enter image description here
그림A처럼 단일 Process에는 하나의 thread를 포함하고 있습니다. 그림B는 단일 Process에 여러개의 thread를 포함하고 있습니다. 하나의 process는 하나 이상의 thread로 구성되어 있다고 할 수 있습니다.


Apple platform에서 병렬 프로그래밍 방식으로 2가지를 들 수 있습니다.

  1. 전통적인 방식
    전통적인 방식의 프로그래밍에서는 병렬 동작하기 위해서 thread를 여러개 생성합니다. 사용자가 전적으로 thread를 관리해야하기 때문에 cpu의 코어 개수에 맞춰서 잘 동작하도록 개발하는게 어렵고 thread간 상호 교류가 쉽지 않습니다.

  2. 비동기 디자인 접근
    Apple에서는 비동기 디자인 접근으로 해결책을 제시합니다.
    비동기 방식은 원하는 작업을 background에서 시작하고 작업이 완료되면 callback(또는 notification) 함수에 알려주는 방식입니다. 과거의 방식으로 한다면 비동기 함수를 만들고 비동기함수가 동작할 thread를 생성한 후 그 함수를 실행시켜야합니다. 그러나 apple에서는 이런 동작을 쉽게 할 수 있도록 기능을 제공하고 있습니다.

    • 그 중에 하나가 GCD(Grand Central Dispatch)입니다.
      이 기술은 thread 관리를 포함하고 있으며 사용자는 thread 관리에 대해서는 GCD에 맞기고 자신이 실행시키고자 하는 task(code)에만 집중할 수 있습니다. 실행시키고자 하는 code를 GCD에 전달하면 필요한 thread 및 스케줄링을 GCD가 알아서 처리합니다.
      GCD가 시스템 레벨단의 thread관리 작업을 포괄적으로 제공하기 때문에 전통적인 thread 방식보다 효율적으로 개발이 가능합니다.

    • operation queue는 dispatch queue와 매우 비슷합니다. 사용자가 정의한 작업을 operation queue에만 추가하면 비동기로 동작합니다. GCD처럼 operation queue도 thread의 모든 것을 관리합니다. 이번 post에서는 operation queue에 대해서는 자세히 다루지 않겠습니다.



Dispatch Queue

dispatch queue는 code를 실행하기 위한 c기반 메카니즘으로 되어 있으며 GCD와 C runtime의 일부분입니다. dispatch queue는 단일 또는 병행 실행이 가능하며 언제난 first-in, first-out순서로 동작합니다. serial queue는 한 작업만 동작할 수 있으며 그 작업이 끝나야 다른 작업이 실행될 수 있습니다.
병렬 queue는 여러 작업이 동시에 동작할 수 있으며 그렇기 때문에 다른 작업이 이미 시작된 작업이 끝날때까지 기다리는 시간없이 바로 시작할 수 있습니다.

distpach queue는 3가지 종류가 있습니다.

구분 내용
serial 한 타임에 단일 작업만 수행하는 queue. 한 작업이 마무리된 후에 다른 작업이 시작됨.
Concurrent 한 타임에 여러 작업을 수행할 수 있는 queue. 한 작업이 마무리 되지 않아도 다른 작업이 시작할 수 있음.
main queue serial queue의 한 종류로 application의 main thread에서 동작. UI 작업 등을 수행할 때 사용.
  • dispatch queue의 이점
    • 직접적이고 단순한 프로그래밍 인터페이스 제공
    • 자동적이고 포괄적인 thread pool을 관리
    • 빠른 속도의 동작
    • 효과적인 메모리 관리
    • 커널을 잡아두지 않음.
    • 비동기 동작은 deadlock을 방지(동기로 동작 시는 발생할 수 있음)
    • 단일 queue는 효과적인 lock을 제공해 줌

  • async와 sync동작 차이

    • asyn 동작은 호출하자마다 바로 return하며 block이 호출될때까지 기다리지 않습니다. gcd에 의해 backgroud thread에서 동작합니다. 다만, main queue는 main thread에서만 동작합니다.

    • sync 동작은 block이 완료될 때까지 return하지 않습니다. 같은 queue에서 호출시 deadlock이 발생할 수 있습니다.
      async 동작과 다르게 queue안에서 retain을 하지 않습니다.(동기로 작동하기 때문에 reference를 빌려오기만 합니다.)
      block안에서 block copy도 하지 않습니다. 성능 최적화를 위해서 가능하면 현재 thread에서 실행되도록 구현되어 있습니다. 특별히 main queue는 언제나 main thread에서만 동작합니다.
      예) main thread에서 global queue를 sync로 호출 시 main thread에서 동작합니다. background thread에서 main queue를 sync로 호출하면 main thread에서 동작합니다.

  • 동기와 비동기 동작을 고려했을 때thread와 dispatch_queue의 관계
    isMainThread가 true일 때 'queue가 mainQueue이다’라는 것을 보장하진 않습니다. 반대로 queue가 main queue일 때는 thead가 main thread임을 보장할 수 있습니다.
    다시, 정리하면 main queue는 항상 main thread에서만 동작을하며 main thread에서는 main queue뿐만이 아니라 다른 queue도 dispatch_sync로 호출 시 동작할 수 있습니다.



마지막으로 dispatch queue를 안전하게 사용하는 방법을 정리하겠습니다.

  • dispach queue 자체는 thread safe합니다. 다른 말로 하면 어떤 쓰레드에서든 dispach queue에 작업을 추가해도 동기화에 문제가 없습니다.
  • 같은 queue에서 dispatch_sync를 호출하지 말아야합니다. 같은 queue에 호출할 경우 deadlock이 발생할 수 있습니다.
  • dispach queue에 전달하기 위해서 lock을 사용하지 말아야합니다. lock을 사용하는게 안전하지만 serial dispatch queue 전체가 block될 위험이 있습니다. 대신 serial dispach queue를 lock대신에 사용합시다.
  • thread를 직접 구현해서 사용하는 것보다 GCD를 사용합시다.


참고

댓글

이 블로그의 인기 게시물

dismiss에 대해서 알아봅시다

1. Framework: framework란?

closure에서는 왜 self를 사용해야 할까?