Dispatch Queue

작성일

Dispatch Queue

  • 쓰레드를 여러개 만들어서 관리한다.

쓰레드

메인쓰레드

    @IBAction func action1(_ sender: Any) {
        simpleClosure {
            finishLabel.text = "끝"
        }
    }
    
    func simpleClosure(completion: () -> Void) {
        
        for index in 0..<10 {
            Thread.sleep(forTimeInterval: 0.1)
            print(index)
        }
        
        completion()
    }

이렇게 작성을 하면 메인쓰레드을 사용해가지고, 다른 작업을 못할수도있다.(쓰레드가 할 일이 너무 많이 때문에)
하지만 이럴떄 쓰레드를 하나 더 추가해주면 된다.(쓰레드는 일꾼같은 느낌이다.)

쓰레드를 하나 더 추가해 주려면,

    @IBAction func action1(_ sender: Any) {
        simpleClosure {
            self.finishLabel.text = "끝"
        }
    }
    
    func simpleClosure(completion: @escaping () -> Void) {
        
        DispatchQueue.global().async {
            for index in 0..<10 {
                Thread.sleep(forTimeInterval: 0.2)
                print(index)
            }
        
            DispatchQueue.main.async {
                completion()
            }
        }
    }

또는,

    @IBAction func action1(_ sender: Any) {
        simpleClosure {
            DispatchQueue.main.async {
                self.finishLabel.text = "끝"
            }
        }
    }
    
    func simpleClosure(completion: @escaping () -> Void) {
        
        DispatchQueue.global().async {
            for index in 0..<10 {
                Thread.sleep(forTimeInterval: 0.2)
                print(index)
            }
                completion()
        }
    }

이런 방식으로 사용할 수도 있다.
쓰레드의 개념을 좀 더 살펴보면,

    @IBAction func action2(_ sender: Any) {
        
        let dispatchGroup = DispatchGroup()
        
        let queue1 = DispatchQueue(label: "q1")
        
        queue1.async(group: dispatchGroup) {
            for index in 0..<10 {
                Thread.sleep(forTimeInterval: 0.2)
                print(index)
            }
        }
        
        queue1.async(group: dispatchGroup) {
            for index in 10..<20 {
                Thread.sleep(forTimeInterval: 0.2)
                print(index)
            }
        }
        
        queue1.async(group: dispatchGroup) {
            for index in 20..<30 {
                Thread.sleep(forTimeInterval: 0.2)
                print(index)
            }
        }  
        
    }

를 살펴보면 원래 async가 있어서 바로바로 실행이 되어야 하지만,
queue1이라는 한 쓰레드 안에서 작동되기 때문에 sync로 작동이 되는 것이다.
만약에, 바로바로 실행시키려면 쓰레드를 추가하면 된다.

    @IBAction func action2(_ sender: Any) {
        
        let dispatchGroup = DispatchGroup()
        
        let queue1 = DispatchQueue(label: "q1")
        let queue2 = DispatchQueue(label: "q2")
        let queue3 = DispatchQueue(label: "q3")
        
        queue1.async(group: dispatchGroup) {
            for index in 0..<10 {
                Thread.sleep(forTimeInterval: 0.2)
                print(index)
            }
        }
        
        queue2.async(group: dispatchGroup) {
            for index in 10..<20 {
                Thread.sleep(forTimeInterval: 0.2)
                print(index)
            }
        }
        
        queue3.async(group: dispatchGroup) {
            for index in 20..<30 {
                Thread.sleep(forTimeInterval: 0.2)
                print(index)
            }
        }
    }

이렇게 작동하면 쓰레드가 3개가 되어서 각각 작동하는걸 볼 수 있다.
하지만, 만약에 이 쓰레드3개가 끝나는 시점을 알고 싶다면 어떻게해야 할까? 그건 바로 dispatchGroup를 사용하면된다.

        dispatchGroup.notify(queue: DispatchQueue.main){
            print("끝")
        }

.notify라는 메서드가 이제 위의 3개의 쓰레드가 각각 끝이 났으면 작동되는 메서드이다.
하지만, 이렇게 쓰레드안에 쓰레드를 넣으면 어떻게 작동될까?

    @IBAction func action2(_ sender: Any) {
        
        let dispatchGroup = DispatchGroup()
        
        let queue1 = DispatchQueue(label: "q1")
        let queue2 = DispatchQueue(label: "q2")
        let queue3 = DispatchQueue(label: "q3")
        
        queue1.async(group: dispatchGroup) {
            DispatchQueue.global().async {
                for index in 0..<10 {
                    Thread.sleep(forTimeInterval: 0.2)
                    print(index)
                }
            }
        }
        
        queue2.async(group: dispatchGroup) {
            DispatchQueue.global().async {
                for index in 0..<10 {
                    Thread.sleep(forTimeInterval: 0.2)
                    print(index)
                }
            }
        }
        
        queue3.async(group: dispatchGroup) {
            DispatchQueue.global().async {
                for index in 0..<10 {
                    Thread.sleep(forTimeInterval: 0.2)
                    print(index)
                }
            }
        }

        dispatchGroup.notify(queue: DispatchQueue.main){
            print("끝")
        }
        
    }

이렇게 하면 일단, “끝”부터나오고 쓰레드들이 작동이 된다.
그 이유는 일단 쓰레드가 작동이 되고, 그안에 다른쓰레드가 작동이 되든 말든 밑에 queue2부터 실행이되고, 마찬가지로 queue3이 작동이된다.
그래서 “끝” 부터 나오고 쓰레드들이 실행이 되는것이다. 이걸 방지 하려면 어떻게 해야할까?

    @IBAction func action2(_ sender: Any) {
        
        let dispatchGroup = DispatchGroup()
        
        let queue1 = DispatchQueue(label: "q1")
        let queue2 = DispatchQueue(label: "q2")
        let queue3 = DispatchQueue(label: "q3")
        
        queue1.async(group: dispatchGroup) {
            dispatchGroup.enter()
            DispatchQueue.global().async {
                for index in 0..<10 {
                    Thread.sleep(forTimeInterval: 0.2)
                    print(index)
                }
                dispatchGroup.leave()
            }
        }
        
        queue2.async(group: dispatchGroup) {
            dispatchGroup.enter()
            DispatchQueue.global().async {
                for index in 0..<10 {
                    Thread.sleep(forTimeInterval: 0.2)
                    print(index)
                }
                dispatchGroup.leave()
            }
        }
        
        queue3.async(group: dispatchGroup) {
            dispatchGroup.enter()
            DispatchQueue.global().async {
                for index in 0..<10 {
                    Thread.sleep(forTimeInterval: 0.2)
                    print(index)
                }
                dispatchGroup.leave()
            }
        }

        dispatchGroup.notify(queue: DispatchQueue.main){
            print("끝")
        }
        
    }

이렇게 dispatchGroup에있는 메서드(enter나 leave)를 사용해여 내가 작업이 끝났다 라는것을 수동으로 작성해줘야 한다.

만약에 sync를 사용하는데 같은 쓰레드안에서 사용하면 어떻게 될까? 바로 deadlock이 걸려버린다.

    @IBAction func action3(_ sender: Any) {
        let queue1 = DispatchQueue(label: "q1")
        
        queue1.sync {
                for index in 0..<10 {
                    Thread.sleep(forTimeInterval: 0.2)
                    print(index)
                }   
                queue1.sync {
                    for index in 10..<19 {
                        Thread.sleep(forTimeInterval: 0.2)
                        print(index)
                }
        }
    }
}

코드를 잘 살펴보면, 겉에있는 sync가 실행되는데 안에 sync가 있어버려가지고, 안에 있는 sync는 밖에있는 sync가 끝나길 기다리고,
안에있는 sync는 밖에있는 sync가 끝나길 기다리는 상태 즉, 무한 루프에 걸려버린것이다.
같은 이치로,

        DispatchQueue.main.sync {
            
        }

이렇게 메인 쓰레드에서 sync를 해버리면 앱자체가 바로 죽어버린다.
메인 쓰레드는 작업이 안끝나기 때문이다.

qos 우선순위

    @available(macOS 10.10, iOS 8.0, *)
    public static let background: DispatchQoS

    @available(macOS 10.10, iOS 8.0, *)
    public static let utility: DispatchQoS

    @available(macOS 10.10, iOS 8.0, *)
    public static let `default`: DispatchQoS

    @available(macOS 10.10, iOS 8.0, *)
    public static let userInitiated: DispatchQoS

    @available(macOS 10.10, iOS 8.0, *)
    public static let userInteractive: DispatchQoS

맨위가 제일 느리고, 점점 빨라지는 식이다. 우선순위가 높아지는것이지 무조건 먼저 실행되는건 아니다.

deadlock

  • 서로의 작업이 끝날때 까지, 기다리는 상태

sync

  • 나 말고 다른 쓰레드는 다멈추고 내 작업을 최우선으로 실행 시킨다.

@escaping

  • Escaping 클로저는 클로저가 함수의 인자로 전달됐을 때, 함수의 실행이 종료된 후 실행되는 클로저.(함수밖에서 실행됨)
    Non-Escaping 클로저는 이와 반대로 함수의 실행이 종료되기 전에 실행되는 클로저이다.