본문 바로가기

IT/iOS

iOS Concurrency 프로그래밍, 동기/비동기 처리, GCD/Operation 에 대한 이해 (1)

1. 왜 동시성(Concurrent) 프로그래밍이 필요할까?

1) 쓰레드란 무엇인가?

  • 예시 8코어 16쓰레드
  • 우리가 사용하는 HW 제품안에는 여러개의 코어가 존재하고, 코어를 세분화하면 쓰레드로 나뉘어진다.
  • “작업 명령 → HW 작업 필요 → 코어내 쓰레드 작업 실시” 매커니즘에 준수하여 SW 작업을 처리한다.
  • 그리고 앱개발을 하다보면 쓰레드와 연관된 문제가 발생한다. 평소 우리가 앱개발을 하면서 화면이 버벅이는 현상이 발생하는데 해당 사유는 여러 일처리를 하나의 쓰레드에서 진행하다 보니 일이 제때 처리되지 않아 버벅이는 현상이다.

2) iOS 에서의 쓰레드 처리

  • 작업(Task)을 어떻게 다른 쓰레드에서 동시에 일을 하게 할 수 있을까? → 작업(Task)을 “대기행렬”에 보내기만 하면 된다. → 작업(Task)을 “큐(Queue)에 보내기만 하면 된다.
  • 큐안에 들어온 순서대로 먼저 나가게 된다. (선입선출)
  • 우리는 작업을 큐로 보내는 작업을 코드로 구현해줘야 한다. 이말은 즉, 메인 쓰레드에 있는 작업들을 큐로 보내는 작업을 코드로 구현해줘야 한다.
  • iOS 는 쓰레드를 직접적으로 관리하지 않고, “큐” 라는 개념을 이용해 작업을 분산 처리한다.
  • GCD / Operation 을 이용해 시스템에서 알아서 쓰레드 숫자를 관리한다. → 직접 쓰레드 생성이 가능하지만 일반적으로 사용하지 않는다.
  • 즉, 쓰레드 보다 더 높은 레벨/차원에서 일을 한다고 보면 된다.
  • 쉽게 다른 쓰레드에서 오래 걸리는 작업들이 “비동기적으로 동작” 하도록 만들어 준다. → 어떤 API들은 내부적으로 다른 쓰레드에서 비동기적으로 실행되도록 설계되어 있다.
  • 개략도는 아래와 같다.

2. 그럼 어떻게 큐(Queue)로 보낼까? (코드로)

  • 아래와 같은 코드를 통해서 큐에 작업을 보낼 수 있다.

  • 위와 같이 코드를 해석 방법을 아예 외워야 향후에 코드를 이해하는데 많은 도움이 된다.
  • 클로저 내부의 한 블록이 작업의 한 단위이다.

1) 큐의 종류는?

  • 큐는 크게 GCD와 Operation이 존재하며 그 차이는 간략하게 아래와 같다.

  • 프로젝트의 효율성, 사례 적합성 등을 고려해서 적절하게 GCD 와 Operation 을 혼합하여 활용하면 된다.

 

3. 동기(sync) VS 비동기(async)

비동기 개념

  • 작업을 다른 쓰레드에서 하도록 시킨 후, 그 작업이 끝나길 “안 기다리고” 다음일을 진행한다. → 안 기다려도 다음 작업을 생성할 수 있다.
  • 여기서 작업을 기다리고, 안기다린다는 것은 해당 작업을 실행하는 쓰레드라는 것을 명심해야한다. 그래야 향후 작업을 기다린다, 안기다린다라는 문구가 나오더라도 안헷갈린다.

동기 개념

  • 작업을 다른 쓰레드에서 하도록 시킨 후, 그 작업을 끝나길 “기다렸다가” 다음일을 진행한다. → 기다렸다가 다음 작업을 생성할 수 있다.
  • 예시는 아래와 같다.
DispatchQueue.global().sync {
	
}
  • 위 코드를 해석하면 원래의 작업이 있던 곳 (메인스레드)에서 디스패치 글로벌 큐로 보낸 작업을 동기적으로 기다린다. 는 의미를 가진다.
  • 동기 처리를 진행할 경우 다른 쓰레드로 작업을 보내더라도 메인쓰레드에서 작업을 처리하게 된다. 그 이유는 다른 쓰레드로 작업을 보내더라도 다른 쓰레드에서 작업을 끝날떄 까지 메인쓰레드도 멈춰있기 떄문이다. 이에 따라서 동기처리를 할 경우 다른 스레드로 작업을 보내나 메인 스레드에서 작업 진행하나 결과가 같기 때문에 메인 스레드에서 일을 처리한다고 한다.

예제

let queue = DispatchQueue.global()

func task1() {
    print("Task 1 시작")
    sleep(1)
    print("Task 1 완료★")
}

func task2() {
    print("Task 2 시작")
    print("Task 2 완료★")
}

func task3() {
    print("Task 3 시작")
    sleep(4)
    print("Task 3 완료★")
}

func task4() {
    print("Task 4 시작")
    sleep(3)
    print("Task 4 완료★")
}

task1()
task2()
task3()
task4()
  • 위와 같이 우리가 일반적으로 사용하는 코드의 경우 동기적으로 작동한다고 보면 될 것이다.
  • 작업순서 : task1() → task2() → task3() → task4()
  • 위와 같은 코드는 메인 쓰레드에서 작업이 되는 것이다.

“비동기” 라는 개념이 필요한 이유는?

  • 대부분은 서버와의 통신(네트워크 작업) 때문이다. → 네트워크와 관련된 작업들은 내부적으로 비동기적으로 구현해놓음
  • 예제

  • URLSession 은 비동기적으로 작업을 처리하고 있다.

 

4. Serial (직렬) vs Concurrent (동시)

  • Serial 과 Concurrent 큐의 특성에 관한 것이다.

  • 위 그림을 보면 큐는 크게 디스패치큐, 오퍼레이션 큐로 나뉘고, 또 디스패치큐와 오퍼레이션큐를 세분화할 수 있다.
  • 디스패치큐 안에는 3개의 버전이 존재하면 각 버전별로 특성이 다르다.

 

Serial (직렬)

  • 위 그림과 같이 Serial 큐는 받아들인 작업을 한개의 스레드로만 보내는 큐이다.
  • 어떤 스레드로 보내는지는 알 수가 없다.
  • 즉, 보통 메인에서 분산처리 시킨 작업을 “다른 한개의 쓰레드에서” 처리하는 큐이다.
  • 순서가 중요한 작업을 처리할 때 사용한다.

 

Concurrent (동시)

  • 위 그림과 같이 Concurrent 큐는 받아들인 작업을 여러개의 스레드로 보내는 큐이다.
  • 어떤 스레드로 보내는지는 알 수 없다.
  • 즉, 보통 메인에서 분산처리 시킨 작업을 “다른 여러개의 쓰레드에서” 처리하는 큐이다.
  • 각자 독립적이지만 유사한 여러 개의 작업을 처리할 때 사용한다. → 테이블뷰에서 셀 하나 하나를 네트워크 통신을 통해 데이터를 받을 때

 

비동기(Async)란 말과 동시(Concurrent)란 말이 같은 말인가?

  • 해당 개념이 많이 헷갈리는 부분이라 그림으로 해당 내용을 명확하게 구분하고 가야한다.
  • 비동기의 개념은 하나의 쓰레드에서 작업을 처리할 때 다음 작업을 기다릴지 안기다릴지를 의미하는 것이다.
  • 반면 동시의 개념은 하나의 쓰레드를 다른 쓰레드에 작업을 분배하는데 하나의 다른 쓰레드에 분배할 것인지 아니면 다른 여러개의 쓰레드에 분배할 것인지를 나타내는 개념이다.
  •