첫째, 스레드와 큐의 차이와 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