GCD의 동시 대기열과 직렬 대기열


117

GCD의 동시 및 직렬 대기열을 완전히 이해하기 위해 고군분투하고 있습니다. 나는 몇 가지 문제가 있으며 누군가가 그 시점에서 명확하게 대답 할 수 있기를 바랍니다.

  1. 작업을 차례로 실행하기 위해 직렬 대기열이 생성되고 사용된다는 것을 읽고 있습니다. 그러나 다음과 같은 경우에는 어떻게됩니까?

    • 직렬 대기열을 만듭니다.
    • I 사용 dispatch_async디스패치 (I 방금 만든 큐에 직렬) 세 개의 블록 A, B, C를 세번

    세 블록이 실행됩니까?

    • A, B, C 순서로 대기열이 직렬이므로

      또는

    • 동시에 (동시에 병렬 스레드에서) ASYNC 디스패치를 ​​사용했기 때문에
  2. 나는 dispatch_sync블록을 하나씩 실행하기 위해 동시 대기열에서 사용할 수 있다는 것을 읽고 있습니다 . 이 경우 원하는만큼의 블록을 SYNCHRONOUSLY 디스패치 할 수있는 동시 큐를 항상 사용할 수 있기 때문에 직렬 큐가 존재하는 이유는 무엇입니까?

    좋은 설명에 감사드립니다!


간단한 좋은 전제 조건 질문 디스패치 동기화 대 비동기
Honey

답변:


216

간단한 예 : 실행하는 데 1 분 정도 걸리는 블록이 있습니다. 메인 스레드에서 큐에 추가합니다. 네 가지 경우를 살펴 보겠습니다.

  • 비동기-동시 : 코드가 백그라운드 스레드에서 실행됩니다. 제어는 즉시 기본 스레드 (및 UI)로 돌아갑니다. 블록은 해당 큐에서 실행중인 유일한 블록이라고 가정 할 수 없습니다.
  • async-serial : 코드가 백그라운드 스레드에서 실행됩니다. 제어는 즉시 주 스레드로 돌아갑니다. 블록 해당 큐에서 실행중인 유일한 블록이라고 가정 할 수 있습니다.
  • sync-동시 : 코드가 백그라운드 스레드에서 실행되지만 주 스레드는 완료 될 때까지 대기하여 UI 업데이트를 차단합니다. 블록은 해당 큐에서 실행중인 유일한 블록이라고 가정 할 수 없습니다 (몇 초 전에 비동기를 사용하여 다른 블록을 추가 할 수 있음).
  • sync-serial : 코드가 백그라운드 스레드에서 실행되지만 주 스레드는 완료 될 때까지 대기하여 UI 업데이트를 차단합니다. 블록 해당 큐에서 실행중인 유일한 블록이라고 가정 할 수 있습니다.

분명히 장기 실행 프로세스에는 마지막 두 가지 중 하나를 사용하지 않을 것입니다. 일반적으로 다른 스레드에서 실행중인 항목에서 UI (항상 기본 스레드에 있음)를 업데이트하려고 할 때 표시됩니다.


14
그래서 당신은 다음과 같이 말하고 있습니다 : (1) 대기열의 유형 (conc 또는 serial)은 작업이 순서대로 실행되는지 병렬로 실행되는지를 결정하는 유일한 요소입니다.; (2) 디스패치 유형 (동기 또는 비동기)은 실행이 진행되는지 아니면 다음 명령어로 이동하지 않는지 여부를 나타냅니다. 내 말은, 태스크 SYNC를 디스패치하면 실행되는 큐에 관계없이 해당 태스크가 완료 될 때까지 코드가 차단됩니다.
Bogdan Alexandru

13
@BogdanAlexandru 맞습니다. 큐는 블록을 큐에 넣는 방법이 아니라 실행 정책을 지시합니다. 동기화는 블록이 완료 될 때까지 대기하지만 비동기는 그렇지 않습니다.
Jano

2
@swiftBUTCHER 특정 지점까지, 예. 대기열을 만들 때 최대 스레드 수를 지정할 수 있습니다. 작업을 더 적게 추가하면 병렬로 실행됩니다. 그 이상을 사용하면 일부 작업은 사용 가능한 용량이있을 때까지 대기열에 남아 있습니다.
Stephen Darlington

2
@PabloA., 주 스레드는 직렬 대기열이므로 실제로 두 가지 경우 만 있습니다. 그 외에는 정확히 동일합니다. 비동기는 즉시 반환됩니다 (그리고 블록은 현재 실행 루프의 끝에서 실행될 것입니다). 당신이 동기화를 할 경우 주요 잡았다는 에서 메인 스레드 에 대한 교착 상태를 얻을 수있는 경우에 메인 스레드.
Stephen Darlington 2016

1
@ShauketSheikh 아니요. 기본 스레드는 직렬 대기열이지만 모든 직렬 대기열이 기본 스레드는 아닙니다. 네 번째 지점에서 주 스레드는 차단되어 다른 스레드가 작업을 완료 할 때까지 기다립니다. 직렬 대기열이 주 스레드 인 경우 교착 상태가 발생합니다.
Stephen Darlington

122

여기에 내가 나 다음에 대해 이해하기 위해 수행 한 실험의 커플 serial, concurrent와 대기열 Grand Central Dispatch.

 func doLongAsyncTaskInSerialQueue() {

   let serialQueue = DispatchQueue(label: "com.queue.Serial")
      for i in 1...5 {
        serialQueue.async {

            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

작업은 GCD에서 비동기를 사용할 때 다른 스레드 (메인 스레드 제외)에서 실행됩니다. 비동기는 블록이 실행될 때까지 기다리지 않고 다음 줄을 실행하여 주 스레드 및 주 대기열을 차단하지 않음을 의미합니다. 직렬 대기열이기 때문에 모든 것은 직렬 대기열에 추가 된 순서대로 실행되며 직렬로 실행되는 작업은 항상 대기열과 관련된 단일 스레드에 의해 한 번에 하나씩 실행됩니다.

func doLongSyncTaskInSerialQueue() {
    let serialQueue = DispatchQueue(label: "com.queue.Serial")
    for i in 1...5 {
        serialQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

GCD에서 동기화를 사용하면 작업이 기본 스레드에서 실행될 수 있습니다. Sync는 주어진 큐에서 블록을 실행하고 이것이 완료 될 때까지 기다립니다. 이로 인해 메인 스레드 또는 메인 큐가 차단됩니다. 메인 큐는 디스패치 된 블록이 완료 될 때까지 기다려야하기 때문에 메인 스레드는 다른 큐의 블록을 처리하는 데 사용할 수 있습니다. 따라서 백그라운드 큐에서 실행되는 코드가 실제로 주 스레드에서 실행될 가능성이 있습니다. 직렬 큐이므로 모두 추가 된 순서 (FIFO)에 따라 실행됩니다.

func doLongASyncTaskInConcurrentQueue() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.async {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executing")
    }
}

GCD에서 비동기를 사용하면 작업이 백그라운드 스레드에서 실행됩니다. 비동기는 블록이 실행될 때까지 기다리지 않고 다음 행을 실행하여 주 스레드를 차단하지 않음을 의미합니다. 동시 대기열에서 작업은 대기열에 추가 된 순서대로 처리되지만 대기열에 다른 스레드가 연결되어 있습니다. 대기열에 추가되는 순서대로 작업을 완료해서는 안되며, 작업 순서는 스레드가 자동으로 생성 될 때마다 달라지며 작업은 병렬로 실행됩니다. 그 이상 (maxConcurrentOperationCount)에 도달하면 일부 작업은 스레드가 해제 될 때까지 직렬로 동작합니다.

func doLongSyncTaskInConcurrentQueue() {
  let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executed")
    }
}

GCD에서 동기화를 사용하면 작업이 기본 스레드에서 실행될 수 있습니다. Sync는 주어진 큐에서 블록을 실행하고 그것이 완료 될 때까지 기다립니다. 이로 인해 메인 스레드 또는 메인 큐가 차단됩니다. 메인 큐는 디스패치 된 블록이 완료 될 때까지 기다려야하기 때문에 메인 스레드는 다른 큐의 블록을 처리하는 데 사용할 수 있습니다. 따라서 백그라운드 큐에서 실행되는 코드가 실제로 기본 스레드에서 실행될 가능성이 있습니다. 동시 대기열이므로 작업이 대기열에 추가 된 순서대로 완료되지 않을 수 있습니다. 그러나 동기 작업에서는 서로 다른 스레드에서 처리 될 수 있지만 그렇게합니다. 따라서 이것은 직렬 대기열처럼 작동합니다.

다음은 이러한 실험에 대한 요약입니다.

GCD를 사용하는 것은 대기열에 작업을 추가하고 해당 대기열에서 작업을 수행하는 것입니다. 큐는 작업이 동기인지 비동기인지에 따라 기본 또는 백그라운드 스레드에서 작업을 전달합니다. 대기열 유형은 직렬, 동시, 기본 디스패치 대기열입니다. 수행하는 모든 작업은 기본적으로 기본 디스패치 대기열에서 수행됩니다. 응용 프로그램에서 사용할 사전 정의 된 전역 동시 대기열이 이미 4 개 있고 기본 대기열 (DispatchQueue.main)이 하나 있습니다. 수동으로 자신의 대기열을 만들고 해당 대기열에서 작업을 수행 할 수도 있습니다.

UI 관련 작업은 항상 메인 스레드에서 메인 큐로 작업을 디스패치하여 수행해야합니다. 짧은 손 유틸리티는 DispatchQueue.main.sync/async반면 네트워크 관련 / 무거운 작업은 메인 또는 백그라운드를 사용하는 스레드에 관계없이 항상 비동기 적으로 수행되어야합니다.

편집 : 그러나 UI를 고정하지 않고 백그라운드 스레드에서 동기식으로 네트워크 호출 작업을 수행해야하는 경우가 있습니다 (예 : OAuth 토큰을 새로 고치고 성공 여부를 기다림). 비동기 작업 내부에 해당 메서드를 래핑해야합니다. 작업은 Blocking main thread없이 순서대로 실행됩니다.

func doMultipleSyncTaskWithinAsynchronousOperation() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    concurrentQueue.async {
        let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
        for i in 1...5 {
            concurrentQueue.sync {
                let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
                let _ = try! Data(contentsOf: imgURL)
                print("\(i) completed downloading")
            }
            print("\(i) executed")
        }
    }
}

편집 편집 : 여기에서 데모 비디오를 볼 수 있습니다 .


훌륭한 데모 .... 다음 줄은 블록이 실행될 때까지 기다리지 않고 메인 스레드를 차단하지 않습니다. 이것이 바로 백그라운드 스레드에서 중단 점을 사용하면 }그 순간에 실제로 실행되지 않기 때문에 점프하는 이유입니다
Honey

@That lazy iOS Guy 웃 나는 여전히 비동기 동시와 비동기 직렬의 차이점을 이해하지 못한다. 둘 중 하나를 사용하는 의미는 무엇입니까? 둘 다 UI를 방해하지 않고 백그라운드에서 실행됩니다. 그리고 왜 동기화를 사용합니까? 모든 코드가 동기화되는 것은 아닙니다. 차례로?
eonist

1
@GitSyncApp 당신은 여기
Anish Parajuli 웃

@ 저 게으른 iOS Guy 웃 : 그거 만들어 주셔서 감사합니다. 나는 slack swift-lang에 게시했습니다. DispatchGroup 및 DispatchWorkItem에 대해서도 만들 수 있다면 👌 일 것입니다. : D
eonist

마지막 기능인 concurrentQueue.syncof doLongSyncTaskInConcurrentQueue()함수를 테스트 했는데 메인 스레드를 인쇄 Task will run in different thread하지만 사실이 아닌 것 같습니다.
gabbler

54

첫째, 스레드와 큐의 차이와 GCD가 실제로하는 일을 아는 것이 중요합니다. 디스패치 큐 (GCD를 통해)를 사용할 때 우리는 스레딩이 아니라 실제로 큐잉합니다. Dispatch 프레임 워크는 우리가 스레딩에서 벗어날 수 있도록 특별히 설계되었습니다. Apple은 "올바른 스레딩 솔루션을 구현하는 것이 [때로는] 불가능하지는 않더라도 매우 어려울 수있다"고 인정합니다. 따라서 작업을 동시에 수행하려면 (UI 고정을 원하지 않는 작업) 해당 작업의 대기열을 만들어 GCD에 전달하기 만하면됩니다. 그리고 GCD는 관련된 모든 스레딩을 처리합니다. 따라서 우리가 실제로하는 일은 대기열에있는 것입니다.

즉시 알아야 할 두 번째 것은 작업이 무엇인지입니다. 태스크는 큐 블록 내의 모든 코드입니다 (큐에는 항상 항목을 추가 할 수 있기 때문에 큐 내에는 없지만 큐에 추가 한 클로저 내에 있음). 태스크는 때때로 블록이라고도하며 블록은 태스크라고도합니다 (그러나 특히 Swift 커뮤니티에서는 태스크로 더 일반적으로 알려져 있습니다). 그리고 코드가 아무리 많든 적든 상관없이 중괄호 안의 모든 코드는 단일 작업으로 간주됩니다.

serialQueue.async {
    // this is one task
    // it can be any number of lines with any number of methods
}
serialQueue.async {
    // this is another task added to the same queue
    // this queue now has two tasks
}

그리고 동시성은 단순히 다른 것들과 동시에를 의미하고 직렬은 차례로 (동시에 결코) 의미하지 않는다는 것을 분명히 언급하고 있습니다. 무언가를 직렬화하거나 직렬화한다는 것은 처음부터 끝까지 왼쪽에서 오른쪽으로, 위에서 아래로, 중단없이 실행하는 것을 의미합니다.

두 가지 유형의 대기열 (직렬 및 동시)이 있지만 모든 대기열은 서로 상대적으로 동시 입니다. "백그라운드에서"코드를 실행하려는 것은 다른 스레드 (일반적으로 주 스레드)와 동시에 실행하려는 것을 의미합니다. 따라서 모든 디스패치 대기열 (직렬 또는 동시) 은 다른 대기열에 비해 작업을 동시에 실행합니다 . 대기열 (직렬 대기열에 의해)에 의해 수행되는 모든 직렬화는 해당 단일 [직렬] 디스패치 대기열 내의 작업과 만 관련이 있습니다 (동일한 직렬 대기열 내에 두 개의 작업이있는 위의 예에서와 같이, 이러한 작업은 순차적으로 실행됩니다). 다른 하나, 절대 동시에).

SERIAL QUEUES- 대기열 (종종 개인 디스패치 대기열이라고 함)은 특정 대기열에 추가 된 순서대로 처음부터 끝까지 한 번에 하나씩 작업 실행을 보장합니다. 이것은 디스패치 큐에 대한 논의에서 직렬화를 보장하는 유일한 방법입니다. 특정 직렬 대기열 내의 특정 작업이 직렬로 실행됩니다. 그러나 직렬 대기열은 별도의 대기열 인 경우 다른 직렬 대기열과 동시에 실행될 수 있습니다. 다시 말하면 모든 대기열이 서로에 대해 동시에 이루어지기 때문입니다. 모든 작업은 별개의 스레드에서 실행되지만 모든 작업이 동일한 스레드에서 실행되는 것은 아닙니다 (중요하지는 않지만 알아두면 흥미 롭습니다). iOS 프레임 워크에는 바로 사용할 수있는 직렬 대기열이 제공되지 않으므로 만들어야합니다. 비공개 (비 글로벌) 대기열은 기본적으로 직렬이므로 직렬 대기열을 만들려면 다음을 수행하십시오.

let serialQueue = DispatchQueue(label: "serial")

속성 속성을 통해 동시에 만들 수 있습니다.

let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])

그러나이 시점에서 비공개 대기열에 다른 속성을 추가하지 않는 경우 Apple은 바로 사용할 수있는 글로벌 대기열 중 하나 (모두 동시)를 사용하는 것이 좋습니다. 이 답변의 맨 아래에는 직렬 대기열을 생성하는 다른 방법 (대상 속성 사용)이 표시되며, 이는 Apple이 권장하는 방법입니다 (보다 효율적인 리소스 관리를 위해). 그러나 지금은 라벨링만으로 충분합니다.

병행 QUEUES 동시에 작업을 실행할 수 있습니다 (주로 글로벌 파견 큐라고도 함); 그러나 작업은 특정 대기열에 추가 된 순서대로 시작 되도록 보장 되지만 직렬 대기열과 달리 대기열은 두 번째 작업을 시작하기 전에 첫 번째 작업이 완료 될 때까지 기다리지 않습니다. 작업 (직렬 대기열과 마찬가지로)은 별개의 스레드에서 실행되며 (직렬 대기열과 마찬가지로) 모든 작업이 동일한 스레드에서 실행되는 것은 아닙니다 (중요하지는 않지만 알아두면 흥미 롭습니다). 그리고 iOS 프레임 워크에는 즉시 사용 가능한 4 개의 동시 대기열이 제공됩니다. 위의 예를 사용하거나 Apple의 전역 대기열 (일반적으로 권장 됨) 중 하나를 사용하여 동시 대기열을 만들 수 있습니다.

let concurrentQueue = DispatchQueue.global(qos: .default)

RETAIN-CYCLE RESISTANT : 디스패치 큐는 참조 횟수 개체이지만 글로벌 큐이므로 유지 및 해제가 무시되므로 글로벌 큐를 유지 및 해제 할 필요가 없습니다. 속성에 할당하지 않고도 전역 대기열에 직접 액세스 할 수 있습니다.

큐를 디스패치하는 방법에는 동기식과 비동기식의 두 가지가 있습니다.

SYNC DISPATCHING 은 큐가 디스패치 된 스레드 (호출 스레드)가 큐를 디스패치 한 후 일시 중지하고 다시 시작하기 전에 해당 큐 블록의 작업 실행이 완료되기를 기다린다는 것을 의미합니다. 동 기적으로 디스패치하려면 :

DispatchQueue.global(qos: .default).sync {
    // task goes in here
}

ASYNC DISPATCHING 은 호출 스레드가 큐를 디스패치 한 후에도 계속 실행되고 해당 큐 블록의 작업이 실행을 완료 할 때까지 기다리지 않음을 의미합니다. 비동기 적으로 디스패치하려면 :

DispatchQueue.global(qos: .default).async {
    // task goes in here
}

이제 직렬로 작업을 실행하려면 직렬 대기열을 사용해야한다고 생각할 수 있습니다. 이는 정확히 맞지 않습니다. 여러 작업을 직렬 로 실행하려면 직렬 대기열을 사용해야하지만 모든 작업 (자체 분리)은 직렬로 실행됩니다. 이 예를 고려하십시오.

whichQueueShouldIUse.syncOrAsync {
    for i in 1...10 {
        print(i)
    }
    for i in 1...10 {
        print(i + 100)
    }
    for i in 1...10 {
        print(i + 1000)
    }
}

이 대기열을 구성 (직렬 또는 동시) 또는 디스패치 (동기 또는 비동기) 방식에 관계없이이 작업은 항상 직렬로 실행됩니다. 세 번째 루프는 두 번째 루프 전에 실행되지 않으며 두 번째 루프는 첫 번째 루프 전에 실행되지 않습니다. 이는 디스패치를 ​​사용하는 모든 대기열에 해당됩니다. 직렬 및 동시성이 실제로 작용하는 여러 작업 및 / 또는 대기열을 도입 할 때입니다.

다음 두 대기열 (직렬 하나와 동시 대기열 하나)을 고려하십시오.

let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)

비동기식으로 두 개의 동시 대기열을 발송한다고 가정 해 보겠습니다.

concurrentQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
103
3
104
4
105
5

그들의 출력은 (예상대로) 뒤죽박죽이지만 각 대기열이 자체 작업을 직렬로 실행했음을 알 수 있습니다. 이것은 동시성의 가장 기본적인 예입니다. 동일한 대기열의 백그라운드에서 동시에 실행되는 두 작업입니다. 이제 첫 번째를 직렬로 만들어 보겠습니다.

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

101
1
2
102
3
103
4
104
5
105

첫 번째 큐는 직렬로 실행되어야하지 않습니까? 그것은 (그리고 두 번째도 마찬가지였습니다). 백그라운드에서 발생하는 모든 일은 대기열에 문제가되지 않습니다. 우리는 직렬 대기열에 직렬로 실행하라고 말했고 실행했습니다 ...하지만 하나의 작업 만 제공했습니다. 이제 두 가지 작업을하겠습니다.

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

그리고 이것은 직렬화의 가장 기본적인 (그리고 가능한 유일한) 예입니다. 동일한 대기열에서 백그라운드 (주 스레드로)에서 직렬로 (하나씩) 실행되는 두 작업입니다. 그러나 두 개의 개별 직렬 대기열을 만들면 (위의 예에서는 동일한 대기열이므로) 출력이 다시 뒤죽박죽이됩니다.

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue2.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
3
103
4
104
5
105

이것이 제가 모든 대기열이 서로에 대해 동시 적이라고 말했을 때 의미하는 바입니다. 이들은 작업을 동시에 실행하는 두 개의 직렬 대기열입니다 (별도의 대기열이기 때문에). 대기열은 다른 대기열을 모르거나 신경 쓰지 않습니다. 이제 동일한 대기열의 두 개의 직렬 대기열로 돌아가서 동시에 세 번째 대기열을 추가합니다.

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 1000)
    }
}

1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005

그것은 예상치 못한 일입니다. 동시 대기열이 직렬 대기열이 실행되기 전에 완료되기를 기다린 이유는 무엇입니까? 그것은 동시성이 아닙니다. 귀하의 놀이터가 다른 출력을 표시 할 수 있지만 내 것이 이것을 보여주었습니다. 그리고 그것은 내 동시 대기열의 우선 순위가 GCD가 작업을 더 빨리 실행하기에 충분하지 않았기 때문에 이것을 보여주었습니다. 따라서 모든 것을 동일하게 유지하지만 전역 대기열의 QoS (단순히 대기열의 우선 순위 수준 인 서비스 품질) let concurrentQueue = DispatchQueue.global(qos: .userInteractive)를 변경하면 출력이 예상대로 표시됩니다.

1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105

두 개의 직렬 대기열은 작업을 직렬로 (예상대로) 실행했으며 동시 대기열은 높은 우선 순위 수준 (높은 QoS 또는 서비스 품질)이 주어 졌기 때문에 작업을 더 빨리 실행했습니다.

첫 번째 인쇄 예제와 같이 두 개의 동시 대기열은 예상대로 뒤죽박죽 된 인쇄물을 보여줍니다. 그것들이 직렬로 깔끔하게 인쇄되도록하려면 둘 다 동일한 직렬 대기열 (동일한 레이블이 아니라 해당 대기열의 동일한 인스턴스) 을 만들어야합니다 . 그런 다음 각 작업은 서로에 대해 직렬로 실행됩니다. 그러나 직렬로 인쇄하는 또 다른 방법은 둘을 동시에 유지하면서 디스패치 방법을 변경하는 것입니다.

concurrentQueue.sync {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

동기화 디스 패칭은 진행하기 전에 대기열의 작업이 완료 될 때까지 호출 스레드가 대기한다는 것을 의미합니다. 여기서주의 할 점은 첫 번째 작업이 완료 될 때까지 호출 스레드가 고정된다는 것입니다. 이는 UI가 수행하기를 원하는 방식 일 수도 있고 아닐 수도 있습니다.

이러한 이유로 우리는 다음을 수행 할 수 없습니다.

DispatchQueue.main.sync { ... }

이것은 우리가 수행 할 수없는 유일한 가능한 큐와 디스 패칭 메소드의 조합입니다. 메인 큐에 대한 동기 디스패치입니다. 그리고 그것은 우리가 중괄호 안에있는 작업을 실행할 때까지 메인 큐에 고정하도록 요청하기 때문입니다. 우리는 메인 큐로 디스패치했습니다. 이것을 교착 상태라고합니다. 놀이터에서 작동하는 모습을 보려면 :

DispatchQueue.main.sync { // stop the main queue and wait for the following to finish
    print("hello world") // this will never execute on the main queue because we just stopped it
}
// deadlock

마지막으로 언급 할 것은 리소스입니다. 대기열에 작업을 제공하면 GCD는 내부적으로 관리되는 풀에서 사용 가능한 대기열을 찾습니다. 이 답변을 작성하는 한 qos 당 64 개의 대기열을 사용할 수 있습니다. 많은 것처럼 보이지만 특히 타사 라이브러리, 특히 데이터베이스 프레임 워크에서 빠르게 사용할 수 있습니다. 이러한 이유로 Apple은 대기열 관리에 대한 권장 사항을 제공합니다 (아래 링크 참조). 하나 :

비공개 동시 대기열을 만드는 대신 전역 동시 디스패치 대기열 중 하나에 작업을 제출합니다. 직렬 작업의 경우 직렬 대기열의 대상을 전역 동시 대기열 중 하나로 설정합니다. 이렇게하면 스레드를 생성하는 개별 큐의 수를 최소화하면서 큐의 직렬화 된 동작을 유지할 수 있습니다.

이를 위해 이전과 같이 생성하는 대신 (여전히 가능) Apple은 다음과 같은 직렬 대기열을 생성 할 것을 권장합니다.

let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))

자세한 내용은 다음을 권장합니다.

https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1

https://developer.apple.com/documentation/dispatch/dispatchqueue


7

내가 제대로 GCD의 작동 방식에 대한 이해한다면, 나는 두 가지 유형이 있다고 생각 DispatchQueue, serial그리고 concurrent동시에, 어떻게 두 가지 방법이 있습니다, DispatchQueue그 작업 할당 파견 closure, 첫 번째는 async, 다른 IS를 sync. 그것들은 함께 클로저 (작업)가 실제로 어떻게 실행되는지를 결정합니다.

그 발견 serialconcurrent큐가 사용할 수있는 평균 얼마나 많은 스레드, serial반면, 수단 하나를 concurrent의미 많은. 그리고 syncasync작업이있는 스레드에서 실행됩니다 의미, 발신자의 스레드 또는 큐를 기본 스레드, sync반면 수단은 호출자의 스레드에서 실행 async수단이 기본 스레드에서 실행됩니다.

다음은 Xcode 플레이 그라운드에서 실행할 수있는 실험용 코드입니다.

PlaygroundPage.current.needsIndefiniteExecution = true
let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent)
let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent)
let sq = DispatchQueue(label: "serial.queue")

func codeFragment() {
  print("code Fragment begin")
  print("Task Thread:\(Thread.current.description)")
  let imgURL = URL(string: "http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")!
  let _ = try! Data(contentsOf: imgURL)
  print("code Fragment completed")
}

func serialQueueSync() { sq.sync { codeFragment() } }
func serialQueueAsync() { sq.async { codeFragment() } }
func concurrentQueueSync() { cq2.sync { codeFragment() } }
func concurrentQueueAsync() { cq2.async { codeFragment() } }

func tasksExecution() {
  (1...5).forEach { (_) in
    /// Using an concurrent queue to simulate concurent task executions.
    cq.async {
      print("Caller Thread:\(Thread.current.description)")
      /// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue.
      //serialQueueAsync()
      /// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers' threads.
      //serialQueueSync()
      /// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads
      //concurrentQueueAsync()
      /// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers' thread
      //concurrentQueueSync()
    }
  }
}
tasksExecution()

도움이되기를 바랍니다.


7

이 은유를 사용하여 이것을 생각하고 싶습니다 ( 원본 이미지에 대한 링크 가 있습니다).

아빠는 도움이 필요해

아빠가 설거지를하고 있고 방금 탄산 음료 한 잔을 마셨다 고 가정 해 봅시다. 당신은 그것을 청소하기 위해 유리 잔을 아빠에게 가져다가 다른 접시 옆에 놓는다.

이제 아버지가 혼자서 설거지를하고 있으므로 하나씩해야합니다. 여기서 아버지는 연속 대기열을 나타냅니다. . .

그러나 당신은 거기 서서 청소하는 것을 보는 데별로 관심이 없습니다. 그래서 당신은 유리 잔을 떨어 뜨리고 방으로 돌아갑니다. 이것을 비동기 디스패치 라고합니다. . 당신의 아버지는 그가 끝나면 당신에게 알려줄 수도 있고 말하지 않을 수도 있지만 중요한 것은 당신이 유리가 깨끗해질 때까지 기다리지 않는다는 것입니다. 당신은 당신의 방으로 돌아가서 꼬마 물건을 해요.

이제 당신이 여전히 목이 마르고 당신이 가장 좋아하는 유리 잔에 물을 조금 마시고 싶다고 가정 해 봅시다. 그래서, 당신은 거기 서서 당신의 요리가 끝날 때까지 아버지가 요리하는 것을 지켜보십시오. 이것은 동기화 디스패치입니다.작업이 완료되기를 기다리는 동안 차단 입니다.

마지막으로 엄마가 아빠를 돕고 함께 설거지를한다고 가정 해 봅시다. 이제 대기열은 동시에 여러 접시를 청소할 수 있으므로 동시 대기열이됩니다 . 그러나 그들이 일하는 방식에 관계없이 여전히 거기에서 기다리거나 방으로 돌아갈 수 있습니다.

도움이 되었기를 바랍니다


3

1. 작업을 차례로 실행하기 위해 직렬 대기열이 생성되고 사용된다는 것을 읽고 있습니다. 그러나 다음과 같은 경우에는 어떻게됩니까? • 직렬 대기열을 생성합니다. • dispatch_async (방금 생성 한 직렬 대기열에서)를 세 번 사용하여 3 개의 블록 A, B, C를 디스패치합니다.

답변 :-세 블록 모두 차례로 실행됩니다. 이해하는 데 도움이되는 하나의 샘플 코드를 만들었습니다.

let serialQueue = DispatchQueue(label: "SampleSerialQueue")
//Block first
serialQueue.async {
    for i in 1...10{
        print("Serial - First operation",i)
    }
}

//Block second
serialQueue.async {
    for i in 1...10{
        print("Serial - Second operation",i)
    }
}
//Block Third
serialQueue.async {
    for i in 1...10{
        print("Serial - Third operation",i)
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.