iOS/iOS 지식

[iOS] GCD Priority Inversion (우선 순위의 뒤바뀜)

홍복치 2024. 8. 3. 18:17

오늘도 어김없이 오류를 내었음.

높은 우선순위의 작업(User-initiated)이 낮은 우선순위 작업(default)을 기다리고 있으니까 이 우선순위 역전을 피할 수 있는 방법을 찾아라 뭐 그런거 같음

오류를 보면 Data(ContentsOf)에서 나는 것을 볼 수 있었음. 

Thread Performance Checker: 
Thread running at User-initiated quality-of-service class waiting on a lower QoS thread running at Default quality-of-service class. Investigate ways to avoid priority inversions

[NSData(NSData) initWithContentsOfURL:options:maxLength:error:]

전체적인 로직은 아래와 같음

좋아요를 누르면 -> 이미지를 dataContentsOf로 다운로드 후 FileManager에 저장

좋아요를 해제하면 -> 이미지를 FileManager에서 삭제

 

두 장의 이미지를 받아야 하기 때문에 DispatchGroup을 통해서 둘 다 정상적으로 다운받지 못할 경우 Toast를 출력하도록 했음

func downloadImage(imageURL: String, profileURL: String, imageID: String){
        let group = DispatchGroup()
        var isFailed = false
        
        group.enter()
        DispatchQueue.global().async(group: group) {
            print("DispatchQueue", Thread.current.qualityOfService.rawValue)
            
            imageURL.loadImage { result in
                switch result {
                case .success(let value):
                    ImageFileManager.saveImageToDocument(image: value, filename: imageID)
                case .failure:
                    isFailed = true
                }
                group.leave()
            }
        }
        
        //DispatchQueue 로직 생략//
    
        group.notify(queue: .main){
            if isFailed {
                self.showToast("이미지를 다운받지 못했습니다")
            }
        }
    }

여기가 오류의 발생지인데, Data(ContentsOf)를 사용하는 곳임

자세히 모르겠지만 Data(ContentsOf) 가 실행되는 스레드의 qos가 default이고

qos가 userInitiated인 스레드가 이 다운로드가 끝날때까지 기다리고 있는 것임 

원래대로라면 userInitiated스레드의 작업 이후, default 스레드의 작업이 이루어져야 하지만 반대로 되고 있네? 라는 것임

    func loadImage(completion: @escaping (Result<UIImage, Error>) -> Void) {
        guard let url = URL(string: self) else {
            completion(.failure(LoadImageError.invalidURL))
            return
        }
        
        if let data = try? Data(contentsOf: url) {
            if let image = UIImage(data: data) {
                completion(.success(image))
            }
        }else{
            DispatchQueue.main.async {
                completion(.failure(LoadImageError.failedDownload))
            }
            
        }
    }

ViewController 호출부의 실행- UserInteractive

DispatchQueue 안 코드들의 실행 - UserInitiated 

dataContentsOf - default

아래 그림과 같은 상황임. qos가 높은 스레드의 실행순서가 더 낮아짐 > 우선순위 역전

 

우선순위 역전 문제에서는 낮은 우선순위의 작업을 높은 우선순위로 변경한다고 함.

Data(ContentsOf) 작업의 우선순위를 높일수가 없을 것 같아서 높은 우선순위의 작업을 우선순위를 낮추어서 해결해볼 수 있지 않을까 생각함

DispatchQueue의 qos를 utility로 변경해서 default보다 낮춰주니 해당 오류가 사라짐

 func downloadImage(imageURL: String, profileURL: String, imageID: String){
        let group = DispatchGroup()
        var isFailed = false
        
        group.enter()
        DispatchQueue.global(qos: .utility).async(group: group) {
            print("DispatchQueue", Thread.current.qualityOfService.rawValue)
            imageURL.loadImage { result 
                switch result {
                case .success(let value):
                    ImageFileManager.saveImageToDocument(image: value, filename: imageID)
                case .failure:
                    isFailed = true
                }
                group.leave()
            }
        }

        //DispatchQueue 로직 생략//

        group.notify(queue: .main){
            if isFailed {
                self.showToast("이미지를 다운받지 못했습니다")
            }
        }
    }

 

참고자료

https://sujinnaljin.medium.com/ios-%EC%B0%A8%EA%B7%BC%EC%B0%A8%EA%B7%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-gcd-15-3fef697f9aab