Notice
Recent Posts
Recent Comments
Link
관리 메뉴

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

[iOS - swift] DispatchQueue.main.sync와 DispatchQueue.main.async 이해하기, sync를 사용해야하는 상황 (#mainSyncSafe) 본문

iOS 응용 (swift)

[iOS - swift] DispatchQueue.main.sync와 DispatchQueue.main.async 이해하기, sync를 사용해야하는 상황 (#mainSyncSafe)

jake-kim 2023. 7. 24. 01:51

DispatchQueue의 sync와 async 동작

  • main queue와 thread 구분하기
    • main thread는 single thread이며, 해당 작업을 처리하는곳이 queue
    • async와 sync로 각각 queue에 작업 담기가 가능
DispatchQueue.main.async { // main queue에 async 작업 넣는 곳 }
DispatchQueue.main.sync { // main queue에 sync 작업 넣는 곳 }
  • ViewController를 생성하고 DispatchQueue.main 없이 코드를 작성하면 그 코드들은 모두 DispatchQueue.main.sync 큐에서 처리
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // run at main thread synchronously
        print(123)
    }
}
  • 만약 DispatchQueue.global().async 로 다른 함수를 호출했을때는 호출된 함수 내부에서는 background thread로 동작
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // run at main thread synchronously
        print(123)
    }
    
    func runOnBackgroundThread() {
        DispatchQueue.global().async {
            self.someCode()
        }
        print("out of background thread?", Thread.isMainThread) // true
    }

    func someCode() {
        print("Thread.isMainThread?", Thread.isMainThread) // false
    }
}

mainSyncSafe 개념

  • 앞에서 알아보았듯이 일반적으로 코드를 입력하면 main에서 sync하게 동작
  • deadlock 현상
    • sync로 작업을 추가하면 큐에서는 이 일을 우선순위로 보고 처리하려고 하고, 동시에 sync하게 들어갔으므로 print(123)작업은 이전에 들어간 task들이 끝나기를 기다리며 무한정 기다리는 문제가 발생

  • mainSyncSafe 라는것을 DispatchQueue 확장으로 만들어서 현재 스레드가 메인 스레드이면 바로 실행하고, 메인 스레드가 아니면 sync하게 동작하도록 구현
extension DispatchQueue {
    static func mainSyncSafe(execute work: () -> Void) {
        if Thread.isMainThread {
            work()
        } else {
            DispatchQueue.main.sync(execute: work)
        }
    }
}

sync, async 코드로 이해하기

  • only sync
    • 코드 순서상 위에서 아래로 실행
func syncOnlySample() {
    print("start only sync \n")
    
    DispatchQueue.mainSyncSafe {
        print(1)
        print(11)
    }
    
    print("\nbetween 1 and 2\n")
    
    DispatchQueue.mainSyncSafe {
        print(2)
        print(22)
    }
    
    print("\nbetween 2 and 3\n")
    
    DispatchQueue.mainSyncSafe {
        print(3)
        print(33)
    }
    
    print("\nbetween 3 and 4\n")
    
    DispatchQueue.mainSyncSafe {
        print(4)
        print(44)
    }
}

/*
start only sync 

1
11

between 1 and 2

2
22

between 2 and 3

3
33

between 3 and 4

4
44

*/
  • only async
    • async 블록으로 감싸지 않으면 우선순위는 sync가 높으므로 sync 작업들이 다 처리된 후 async 작업들이 호출
    • async는 이전에 큐에 담긴 작업들이 끝나는 것을 기다리지 않으므로 메인 큐에 print("nbetween 1 and 2\n")보다 print(1)가 먼저 담겼지만 print(1)가 나중에 실행
func asyncOnlySample() {
    print("start only async \n")
    
    DispatchQueue.main.async {
        print(1)
        print(11)
    }
    
    print("\nbetween 1 and 2\n")
    
    DispatchQueue.main.async {
        print(2)
        print(22)
    }
    
    print("\nbetween 2 and 3\n")
    
    DispatchQueue.main.async {
        print(3)
        print(33)
    }
    
    print("\nbetween 3 and 4\n")
    
    DispatchQueue.main.async {
        print(4)
        print(44)
    }
}

/*
start only async 

between 1 and 2

between 2 and 3

between 3 and 4

1
11
2
22
3
33
4
44

*/
  • async보다 sync의 우선순위가 높은 것을 확인해볼수 있는 예제
    • 메인 큐에 추가된 것은 print(1)가 print(2)보다 더욱 빠르게 추가됐지만, print(2)가 먼저 실행
func combinationSample1() {
    print("start multi Thread \n")
    
    DispatchQueue.main.async {
        print(1)
        print(11)
    }
    
    print("\nbetween 1 and 2\n")
    
    DispatchQueue.mainSyncSafe {
        print(2)
        print(22)
    }
}

/*
start multi Thread 

between 1 and 2

2
22
1
11

*/

sync를 언제 사용해야할까?

  • 대부분 async를 사용하여 main thread를 관리하면 deadlock을 예방할 수 있지만 sync를 특별한 경우에 사용해야하는 케이스가 존재
  • sync를 사용해야하는 경우?
    • UI 업데이트가 순서대로 처리되어야 하는 경우
    • 여러개의 UI 업데이트에 있어서, 우선순위가 높은 업데이트를 해주고 싶은 경우, sync를 사용하여 메인 스레드를 점유할 수 있도록 구현

* 전체 코드: https://github.com/JK0369/ExMainThread

* 참고

https://gist.github.com/sgr-ksmt/4880c5df5aeec9e558622cd6d5b477cb

Comments