[英]Why calling `DispatchQueue.main.sync` asynchronously from concurrent queue succeeds but synchronously fails?
[英]DispatchQueue Sync Concurrent
好吧,我已經經歷了大量的問題和答案,我知道對它的理解,但是當我嘗試一些代碼時,我得到了一些不符合這些理論的結果。
到目前為止我的理解是:
這是看起來不錯的部分。 現在是棘手的部分
例如-
let syncQ = DispatchQueue(label:"xyz") // by default it is serial
syncQ.sync{
for _ in 0...10{
print("ABC")
}
}
syncQ.sync{
for _ in 0...10{
print("XYZ")
}
}
預期 Output:ABC * 10,XYZ * 10 這很好。
現在我介紹並發連載Q的時候,output也是一樣的。 所以我的問題是並發隊列說任務將同時或並發完成,但它並沒有發生。
例如-
let syncConc = DispatchQueue(label:"con",attributes:.concurrent)
syncConc.sync{
for _ in 0...10{
print("XYZ")
}
for _ in 0...10{
print("ABC")
}
}
syncConc.sync{
for _ in 0...10{
print("HHH")
}
for _ in 0...10{
print("XXX")
}
}
Output: XYZ *10,ABC*10, HHH*10, XXX*10
所以看起來同步並發隊列,就像串行隊列一樣,並且進行並發操作的唯一方法是如果我們在操作之間拋出一個異步隊列。 因此,我無法理解並發類型的串行隊列的目的是什么。
如果有人可以提供編碼示例,將不勝感激,因為我已經知道它的理論和工作原理。 非常感激。
實際上,在您的代碼中,當您在並發隊列(第二個片段)中執行任務時,您正在通過同步塊分派任務,因此這將根據同步行為阻止當前線程。
“同步:一旦塊內的所有任務將被執行,控制就會返回。”
let syncConc = DispatchQueue(label:"con",attributes:.concurrent)
syncConc.sync{
for _ in 0...10{
print("XYZ")
}
for _ in 0...10{
print("ABC")
}
}
syncConc.sync{
for _ in 0...10{
print("HHH")
}
for _ in 0...10{
print("XXX")
}
}
所以,在這種情況下,suncConc 隊列首先會調度第一個同步塊,現在由於它正在阻塞調用,下一個任務不會立即被調度,它會被調度,一旦第一個完成,然后它就會在 suncConc 隊列中調度並再次執行阻塞調用。
現在,讓我來回答您的問題
“現在當我引入並發串行 Q 時,輸出是相同的。所以我的問題是並發隊列說任務將在同一時間或並發完成,它不會發生。”
是的,同步操作也可以同時執行,但只有當您立即分派兩個調用而不阻塞當前線程時才有可能。 檢查以下代碼段,兩個同步任務從不同的隊列分派,因此將並發執行。
let syncConc = DispatchQueue(label:"con",attributes:.concurrent)
DispatchQueue.global(qos: .utility).async {
syncConc.sync{
for _ in 0...10{
print("XYZ - \(Thread.current)")
}
for _ in 0...10{
print("ABC - \(Thread.current)")
}
}
}
DispatchQueue.global(qos: .userInitiated).async {
syncConc.sync{
for _ in 0...10{
print("HHH - \(Thread.current)")
}
for _ in 0...10{
print("XXX - \(Thread.current)")
}
}
}
執行代碼並查看所有理論將按預期應用的魔法:)
問題是您混淆了隊列類型和執行模型。
有串行和並發隊列,您可以同步或異步地將任務分派給這兩種類型。
隊列可以是:
我們可以將任務提交到隊列:
總結一下:
一些術語觀察:
術語“同步”和“異步”決定了調用者的行為。 它控制調用線程是否等待分派的項目。
術語“串行”和“並發”定義了您要分派到的隊列的行為。 它決定隊列是否可以同時在不同的工作線程上運行兩個單獨分派的項目。
重要的是不要混淆這些術語。 同步/異步決定調用線程的行為。 串行/並發決定了調度隊列的行為。
除此之外,還有一些觀察:
考慮:
queue.sync { for _ in 0...10 { print("ABC") } } // because above is `sync`, calling thread won't even get here until the // code dispatched above is finished; it is irrelevant whether the queue // is serial or concurrent. queue.sync { for _ in 0...10 { print("XYZ") } }
在這種情況下, queue
是串行隊列還是並發隊列並不重要:因為您使用了sync
,所以在ABC
行全部完成之前它不會到達任何XYZ
行。
如果要查看並發行為,則必須使用async
而不是sync
。 如果您希望它們同時運行,您不希望調用者在提交第二個之前等待第一個已分派的項目完成。 您希望它提交一個工作項而不是在提交第二個之前等待。
考慮:
let serialQueue = DispatchQueue(label: "serial") let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent) serialQueue.async { // A } serialQueue.async { // B } concurrentQueue.async { // C } concurrentQueue.async { // D }
這是使用 Instruments 時間軸中的“興趣點”工具繪制的這四項任務(每項旋轉一秒鍾)的圖表:
因為我們使用的是async
而不是sync
,所以您現在可以看到並發隊列同時運行 C 和 D,而串行隊列依次運行 A 和 B。
雖然不是我觀察的中心,但為了完整起見,這里是上面使用的實際代碼:
import os.signpost
func gcdExperiment() {
let pointsOfInterest = OSLog(subsystem: "log", category: .pointsOfInterest)
let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
serialQueue.async {
pointsOfInterest.interval(name: "serial", label: "A") {
self.spin(seconds: 1)
}
}
serialQueue.async {
pointsOfInterest.interval(name: "serial", label: "B") {
self.spin(seconds: 1)
}
}
concurrentQueue.async {
pointsOfInterest.interval(name: "concurrent", label: "C") {
self.spin(seconds: 1)
}
}
concurrentQueue.async {
pointsOfInterest.interval(name: "concurrent", label: "D") {
self.spin(seconds: 1)
}
}
}
func spin(seconds delay: TimeInterval) {
let start = CACurrentMediaTime()
while CACurrentMediaTime() - start < delay { }
}
這是OSLog
擴展,用於在 Instruments 的“興趣點”工具中顯示間隔:
extension OSLog {
func interval(name: StaticString, label: String, endLabel: String = "", block: () -> Void) {
let id = OSSignpostID(log: self)
os_signpost(.begin, log: self, name: name, signpostID: id, "%s", label)
block()
os_signpost(.end, log: self, name: name, signpostID: id, "%s", endLabel)
}
}
為了能夠看到串行/並發隊列之間和同步/異步方法之間的區別以及它們如何被分派到隊列中,嘗試在您的 Playground 上播放下一個片段。
import PlaygroundSupport
import Dispatch
PlaygroundPage.current.needsIndefiniteExecution = true
let q = DispatchQueue(label: "concurrect queue", qos: .background, attributes: .concurrent)
func runner0(queue: DispatchQueue) {
for _ in 1..<10 {
let result = queue.sync { ()->Int in
usleep(100)
return 0
}
DispatchQueue.main.async {
print(result, "-")
}
}
}
func runner1(queue: DispatchQueue) {
for _ in 1..<10 {
let result = queue.sync { ()->Int in
usleep(100)
return 1
}
DispatchQueue.main.async {
print("-", result)
}
}
}
let arr = [runner0, runner1]
DispatchQueue.concurrentPerform(iterations: 2) { (i) in
arr[i](q)
}
我們有一個並發隊列,其中兩個不同的運行器同步分派任務。 如您所見,我們並發隊列中的所有任務都是並發執行的,然后將結果串行(異步)打印在串行隊列上。
我希望,這可以幫助您了解它是如何工作的。
我的游樂場打印
- 1
0 -
- 1
0 -
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
0 -
- 1
0 -
- 1
- 1
讓我們通過更改其定義使隊列成為串行
let q = DispatchQueue(label: "serial queue")
並比較我們得到的結果
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.