Mobile,Jasmine,JavaScript,Java,テスト駆動開発等)など扱っています。マルチスレッド処理スポンサードリンク「iOS(Xcode6とSwift)におけるマルチスレッド処理の実装方法その1[NSThreadクラスの簡単な利用例]」ではNSThreadクラスを利用したマルチスレッド処理の実装方法の説明をさせていただきました。Dispatch)を利用したマルチスレッド(非同期)処理の実装方法の説明を記載させていただきます。利用しているXcodeは6.1です。エントリーの内容は以下の通りです。Dispatch)の利用]」と良く似ていますが、本エントリーではiOS(iPhone)アプリケーションを題材とします。Dispatch)の概要GCDの概要は、利用する言語がObjective-CとSwiftでほぼ同じとなりますの以下のエントリーを参照ください。「Objective-Cを利用する時のGCDの概要」Swiftになって異なる部分といえば、キューに追加するのがブロック(ブロック構文を利用した処理の固まり)でないことぐらいです。いってもクロージャーをキューに追加することは可能ですので、結果的に同じと言えるかもしれません。説明といっても前回エントリーで利用したアプリケーションをそのまま利用します。画面は以下のようになっています。画面パラメータ1と画面パラメータ2の横にあるテキストボックスの入力値はキューに登録する処理の数等に利用します。開始ボタンが押されるとスレッドを起動するといった感じです。上のラベルは、メインディスパッチキューでUIコンポーネントを更新する時のターゲットにしようと考えています。UIコンポーネントのoutlet接続と「開始」ボタンをタップした時のイベントハンドラーの登録がされただけのViewController.swiftは以下の通りです。シリアルキューとコンカレントキューを利用したマルチスレッド処理シリアルキューを利用したスレッド処理シリアルキューの作成方法シリアルキューはdispatch_queue_create関数にキューの名前を指定する文字列とDISPATCH_QUEUE_SERIALを引数として呼び出すことで作成可能です。DISPATCH_QUEUE_SERIAL)Objective-Cでは、第二引数にNULLを指定することでシリアルキューが作成可能でしたが、Swiftでは明示的にDISPATCH_QUEUE_SERIALを指定しないとコンパイルエラーとなります。シリアルキューへの処理の追加方法キューの追加はdispath_async関数にキューと実行したい処理を指定して呼び出すことで行えます。})シリアルキューの利用例シリアルキューを作成し、キューに処理を追加する例は以下の通りです。{self.thredFunc(i)})}}シリアルキューに追加する処理は、NSLogの出力とsleepを行うだけとなっています。for文を回す中で、何回forが回ったかの情報を意味する変数をthredFuncに渡して、その呼び出し処理をキューに追加しています。アプリケーションを実行後にパラメータ1に5を指定して「実行」ボタンをタップしてみました。実行時のログは以下のようになりました。全ての行のスレッドIDが1798967となっており、キューに追加された全ての処理が同じスレッドで実行されています。シリアルキューはワーカースレッドが1つなので、うんうん!、って感じです。1行目と2行目は、0番目の・・・となるはずですし、その他の行は全て5番目の・・・となってしまっています。iは、キューに追加した時の値ではなく、ワーカースレッドに実行される時の値となってしまっています。ためしにキューに追加する処理をクロージャーに変更してみました。{closure(i)})}}この変更後に再度実行してみましたが結果は同じでした。ブロック構文であれば、ブロックの処理が確定したタイミングでiが固定されます。クロージャーも同じと思ったのですが、この部分に関しては謎ですよね、値型ですので、一般的なOSの実行スタックに命令が積まれた時点でiは固定されるはずです。Objective-Cと仕様が異なることでハマるポイントにならなければ良いのですが・・・コンカレントキューを利用したスレッド処理コンカレントキューの作成方法コンカレントキューを利用する方法は2種類存在しています。この利用方法もObjective-Cと全く同じです。優先度には以下の値が指定可能です。0);コンカレントキューへの処理の追加方法キューの追加の仕方はシリアルキューと同じでdispath_async関数にキューと実際の処理を指定して呼び出すことで行えます。コンカレントキューの利用例シリアルキューの例のキュー生成部分をコンカレントキューの生成or取得に変更するだけです。言え、closureの引数のiをNSLogで出力するままではログが分かりにくいので、iをViewControllerのインスタンスメンバとして宣言し、closureの処理の最後にインクリメントするようにします。変更後のViewController.swiftは以下のようになりました。{closure()})}}}実行時のログは以下のようになりました。4番目のキューの最後の処理うん、うん、予想通りの動きですね。iと各スレッドの対応がきれいにとれていません。各スレッドに対応する正しいiの値は2158779が02158805が12158781が22158780が32158782が4となりますが、コンカレントキューを利用したマルチスレッド処理はスレッドセーフでないことを思い出してください。キューに追加し、実行された各スレッドの共有リソースであるiはこの例ではスレッドセーフとなっていませんので、これが想定される動作となります。この現象を回避するための手法は色々ありますが、@synchronizedディレクティブでclosureの処理をクリティカルセクション化してみようと思いましたが、@synchronizedはXcode6.1のSwiftでは利用できないため、objc_sync_enterとobjc_sync_exitを利用します。@synchronizedとobjc_sync_enterとobjc_sync_exitは仕様的には同じ物です。変更後のstartActionは以下のようになりました。{closure()})}}変更後の実行結果のログは以下のようになりました。各スレッドとiの値の整合性が取れていることが確認できます。時間がかかる処理を並行に行えることにマルチスレッドを用いた非同期処理の意味があります。いう事で、iをインクリメントする処理とその1行前のNSLogだけをクリティカルセクションにしてそれ以外のNSLogでiを参照しないように変更してみます。変更後のstartActionは以下のようになりました。{closure()})}}変更後の実行結果ログは以下のようになりました。意図した通りにiのインクリメントとその前のNSLogだけがクリティカルセクションになりましたし、非同期で実行している意味が感じられるようになりました。いえ、どの共有リソースをどの処理で保護しないといけないかで、実際のクリティカルセクションの範囲を決めるべきです。変更前のようにclosureの大部分がクリティカルセクションになる処理を実装しなければいけないときもありますが、できるだけクリティカルセクションは小さくなるよう留意することはとても重要です。コンカレントキューとシリアルキューを併用したマルチスレッド処理コンカレントキューの最後の例は、クリティカルセクションを利用して実現しましたが、コンカレントキューとシリアルキューを併用することでも実現可能です。クリティカルセクション化している処理をシリアルキューに追加するだけです。言ってもNSLogで出力していた”%d番目のキューの最後の処理”の文言は変更しないとおかしいですので変更します。変更後のstartActionは以下のようになりました。//シリアルキューに処理を追加することでクリティカルセクションと同じ効果が得られる。sleepSec)sleep(sleepSec)//シリアルキューに処理を追加することでクリティカルセクションと同じ効果が得られる。{closure()})}}実行後のログは以下のようになりました。iの値=46行目以降は、コンカレントキューに追加された処理の内部からさらにシリアルキューに追加した処理の実行時のログなので全て同じスレッドで動作するはずなのですが、2206087と2206034の2つのIDの行が存在しています。これはシリアルキュー(ワーカースレッドが1つ)の動きと異なります。うーん、なんだかすんなり行かないですね・・・シリアルキューの動作が正しいかどうか、コンカレントキューの5個のスレッドからシリアルキューに追加する処理にスリープを入れてみます。ワーカースレッドが複数動いているか確認できるようにiの値が0の時は10秒、iが1増える毎に1秒スリープする時間を短くします。これによって複数のワーカースレッドが動作している場合は追い越されるスレッドが発生するはずです。変更後のstartActionは以下のようになりました。//シリアルキューに処理を追加することでクリティカルセクションと同じ効果が得られる。sleepSec)sleep(sleepSec)//シリアルキューに処理を追加することでクリティカルセクションと同じ効果が得られる。{closure()})}}実行後のログは以下のようになりました。今度はシリアルキューで実行されるスレッドのIDが2232483だけです。ログの内容からもワーカースレッドが1つしか動作していないことが確認できました。メインディスパッチキューを利用したマルチスレッド処理メインディスパッチキューの利用方法dispatch_get_main_queue関数を呼び出して、既に存在しているメインディスパッチキューを取得します。キューに処理を追加する方法はシリアルキュー、コンカレントキューと同じです。メインディスパッチキューを利用してみるiOSにおけるマルチスレッド処理で注意しなければならないポイントとして、UIコンポーネントの更新はメインスレッドで行う必要があることがあげられます。Loopとして常に動作しています。メインディスパッチキューを利用すればメインスレッドに処理を実行させることが簡単にできます。メインディスパッチキューが利用できないバージョンのiOSではNSObjectクラスのperformSelectorOnMainThread:withObject:waitUntilDone:メソッドを利用する必要がありまましたが、メインディスパッチキューが利用できるようになって格段に処理が簡単になりました。開始ボタンが押されるとラベルのテキストに0をセットし、パラメータ1は、メインディスパッチキューに登録する処理の数、パラメータ2はその登録する処理内で利用する数値を指定します。パラメータ2に指定した値をセットし、パラメータ2の秒数だけスリープするとの内容です。コードの変更はまたまたstartActionのみとなります。{closure()})}}実際にパラメータ1に1、パラメータ2に5を指定して「実行」ボタンを押し、処理が終わった時の画面表示は以下のようになりました。実行時のログは以下のようになりました。パラメータ2の秒数=5スリープ「実行」ボタンをタップした後すぐに「実行」ボタンがタップできる状態になり、ラベルに想定通りの値が表示されましたし、ログの内容自体も想定通りです。次に、実際にパラメータ1に5、パラメータ2に10を指定して「実行」ボタンを押し、処理が終わった時の画面表示は以下のようになりました。処理終了時の画面はOKなのですが、あいだの表示がうまくいきません。ラベルに表示される値は0からいきなり50に変わってしまいます。実行時のログは以下のようになりました。Xのためのプログラミング言語。前回は、「Xcode5&Objective-CでUITableViewでUINibを使ったカ記事を読むSwift入門[公式リファレンスのチュートリアルのSteps2の後半]「Swift入門」では、Steps2の前半まで説明させていただきました。記事を読む動画で英語を学習できるiOSの無料アプリCapTubeをリリースいたしました。個人では初となるiOSアプリをリリースいたしました。コメントをキャンセルメールアドレスが公開されることはありません。ENTRY動画で英語を学習できるiOSの無料アプリCapTubeをリリースいたしました。個人では初となるiOSアプリをリリースいたしました。前回はSwiftの概要をザックリと説明させていただきました。Mobile,Jasmine,JavaScript,Java,テスト駆動開発等)など扱っています。