본문 바로가기

iOS

[iOS] 동시성 프로그래밍 햇갈리는 개념 잡기(feat. 야곰닷넷)

0️⃣ core와 thread(솦트웨어에서의)는 각각 무엇을 의미하는지 설명해보세요

core는 cpu에서 일처리하는 개체입니다.
코어는 한번에 한가지일만 처리가 가능하다는 특징을 가지고있습니다.

thread는 프로세스(프로그램) 내부에서 작업 단위가 되는 가상 스레드이며 작업을 처리하는 개체입니다.

 

1️⃣ sync와 async의 차이는 무엇인가요 ?

sync는 동기고 async는 비동기 방식이며 병프와 동프와는 전혀다른 맥락이다.
둘의 차이점은 동기는 작업을 끝날때까지 기다리는 것이고 비동기는 기다리지 않고 다음 블럭을 바로 실행시키는 코드이다.

 

2️⃣ async(비동기)와 concurrent(동시성)는 구분되는 개념이라고 했습니다. 각각을 설명해보세요.

async는 기다리지 않고 다음 코드 작업을 수행합니다.
concurrent는 core가 여러 thread를 이용해 동시에 조금씩 작업을 처리합니다.

 

3️⃣ DispatchQueue에서 serial 큐와 main 큐는 같은 것인가요?

이 둘은 작업이 실행되도록 하는 큐이다.

큐에서 얼마나 많은 일을 보내는지와 무관하게 serial queue는 한번에 하나 이상의 스레드에서 작업이 실행되지않습니다. 선입선출을 하며 시작과 종료를 보장해주는 것이죠 또한 using a sync call, semaphore, or some other tool을 사용해 serial queue를 차단하면 차단이 끝날때까지 해당 queue의 모든 작업이 중지됩니다.
ex) 세수하기의 async, 코딩하기 async, 롤하기 async를 Dispatch Queue에 보내면 실행 순서는

  1. 세수하기 시작… 종료
  2. 코딩하기 시작… 종료
  3. 롤하기 시작…종료
    이렇게 실행이된다.

반면 concurrent queue는 여러 thread를 생성할 수 있으며 시스템은 생성되는 thread의 수를 결정합니다. task은 항상 FIFO순서로 시작하지만 queue는 다음 task를 시작하기전에 작업이 완료될떄까지 기다리지않습니다. 따라서 순서가 랜덤하게 완료되는 것이죠
또한 concurrent queue에서 block 명령을 실행하면 다른스레드는 차단이 되지않습니다. 추가로 queue가 차단될때 thread 폭팔위험이 있습니다.

ex) 세수하기의 async, 코딩하기 async, 롤하기 async를 Dispatch Queue에 보내면 실행 순서는

  1. 세수하기 시작
  2. 코딩하기 시작
  3. 롤하기 시작
    이렇게 실행만 시키고 끝나는 시점을 기다리지않습니다.

원래 main queue(기본대기열은)는 직렬입니다. 사전에 정의돈 global queue(기본 대기열)은 모두 동시적입니다. 모든 dispatchqueue는 serial로 기본설정이 되있지만 선택적으로 속성을 설정해 동시성으로 사용할 수 있습니다.

 

4️⃣ DispatchQueue에서 main과 global()의 차이는 무엇인가요?

main은 기본적으로 생성이 되어있는 앱에 사용되는 Thread이고 global은
main에 존재하는 thread를 파생시켜 여러 작업을 동시에하기위해 사용하는 것입니다.

 

# GCD가 무엇인지 설명해보세요

일을 효율적으로 그리고 동시작업을 하기위해 다중 스레드를 활용하는것은 불가피 하다고 알고있습니다. 이를 개발자가 일일이 관리한다는것은 매우번거롭고 힘든일이기때문에 애플에서 스레드를 관리해주는 기술은 만든게 GCD라고 합니다.
이는 Grand Central Dispatch의 약자로 멀티코어 환경과 멀티 스레드 환경에서 최적화된 프로그래밍을 할 수 있도록 개발한 기술입니다.
그래서 개발자가 직접 코어와 스레드를 관리하지 않아도 시스템에서 알아서 관리를 해줍니다.

 

5️⃣ main 스레드의 개념과 특징을 설명해보세요

  • main thread란
    main 스레드는 앱의 기본적인 thread이며 앱이 실행되는 동안에 늘 메모리에 올라와 있는 기본 스레드입니다. 필수적으로 존재해야하다보니 main thread가 존재하지않으면 앱은 동작하지않습니다.
  • 특징
    • 전역적으로 사용이 가능합니다.
    • global스레드들과는 다르게 Run Loop가 자동으로 설정되고 실행됩니다. 메인스레드에서 동작하는 Run Loop를 Main Run Loop라고 합니다.
    • UI작업은 Main thread에서만 작업할 수 있습니다.

6️⃣ Serial에서 async는 어떻게 동작하나요?

serial queue에 들어가있는 작업이 밥먹기, 코딩하기, 롤하기 3가지가 있다고 가정을 해본다면 밥먹기를 실행행하고 끝날때까지 기다리지않고 코딩하기를 실행하기 그리고 롤하기까지 실행을 합니다. 그래서 추가적으로 main Thread외에 생성된 3개의 thread(밥먹기 thread, 코딩하기 thread, 롤하기 thread)가 조금씩 작업이 수행이되면서 동시다발적으로 작업이 돌아갑니다.

 
let serial1 = DispatchQueue(label: "com.besher.serial1")
let serial2 = DispatchQueue(label: "com.besher.serial2")

serial1.async {
    for _ in 0..<5 { print("🥶") }
}

serial2.async {
    for _ in 0..<5 { print("😡") }
}
//뒤죽박죽
🥶
😡
😡
😡
😡
😡
🥶
🥶
🥶
🥶

6️⃣-1️⃣ 여기서 잠깐 async를 serial이 각각이 아니라 하나의 serial queue로 작업을 aysnc로 해준다면 어떻게될까 ?

 
let serial = DispatchQueue(label: "com.besher.serial")

serial.async {
    for _ in 0..<5 { print("🥶") }
}

serial.async {
    for _ in 0..<5 { print("😡") }
}
//
🥶
🥶
🥶
🥶
🥶
😡
😡
😡
😡
😡

결과는 serial 방식이로 나오게됩니다. 왜그런 걸까요??
serial queue라서 그렇기때문입니다. serial queue의 동작방식이 [밥먹기, 코딩하기, 롤하기]가 있다고 하면 밥먹기가 실행되고 끝날때까지 코딩하기가 기다립니다. 그래서 밥먹기가 종료되면 코딩하기가 실행되고 똑같이 롤하기까지 같은 패턴으로 실행됩니다.
밥먹기라는 내부적인 thread세계에서는 추가적으로 반찬먹기, 국먹기, 쌀밥먹기 등이 있을텐데 이 부분이 비동기적으로 실행이되게됩니다. queue의 성격과 q내부에 작업단위가 동작하는 방식(동기/비동기)으로 구분되기때문에 헷갈릴 수 있지만 결과적으로 serial queue에 있는 큰작업단위는 한 단위가 종료될때까지 기다립니다.

 

6️⃣-2️⃣ 그러면 Dispatch Queue를 concurrent로 sync 해준다면 어떻게될까 ?

 
let concurrent = DispatchQueue(label: "com.besher.concurrent", attributes: .concurrent)

concurrent.sync {
    for _ in 0..<5 { print("🥶") }
}

concurrent.sync {
    for _ in 0..<5 { print("😡") }
}
🥶
🥶
🥶
🥶
🥶
😡
😡
😡
😡
😡

7️⃣ serial 큐에서 sync로 작업을 처리하면 어떻게 될까요?

serial queue에 들어가 있는 작업이 밥먹기, 코딩하기, 롤하기 3가지가 있다고 가정을 해본다면 밥먹기를 실행하고 끝날때까지 코딩하기와 롤하기는 block-wait 상태입니다. 그리고 밥먹기가 종료되면 코딩하기가 실행이 됩니다. 똑같이 코딩하기가 종료가되야만 롤하기가 실행이됩니다.

 
let serial1 = DispatchQueue(label: "com.besher.serial1")
let serial2 = DispatchQueue(label: "com.besher.serial2")

serial1.sync {
    for _ in 0..<5 { print("🥶") }
}

serial2.sync {
    for _ in 0..<5 { print("😡") }
}
//정렬
🥶
🥶
🥶
🥶
🥶
😡
😡
😡
😡
😡

8️⃣ main.sync를 사용하면 어떻게 되나요? 그 이유는 무엇인가요?

main.sync를 사용하면 교착상태에 빠지게 됩니다. 이는 작업이 끝나기를 기다리는 sync의 특성 때문인데요.
sync는 블록이 처리되기전까지 다음코드로 넘어가지 않는 특성을 가지고 있습니다. 이러한 상황을 Block-wait이라고 하는데 코드블록이 끝나기 전까지 그 스레드는 멈춰있겠다는 뜻입니다.

따라서 main thread에서 main.sync를 호출하게되면 main thread는 sync의 코드블록이 수행되기를 기다려야합니다. 근데 이때 sync의 코드블록 마저 멈춰버립니다.. main 스레드에서 실행되고 있던 코드이기 때문이죠

따라서 main스레드는 sync가 끝나기를, sync는 main thread의 Block-wait이 끝나기를 기다리는 상태입니다.

9️⃣ 병렬 프로그래밍과 동시성 프로그래밍은 각각 무엇인가요? 서로 반대되는 개념일까요?

서로 반대대는 개념은 아닙니다.

병프는 물리적인 개념으로 여러개의 core가 하나의 task를 분담해서 처리하는것임. 그래서 core가 여러개 있을때에 가능하다. 그렇다보니 single core에서는 병프가 불가합니다. 왜냐하면 병프는 동프와달리 실제로 동시에 task를 처리하기때문입니다.

동프는 논리적인 개념으로 하나의 core가 여러 task를 동시에 처리하는 것이다. 물리적으론 하나의 task를 처리하는 환경인데 그안에서 논리적으로 여러 task를 동시에 처리한다. 어떻게 ? 여러 가상 thread를 이용해서!

이 둘의 개념이 모두 동시에 처리하는 것처럼 보일 수 있지만
동프는 가상 스레드를 만들어 다중 스레드를 활용하며 병프는 다중 코어를 활용한다는 차이점이있다.


 

# 직렬큐와 동시큐 그리고 동기와 비동기는 어떻게다른가 ?

동기와 비동기
발신자 입장에서 기다리는지(동기) 안 기다리는지(비동기) 의 차이

직렬큐 동시큐
발신자(main thread)가 GCD에게 -> GCD가 받은 작업의 분배를 순서(직렬)에 맞게 할것인가? 동시에 해벌리 것인가? 차이이다.

 

 

# Reference

- 야곰닷넷 동시성프로그래밍