본문 바로가기

iOS

[동시성 프로그래밍] GCD - 비동기 VS 동기 / 직렬 VS 동시

728x90

비동기 VS 동기

  • 메인 쓰레드가 큐에 작업을 보내고 나서 어떻게 작업을 처리할게 될까?
    • 신경 끄고 자기에게 쌓여 있는 다음 일을 한다.
    • 보낸 작업이 끝날 때까지 기다린 후에 다음 일을 한다.
  • 이 두가지 반응이 비동기와 동기의 개념이다.
    • 자기에게 쌓여 있는 다음 일을 실행한다 = 다음 코드 라인을 실행한다.

 

비동기 (Async)

  • 메인 쓰레드가 Queue에 일부 작업을 보내고 난 뒤, 보낸 작업에 대해 신경을 끄고 다음 작업을 실행하는 것을 의미한다.
  • Queue에 보낸 작업에 대해서는 더 이상 신경을 쓰지 않고 다음 일을 하는 것이다.
    • 보낸 작업에 걸렸을 시간만큼 다른 일을 할 수 있다.
DispatchQueue.global().async {
  //task
}
  • DispatchQueue: iOS에서 동시성 프로그래밍을 돕기 위해 제공하는 queue
  • global: DispatchQueue의 종류
  • async: 비동기

→ global dispatch queue에 비동기로 Task를 보낸다.

  • 비동기로 보낸다?
    • 원래의 작업이 진행되고 있던 메인 쓰레드에서 global dispatch queue로 task를 보낸 후, 해당 작업이 끝나기를 기다리지 않고 이어서 할 일을 하는 것을 말한다.
  • 메인 쓰레드에서 실행하는데 걸리는 시간?
    • 얼마인지 정확하게 알 수는 없어도 1+2+3인 6초보다는 적게 걸린다는 것을 알 수 있다.
    • 왜냐하면, 메인 쓰레드에서는 작업을 queue에 넘기고 나면 더 이상 해당 작업에 대해서는 신경을 쓰지 않고 남아 있는 자기 할 일을 하기 때문이다.
  • 일을 Queue에 보내기만 하고 신경을 쓰지 않는다면, 실제 해당 작업이 언제 끝나는지 어떻게 아는가?
    • Swift에서는 클로저를 통해서 해당 시점을 알려준다.
    • completionHandler, completion이라고 부른다.

 

동기 (Sync)

  • 어떤 작업을 queue에 보낸 뒤 신경 끄고 다음 라인을 실행하는 async와 다르게 queue에 작업을 보낸 작업이 완료 될 때까지 기다린 후에 다음 라인을 실행한다.
  • 큐로 보낸 작업이 끝난 후에야 다음 task를 실행하기 때문에 시간 절약이 되지 않는다.
DispatchQueue.global().sync {
  //task
}
  • 위의 코드를 해석하자면,
    • 원래의 작업이 진행되고 있던 메인 쓰레드에서
    • global dispatch queue로 task를 보낸 후,
    • 다음 라인 실행을 위해 해당 작업이 끝나기를 기다리는 것을 의미한다.
  • 메인 쓰레드에서 걸리는 시간?
    • 6초보다는 좀 더 걸리겠다고 예상할 수 있다.
    • 메인 쓰레드에서는 작업을 queue에 넘긴 후에 해당 작업이 끝나고 나서야 다시 자기 할 일을 하기 때문이다.
  • 그렇기 때문에 동기적으로 보내는 코드를 짜게 되면, 실질적으로 메인 쓰레드에서 일을 하게 된다.

 

 

왜 비동기 처리가 필요한가?

  • 비동기로 처리했을 때 좋은 점은 시간 절약이다.
  • 시간이 많이 드는 작업의 대부분은 서버 통신이기 때문에, 네트워크와 관련된 작업들은 내부적으로 비동기적으로 구현이 되어 있다.

 


큐의 특성 (직렬 VS 동시)

  • queue.async { task } 또는 queue.sync { task } 를 통해 task를 queue로 보낸다.
  • 그러면, 큐에 쌓여 있는 task들을 다른 쓰레드로 보내야 한다.
  • 이 때, GCD/Operation은 다음 두가지 방법으로 보낼 수 있다.
    1. 한 개의 쓰레드에 몰아 넣는다.
    2. 여러 개의 쓰레드에 나눠서 넣는다.

→ 둘 중 어떤 방식을 선택할건지가 바로 큐의 특성에 따라서 결정이 된다.

 

직렬 (Serial)

  • (보통 메인 쓰레드에서) 분산 처리 시킨 작업을 다른 한 개의 쓰레드에서 처리하는 큐

 

동시 (Concurrent)

  • (보통 메인 쓰레드에서) 분산 처리 시킨 작업을 다른 여러 개의 쓰레드에서 처리하는 큐
  • 몇개의 쓰레드로 분산할 지는 시스템이 알아서 결정한다.

 

어떤 큐를 사용할 것인가?

  • 언제 직렬 큐를 사용하고 언제 동시 큐를 사용할까? 분산처리하는거라면 동시 큐가 좋지 않을까? 라고 생각할 수 있다.
  • 어떤 큐를 사용할 것인지에 대한 핵심 포인트는 작업 순서의 중요도에 있다.
  • 직렬 큐에 담긴 작업들은 오직 하나의 쓰레드에만 분배된다.
    • 모든 작업들이 그 전 작업이 끝나길 기다렸다가 하나씩 실행되기 때문에 task의 시작과 종료에 대한 순서 예측이 가능하다.
  • 동시 큐에 담긴 작업들은 여러 개의 쓰레드로 분배된다.
    • 선입 선출이라는 큐의 특성상 작업들이 들어온 순서대로 분배되어서 실행은 되지만, 끝나는 순서는 알 수 없다.
  • 예를 들어 각 셀에서 이미지를 로드해야 한다고 할 때, 어떤 데이터가 먼저 들어와야 할 지 순서는 중요하지 않다. 그저 빠르면 좋은 것이다. 이런 상황에서는 동시 큐를 사용해야 한다.
  • 하지만 반대로 순서가 중요한 작업들을 처리해야 한다면 늦게 들어온 것이 먼저 끝나는 상황을 방지하기 위해 직렬 큐를 써야 한다.

 

async vs sync

작업을 보내는 시점에서 기다릴지 말지에 대해 다루는 것 것

 

concurrent vs serial

Queue(대기열)로 보내진 작업들을 여러 개의 쓰레드로 보낼 것인지 한 개의 쓰레드로 보낼 것인지에 대해 다루는 것 

 

  • SerialQueue.sync :
    • 메인 스레드의 작업 흐름이 queue에 넘긴 태스크가 끝날때까지 멈춰있고(sync), 넘겨진 task는 queue에 먼저 담겨있던 작업들과 같은 스레드에 보내지기 때문에 해당 작업들이 모두 끝나야 실행 (Serial Queue)
  • ConcurrentQueue.sync :
    • 메인 스레드의 작업 흐름이 queue에 넘긴 태스크가 끝날때까지 멈춰있고(sync), 넘겨진 task는 queue에 먼저 담겨있던 작업들과 다른 스레드에 보내질 수 있기 때문에 해당 작업들이 모두 끝나지 않아도 실행 (Concurrent Queue)
  • SerialQueue.async :
    • 메인 스레드의 작업 흐름이 태스크를 queue에 넘기자마자 반환되고 (async), 넘겨진 task는 queue에 먼저 담겨있던 작업들과 같은 스레드에 보내지기 때문에 해당 작업들이 모두 끝나야 실행 (Serial Queue)
  • ConcurrentQueue.async :
    • 메인 스레드의 작업 흐름이 태스크를 queue에 넘기자마자 반환되고 (async), 넘겨진 task는 queue에 먼저 담겨있던 작업들과 다른 스레드에 보내질 수 있기 때문에 해당 작업들이 모두 끝나지 않아도 실행 (Concurrent Queue)