ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • GCD - DispatchQueue Main/Global 큐 스레드에 관해
    개발/Swift 2021. 11. 23. 18:07

    메인스레드에 몰린 일들을 여러 스레드에게 나누어주는것이 동시성 프로그래밍의 이유이다.

     

    GCD는 DispatchQueue 에 들어온 작업들에 알맞은 스레드를 생성→ 실행→제거 한다.

    GCD가 해주니까 우리는 DispatchQueue에 넣어주면 된다는것이다.

     

    DispatchQueue 에는 serial (직렬) 큐와 concurrent(병렬) 큐가있다.

    Serial은 한스레드를 실행하여 전 작업이 끝나고 후작업을 시작하고

    Concurrent는 여러 스레드로 작업이 분배되어 순서대로 시작은 하지만

    끝나는 시점은 모두 다르다.

     

    지금까지 이해하기로는 Serial은 동기성

    Concurrent는 비동기 같은 느낌이다. 하지만 이들은 다르다고한다.

     

    Serial + Sync → 작업을 1개 스레드로 보내고 동기로 처리

    메인스레드가 다른 1개 스레드로 작업을 보내고 멈춤(Sync)

    넘겨진 작업들은 직렬로 처리

     

    serial + Async → 작업을 1개 스래드로 보내고 비동기 처리

    메인스레드가 다른 하나의 스레드로 작업을 분배 하자마자 다른일을 함(Async)

    작업들은 1개 스레드에서 직렬로 처리됨

     

    Concurrent + Sync → 여러 스레드로 작업을 보내고 동기처리

    메인스레드가 여러 스레드로 작업을 보내고 멈춤(Sync)

    넘겨진 작업들은 다른작업들을 기다리지않고 실행됨

     

    Concurrent + Async → 여러 스레드로 작업을 보내고 비동기처리

    메인스레드가 여러 스레드로 작업들을 보내고 다른일을 함 (Async)

    넘겨진 작업들 병렬 처리됨

     

     

    이렇게 보면 쉽게 처리하자면 Sync/Async는 메인스레드가 어떻게 작업하냐에달린거고

    Serial/Concurrent는 다른 스레드로 넘겨진 작업들이 어떻게 작업하냐인것으로 정리된다.

     

    Main큐는 단 하나만 있다. 코드에서 별도로 처리해주지 않으면 무조건 Main큐로 작업이 할당된다.

    Main큐는 Main스레드로 작업을 보내고 Main스레드도 하나만 있다. 여러스레드가 아닌 하나의 스레드로 작업을 할당 하므로 Main큐는 Serial 큐이다.

    반대로 global큐는 Concurrent큐이며 Qos에 따라 여섯종류로 나뉘며 이 종류에따라 중요도가 결정된다.

     

    UI처리는 무조건 Main스레드에서 한다. 이미지 데이터를 global에서 받더라도 UI를 업데이트해주는것은

    Main에서 해야한다.

    이렇게 Main스레드에서 UI관련 일을 하는데 Sync를 사용하게되면 UI 업데이트가 느려진다 그러므로 sync를 사용하지 않고 Async를 사용해야한다.

    DispatchQueue.main.sync {
    	//Do Not Use This
    }
    

    그러면 조금 덜 중요한작업을 Global 큐에 보내면 되겠다.

    여기서 Qos에 따라 중요도를 결정짓는다 하는데

    UserInteractive → UserInitiated → default → utility → background →unspecifed 순서이다.

    DispatchQueue.global().asunc{
    //Qos Unspecified
    }
    

    위와같이 사용하면 덜중요하다 판단하기 떄문에 qos설정을 넣어주는것이 좋다.

    UserInteractive 같이 중요도가 높은 큐의 작업은 그렇지 않은 작업들보다 더 많은 스레드를 차지하여 빨리 처리하는 방식이다.

    참조 : https://sujinnaljin.medium.com/io s-차근차근-시작하는-gcd-5-c8e6eee3327b

     

    주의사항

     

    1. 메인스레드에서 다른큐로 보낼때 sync를 사용하면 안된다.

    메인에서 sync스레드를 사용하는순간 기다린다. 그럼 UI 가멈출것이다. 그러므로 사용하면안된다.

    즉 sync가 필요할때는 async 큐로 보내고 그안에서 sync큐로 보내는 방법이있다.

     

    2. async 큐 안에 sync 큐를 사용할때 같은 global 큐에 sync 작업을 보낸다면 데드락 현상이 발생한다.

    DispatchQueue.global().async{  //Task1
      DispatchQueue.global().sync{ //Task2
      
      }
    }
    

    Task2 는 동기처리되므로 작업이 끝날때까지 기다릴것이다.

    하지만 위와 같은경우 같은 큐를 사용한다. (unspecified)

    그리고 같은 큐를 사용하는경우 같은 스레드를 사용 할수있다.

    즉 위와같은 경우 같은 스레드가 Task1과 Task2를 모두 처리해야 하는 상황이 올수있다.

     

    Task2를 보내고 기다리고있을 텐데 다시 Task1이 돌아온 셈이다.

    의도치않은 경우일것이며 이럴때 교착상태(데드락)이 발생한다.

    그러므로 아래와 같이 다른 큐를 사용하는것이 좋다.

    DispatchQueue.global(qos:.utility).async{  //Task1
      DispatchQueue.global().sync{ //Task2
      
      }
    }
    

     

     

    Dispatch group

    디스패치 그룹을사용하면 여러 스레드에서 하는 작업을 묶을수있다.

    묶는 이유는 그 작업들이 모두 끝나는 시점을 파악할수있기때문이다.

    let group = DispatchGroup()
    DispatchQueue.global().async(group:group){
    
    }
    

    끝나는 시점을 알기 위해서는 notify(queue)를 사용한다

    group.notify(quere:.main) {
    //Do something when finish
    }
    

    notify 함수 안에 끝났을떄 실행해야할 로직을 구현하면 될것이다.

    이외에도 wait() 함수를 사용해 끝날 시점을 알수있지만 굳이 사용할 일은 없다한다.

    그러나 wait() 함수를 통해 group에 timeout을 지정해줄수 있다.

    만약 그룹에서 각각의 작업들이 제한 시간내에 안끝날때 실패처리를 한다던지 등에 사용될거같다.

    let timeoutResult = group.wait(timeout: .now() +60 )
    
    switch timeoutResult {
    	case .success:
    		print("done in 60 sec")
    	case .failed:
    		print("failed in 60 sec")
    }
    

    다만 wait 을 사용하려면 group이 메인이 아닌 글로벌 큐에서 진행되어야한다.

    wait은 작업들이 끝날때까지 현재 스레드를 멈추기 떄문에 메인인경우 메인이 멈춰버린다.

    DispatchQueue.global().async(group:group){
    	print("Task1")
    	ListAPI.fetchList() { }// Async Task
    	print("Task2")
    }
    

    디스패치 그룹에 비동기 작업을 넣는경우 문제가 발생한다.

    비동기 작업은 다른 스레드에서 진행되기 때문에 해당 스레드에서는 작업을 던져준후 리턴한다.

    즉 해당 비동기 작업이 안끝났는데 끝났다고 notify 된다.

    그래서 enter() / leave()를 사용해준다.

    DispatchQueue.global().async(group:group){
    	print("Task1")
    
    	group.enter()
    	ListAPI.fetchList() { 
    		group.leave()
    	}// Async Task
    
    	print("Task2")
    }
    

    이렇게 사용한다.

    enter() leave()는 reference counting을 사용한다.

    enter는 +1 count / leave는 -1 count 해주며 0이 되는게 그룹이 끝나는 시점이다.

    댓글

Designed by Tistory.