🕑

DispatchQueueのlabelはあくまでただのlabel

2023/01/09に公開約2,900字

めちゃくちゃアタリマエのことを書いておる。

概要

Foundation.DispatchQueueを初期化する際、指定するlabelはあくまでlabelであって、idではない。もし同一のlabelを指定して複数のDispatchQueueを用意してしまってもそれらはlabelが同一の別々のキューが生成される。同一のシリアルキューを使いたい場合、同一のDispatchQueueを保持する必要があり、labelで指定するのではない。

比較して理解したい

2つのコードを示して実験してみる

  • 単一labelでqueueを1つしか生成しない例
    • 意図通りなやり方
  • 単一labelでqueueを2つ生成する例
    • おそらく意図してることができていないやり方

queueを一つしか生成しない例

まず普通の使い方を示す。これは正しい。

1つのDispatchQueue(DispatchQueue(label: label))を用意し、AとBの2つのクロージャを書く例を示す。Aのクロージャは終了を遅くしていて、先にキューに乗ってるのでBが待たされる。

import Foundation

let queue = DispatchQueue(label: "dummy") // ❗差分はここ

DispatchQueue.global().async {
    print("A: before")
    queue.async {
        print("A: begin")
        Thread.sleep(forTimeInterval: 5) // stopを遅らせたい
        print("A: end") // ここがB:beginより先に終わってほしい
    }

    print("B: before")
    queue.async {
        print("B: begin")
        print("B: end 🕑") // 最後に出力されるのはここであってほしい
    }
}

print("Hello, playground") // ここが先に実行されてることを示すため

出力結果はAの終了を待ってB待たされていて想定通り。

A: before
Hello, playground
A: begin
B: before
A: end  🕑
B: begin
B: end

単一labelでqueueを2つ生成する例

次に、想定とは別の本来意図していないであろう使い方をやってしまっている例。

import Foundation

let label = "dummy" // ❗差分はここ

DispatchQueue.global().async {
    print("A: before")
    DispatchQueue(label: label).async {
        print("A: begin")
        Thread.sleep(forTimeInterval: 5) // stopを遅らせたい
        print("A: end 🕑") // ここがB:beginより先に終わってほしい
    }

    print("B: before")
    DispatchQueue(label: label).async {
        print("B: begin")
        print("B: end") // 最後に出力されるのはここであってほしい
    }
}

print("Hello, playground") // ここが先に実行されてることを示すため

出力結果はAが終わる前にBが始まっている。つまり別々のシリアルキューを実行していて、本来やりたいことって単一のキューに乗せたいことだろうから結果は伴っていない。

Hello, playground
A: before
B: before
A: begin
B: begin
B: end
A: end 🕑

データの競合させられるかを検証する

すでに自分で実験済みだった

https://gist.github.com/yimajo/104a13e3e063a9c8773eb9eac44eeb73

ドキュメントを見てみる

convenience init(
    label: String,
    qos: DispatchQoS = .unspecified,
    attributes: DispatchQueue.Attributes = [],
    autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit,
    target: DispatchQueue? = nil
)

https://developer.apple.com/documentation/dispatch/dispatchqueue/2300059-init

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っぽい名前にしたらいいとある

(いや、そこまでしなくても。別アプリと混ざりようがないから)

追記

https://twitter.com/k_katsumi/status/1612416940601114624

This parameter is optional and can be NULL.

これは明らかに間違いな気がする

感想として、もし自分がドキュメントを書くなら重要なのはlabelが重複してもそれは2つのキューができて困るということであり、idとして機能するわけじゃないということなんじゃないだろうか。なので、「labelはあくまで重複チェックされておらず、もし同一labelを付けて初期化を行うと、複数同じlabelがついたqueueが生成される」って書きたい。

Discussion

ログインするとコメントできます