DispatchQueueのlabelはあくまでただのlabel
めちゃくちゃアタリマエのことを書いておる。
概要
Foundation.DispatchQueueを初期化する際、指定するlabelはあくまでlabelであって、idではない。もし同一のlabelを指定して複数のDispatchQueueを用意してしまってもそれらはlabelが同一の別々のキューが生成される。同一のシリアルキューを使いたい場合、同一のDispatchQueueを保持する必要があり、labelで指定するのではない。
比較して理解したい
2つのコードを示して実験してみる
- 単一labelでqueueを1つしか生成しない例
- 意図通りなやり方
- 単一labelでqueueを2つ生成する例
- おそらく意図してることができていないやり方
実行環境について
実行環境はXcode 14.2のiOS Appからプロジェクトを作成してみます。
なぜ上記を明記したかというと、Playgroundで試すと結果が違うためです。
queueを一つしか生成しない例
まず普通の使い方を示す。これは正しい。
1つのDispatchQueue(DispatchQueue(label: label)
)を用意し、AとBの2つのクロージャを書く例を示す。Aのクロージャは終了を遅くしていて、先にキューに乗ってるのでBが待たされる。
let queue = DispatchQueue(label: "dummy") // ❗差分はここ
DispatchQueue.global().async {
print("A: before")
queue.async {
print("A: begin")
print("A: thread \(Thread.current)") // 実行スレッド
Thread.sleep(forTimeInterval: 5) // stopを遅らせたい
print("A: end") // ここがB:beginより先に終わってほしい
}
print("B: before")
queue.async {
print("B: begin")
print("B: thread \(Thread.current)") // 実行スレッド
print("B: end 🕑") // 最後に出力されるのはここであってほしい
}
}
print("Hello, Xcode Project") // ここが先に実行されてることを示すため
出力結果は下記の通り。
Hello, Xcode Project
A: before
B: before
A: begin
A: thread <NSThread: 0x6000013f5240>{number = 6, name = (null)}
A: end
B: begin
B: thread <NSThread: 0x6000013f5240>{number = 6, name = (null)}
B: end 🕑
-
Hello ...
が最初に出力されていて想定通り -
A: before
のあとにすぐB: before
が続くのも想定通り -
A: thread
とB: thread
を出力してみると同じスレッドnumberが使われていて想定通り -
A: end
がB: begin
より先に終わっているのも想定通り- 1つのキューなので、Bは開始自体を送らされている
単一labelでqueueを2つ生成してしまう例
次に、想定とは別の本来意図していないであろう使い方をやってしまっている例。
import Foundation
let label = "dummy" // ❗差分はここ
DispatchQueue.global().async {
print("A: before")
DispatchQueue(label: label).async {
print("A: begin")
print("A: thread \(Thread.current)") // 実行スレッド
Thread.sleep(forTimeInterval: 5) // stopを遅らせたい
print("A: end 🕑") // ここは最後に出力される
}
print("B: before")
DispatchQueue(label: label).async {
print("B: begin")
print("B: thread \(Thread.current)") // 実行スレッド
print("B: end") // 最後に出力されるのはここであってほしいがそうならない
}
}
print("Hello, Xcode Project") // ここが先に実行されてることを示すため
出力結果は下記の通り。
Hello, Xcode Project
A: before
B: before
B: begin
A: begin
A: thread <NSThread: 0x600002c0cf00>{number = 5, name = (null)}
B: thread <NSThread: 0x600002c07900>{number = 7, name = (null)}
B: end
A: end 🕑
-
A: thread
とB: thread
を出力してみると違うスレッドのnumberなのがわかる
Aが終わる前にBが始まっている。つまり別々のシリアルキューを実行していて、本来やりたいことって単一のキューに乗せたいことだろうから結果は伴っていない。
データの競合させられるかを検証する
すでに自分で実験済みだった
ドキュメントを見てみる
convenience init(
label: String,
qos: DispatchQoS = .unspecified,
attributes: DispatchQueue.Attributes = [],
autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit,
target: DispatchQueue? = nil
)
labelについて、分かった上で読んだらそんなに説明がわかりづらくはない。
A string label to attach to the queue to uniquely identify it in debugging tools such as Instruments, sample, stackshots, and crash reports.
Instrumentsなんかのデバッキングツール、サンプル、スタックショット(?)、クラッシュレポートに使われる。
Because applications, libraries, and frameworks can all create their own dispatch queues, a reverse-DNS naming style (com.example.myqueue) is recommended.
おすすめとしてはリバースDNSっぽい名前にしたらいいとある
(いや、そこまでしなくても。別アプリと混ざりようがないから)
追記
DNSについて
labelのはなし
This parameter is optional and can be NULL.
このドキュメントのoptionlは明らかに間違いな気がする
感想として、もし自分がドキュメントを書くなら重要なのはlabelが重複してもそれは2つのキューができて困るということであり、idとして機能するわけじゃないということなんじゃないだろうか。なので、「labelはあくまで重複チェックされておらず、もし同一labelを付けて初期化を行うと、複数同じlabelがついたqueueが生成される」って書きたい。
Playgroudの実行結果のはなし
Playgroundで試してたら結果がおかしなことになっていたのを指摘していただきました。
Discussion