관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 1. 스레드 프로그래밍 이해하기 - 동시성 프로그래밍, thread, GCD, Swift Concurrency 본문

iOS 응용 (swift)

[iOS - swift] 1. 스레드 프로그래밍 이해하기 - 동시성 프로그래밍, thread, GCD, Swift Concurrency

jake-kim 2023. 2. 26. 22:46

1. 스레드 프로그래밍 이해하기 - 동시성 프로그래밍, thread, GCD, Swift Concurrency <

2. 스레드 프로그래밍 이해하기 - Swift Concurrency가 나온 이유 (GCD의 한계, Thread explosion)

스레드란?

  • process가 만들어내는 '일꾼'같은 개념
  • process와 data영역을 공유하며 개별적으로 stack, text와 같은 영역을 가지며 일을 처리
  • process가 thread를 만들어서 처리하는 이유는 context switching 비용이 덜 들고 여러개의 작업을 처리할 때 특정 작업이 오래걸리면 A 일꾼에게 시키고, B, C 일꾼들은 다른 작업을 처리하게하면 일을 효율적으로 처리할 수 있기 때문

스레드 프로그래밍이 왜 필요한지?

  • 자원을 효율적으로 사용하고 더 좋은 성능을 만들기 위해 스레드가 필요
    • 일반적으로 I/O 작업은 많은 시간 소요가 필요한데, 이 때 CPU 스케줄러가 해당 작업을 A thread에게 맡기고 이 작업이 끝날동안 다른 thread를 이용하여 처리할 때 효율적
    • I/O 작업 말고도 이처럼 많이 걸리는 작업이 있을때 thread를 사용하여 처리하여 효율적인 프로그래밍이 가능
    • thread 전환 비용이 process의 context switching 작업보다 더 작은 비용
  • Thread 관점에서 생각하여 효율적인 프로그래밍을 위한 방법을 고민하며 프로그래밍해보기
  • Thread 관점에서 효율적인 프로그래밍이 무엇인지 알기 전에 swift에서 thread 사용에 관해서 살펴보자

스레드 프로그래밍 살펴보기

  • GCD를 사용하여 스레드를 변경해주는 것의 의미?
    • 아래 예제에서 1 -> 2 -> 3으로 출력되지 않고 1 -> 3 -> 2로 출력되는 이유?
print(1)
DispatchQueue.main.async {
    print(2)
}
print(3)

/*
 1
 3
 2
 */
  • print(2)는 async로 작업되는것으로 프로그래밍 했으니, print(2) 의 실행은 지금 바로 실행이 되지 않아도 되기 때문에 print(2)의 실행 시점은 알 수 없음
  • 여기까지는 일반적으로 알고 있는 내용이지만 실행 시점은 누가 제어하는것인지?
    • 아래 스레드 제어권을 이해할 것

스레드 제어권

https://developer.apple.com/videos/play/wwdc2021/10132/?time=919

(WWDC 2021에 나오는 내용)

  • 스레드 제어권이란 해당 스레드를 점유하여 이 스레드를 사용하여 work를 처리하는데 사용하는 주체
  • 스레드 제어권은 바로 함수가 가지고 있음
    • A()라는 함수를 호출 할때 A에서 사용하려는 스레드 제어권은 A함수가 가지고 있는 상태여서 async같은 호출을 하지 않으면 일반적으로 코드 순서대로 실행
    • 만약 DispatchQueue.main.async를 사용하면 해당 블록 안의 스레드 제어권은 시스템에게 넘기게 되어 시스템 내부에서 scheduling하여 실행되도록 처리
  • 시스템에게 스레드 제어권을 넘기게되어서 DispatchQueue.main.async 블록 안의 코드는 언제 실행될지 모르는 것
print(1)
DispatchQueue.main.async {
    print(2)
}
print(3)

/*
 1
 3
 2
 */
  • 시스템에 스레드 제어권을 넘기기 때문에 개발자는 따로 우선순위를 정하여 작업을 요청하는 global queue에서의 qos도 있는것

https://ios-development.tistory.com/1201

  • DispatchQueue.main.sync를 사용하면 deadlock이 걸리는 이유도 스레드의 제어권을 생각하며 이해하기
    • viewDidLoad()에서 GCD 관련 코드 없이 코딩하면 모두 viewDidLoad에서 main thread에 대한 제어권을 가지고 작업을 수행
    • 하지만 DispatchQueue.main.sync를 추가하면 스레드의 제어권은 시스템에 넘어간 상태
    • 이 큐는 하나의 스레드만 담당하는 serial queue이며 sync이기 때문에 FIFO 형태로 일을 처리
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // deadlock
        DispatchQueue.main.sync {
            print(1)
        }
    }
}
  • main queue에는 현재 viewDidLoad()의 DispatchQueue.main.sync 코드를 만나게 되어 block처리가 되며, DispatchQueue.main.sync를 처리하라는 명령어가 입력된 상태 -> DispatchQueue.main.sync에서 처리하는 스레드는 방금전 block한 같은 main thread를 사용하는데 이미 block된 상태이므로 deadlock 발생
    • 만약 async로 한다면 애초에 block을 걸지 않기 때문에 deadlock 발생 x

swift에서의 스레드 관리

  • swift에서는 async나 concurrent하게 동작되도록하면 동시에 여러 개의 명령들을 처리가 되도록 구현

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/

 

  • 그럼 thread만 무한히 만들면 효율적일까?
    • 저렴한 context switching 비용을 위해 thread를 만들었지만 아이러니하게도 8core에서 thread의 갯수가 100개가 되는 경우가 존재
    • 100개의 context switching보단 8개의 context switching이 효율적일것
    • 이 문제를 Thread explosion 라고 명칭

Thead Explosion 및 GCD의 한계, swift Concurrency가 나온 이유에 관한 글은 다음 포스팅 글에서 계속

 

* 참고

https://developer.apple.com/videos/play/wwdc2021/10254/

https://sujinnaljin.medium.com/swift-actor-%EB%BF%8C%EC%8B%9C%EA%B8%B0-249aee2b732d

https://developer.apple.com/videos/play/wwdc2021/10132/?time=919 

Comments