もうちょっとだけ続くんじゃ。
ディスパッチキュー
並列処理を書くもう一つの方法が、ディスパッチキューを使う方法。
といっても、内容はオペレーションキューを使うのとほぼ一緒。
基本的にはディスパッチキューにブロック(Swiftの場合、クロージャ)を追加すれば、その処理を(必要かつ可能なら)並列に処理してくれる。
ただ、ちょっと違うのは、
- インタフェースがオブジェクトのメソッド呼び出しではなく、関数呼び出しである。
- 処理間の依存関係は管理せず、追加された順に処理を開始する。
- 直列ディスパッチキューと並列ディスパッチキューという2種類がある。
ということ。
直列ディスパッチキューと並列ディスパッチキュー
ディスパッチキューには、直列ディスパッチキューと並列ディスパッチキューがある。
種類 | 説明 |
---|---|
直列ディスパッチキュー | 処理を追加された順に実行する。 キューに追加された処理で同時に実行されるものは、最大1つ。 |
並列ディスパッチキュー | 処理を追加された順に実行する。 キューに追加された処理は、可能なら複数のものが並列して行われる。 |
なお、複数の直列ディスパッチキューがあった場合、それぞれに追加された処理は並列して動く場合もある。
(同時に動くことがないことが保証されているのは、同じ直列キューに追加された処理のみ、ということ)
このことから、排他制御が必要なリソースへのアクセスを行う処理は直列ディスパッチキューへ追加し、並列して行いたい処理は並列ディスパッチキューへ追加するといい。
なお、メインスレッドに関連付けられたディスパッチキューとして、メインディスパッチキューがある。
メインディスパッチキューは直列ディスパッチキューになっている。
ディスパッチキューの生成と取得
ディスパッチキューを生成するには、dispatch_queue_create()
を使う。
// 直列ディスパッチキューを生成 let serialQueue = dispatch_queue_create("com.hoge.app.serial", DISPATCH_QUEUE_SERIAL) // 並列ディスパッチキューを生成 let concurrentQueue = dispatch_queue_create("com.hoge.app.concurrent", DISPATCH_QUEUE_CONCURRENT)
第1引数に指定しているのはディスパッチキューのラベルで、逆DNSのスタイルが推奨されている。
なお、並列ディスパッチキューはアプリのどこからでも使えるもの(グローバルな並列ディスパッチキュー)があらかじめ4つ用意されているので、それを取得して使うこともできる。
グローバルな並列ディスパッチキューを取得するには、dispatch_get_global_queue()
を使う。
// 第1引数には、次のいずれかの優先度を指定する。 // - DISPATCH_QUEUE_PRIORITY_HIGH // - DISPATCH_QUEUE_PRIORITY_DEFAULT // - DISPATCH_QUEUE_PRIORITY_LOW // - DISPATCH_QUEUE_PRIORITY_BACKGROUND // 第2引数は将来の拡張用で、今は常に0を指定する。 let defaultGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
また、メインディスパッチキューはdispatch_get_main_queue()
で取得できる。
// メインディスパッチキューの取得 let mainQueue = dispatch_get_main_queue()
処理の追加
ディスパッチキューに処理を追加するには、dispatch_async()
を使う。
dispatch_async(処理を追加するディスパッチキュー, 追加する処理のブロック)
// Swiftなら接尾クロージャを使うことも出来る
dispatch_async(処理を追加するディスパッチキュー) {
追加する処理の内容
}
こうやって追加すると、ディスパッチキューが処理をよろしく実行してくれる。
なお、dispatch_async()
を使うと、処理をディスパッチキューに追加したら制御はすぐに呼び出し元に戻ってくるけれど、ディスパッチキューに処理を追加して、その処理が終わるまで呼び出し元に制御を戻したくない場合には、dispatch_sync()
を使うといい。(呼び出し方はdispatch_async()
と同じ)
処理の終了を待つ
処理の終了を待ちたい場合には、ディスパッチグループを使う。
ディスパッチグループを生成するには、次のようにする。
let group = dispatch_group_create()
そして、処理を追加するときに、dispatch_async()
の代わりにdispatch_group_async()
を使う。
dispatch_group_async(group, 処理を追加するディスパッチキュー, 追加する処理のブロック)
あとは、dispatch_group_wait()
を使うことで、同じディスパッチグループとしてディスパッチキューに追加された処理が全部終わるまで、待つことが出来る。
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
なお、第2引数でDISPATCH_TIME_FOREVER
を指定するとずっと待ってくれるし、時間を指定するとタイムアウトするとのこと。
(※ただ、実際にはDISPATCH_TIME_FOREVER
以外だと上手く動かなかった)
その他
繰り返し処理の並列化
for文で繰り返し処理を行う場合、それぞれの繰り返しが独立していて、かつ、1回の処理が重い場合、それぞれの処理を並列して行うといい。
例えば、次のような感じ。
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) for i in 0..<count { dispatch_async(queue) { // 繰り返す処理 } }
これを簡単に書くために、dispatch_apply()
という関数が用意されている。
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) dispatch_apply(count, queue) { i in // 繰り返す処理 }
そんなに変わらないじゃんとか、たぶん言っちゃいけないw
まぁ、自分で使うことはないと思うけど、使われてるコードで困るといけないので、頭の片隅に置いておいた方がいいかもしれない。
セマフォ
排他制御は、普通は直列ディスパッチキューを使えば問題ないのだけど、一応セマフォも用意されている。
排他制御が必要な複数リソースへのアクセスを行う場合に、そのリソースの獲得順番が決まっている場合には、セマフォを使った方が分かりやすいのかも。
(※排他制御が必要なリソースが複数ある場合、そのアクセス順を決めて守らないと、デッドロックに陥る危険性がある)
セマフォを生成するには、dispatch_semaphore_create()
を使う。
// 第1引数で、利用可能なリソース数の初期値を指定する let semaphore = dispatch_semaphore_create(1)
セマフォを獲得するには、dispatch_semaphore_wait()
を使う。
// セマフォを獲得しようとする。 // 利用可能なリソースがない場合、セマフォは獲得できず、 // 利用可能なリソースが増えるまで待ち状態になる。 // 利用可能なリソースがある場合、セマフォを獲得でき、 // 利用可能なリソース数が1減る。 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
セマフォを返すには、dispatch_semaphore_signal()
を使う。
// セマフォを返す。 // 利用可能なリソース数を1増やす。 dispatch_semaphore_signal(semaphore)
なお、セマフォには排他制御以外にも使い方があるけど、基本的にはディスパッチキューを使えば問題ないはず。
今日はここまで!