*

iOS(Xcode6とSwift)におけるマルチスレッド(非同期)処理の実装方法その2[GCD(Grand Central Dispatch)の利用]

公開日: : 最終更新日:2016/12/05 Ruby, Swift , ,


スポンサードリンク



「iOS(Xcode6とSwift)におけるマルチスレッド処理の実装方法その1[NSThreadクラスの簡単な利用例]」ではNSThreadクラスを利用したマルチスレッド処理の実装方法の説明をさせていただきました。

本エントリーでは、SwiftでGCD(Grand Central Dispatch)を利用したマルチスレッド(非同期)処理の実装方法の説明を記載させていただきます。

利用しているXcodeは6.1です。
エントリーの内容は以下の通りです。内容の構成として
「iOS(Xcode6とObjective-C)におけるマルチスレッド(非同期)処理の実装方法その2[GCD(Grand Central Dispatch)の利用]」と良く似ていますが、本エントリーではiOS(iPhone)アプリケーションを題材とします。

  1. GCD(Grand Central Dispatch)の概要
  2. サンプルの実装に利用するアプリケーションの説明
  3. シリアルキューとコンカレントキューを利用したマルチスレッド処理
  4. メインディスパッチキューを利用したマルチスレッド処理


1 GCD(Grand Central Dispatch)の概要

GCDの概要は、利用する言語がObjective-CとSwiftでほぼ同じとなりますの以下のエントリーを参照ください。
「Objective-Cを利用する時のGCDの概要」

Swiftになって異なる部分といえば、キューに追加するのがブロック(ブロック構文を利用した処理の固まり)でないことぐらいです。
といってもクロージャーをキューに追加することは可能ですので、結果的に同じと言えるかもしれません。

2 サンプルの実装に利用するアプリケーションの説明

説明といっても前回エントリーで利用したアプリケーションをそのまま利用します。

画面は以下のようになっています。
スクリーンショット 2014-11-16 16.48.52

画面パラメータ1と画面パラメータ2の横にあるテキストボックスの入力値はキューに登録する処理の数等に利用します。開始ボタンが押されるとスレッドを起動するといった感じです。

一番上のラベルは、メインディスパッチキューでUIコンポーネントを更新する時のターゲットにしようと考えています。
UIコンポーネントのoutlet接続と「開始」ボタンをタップした時のイベントハンドラーの登録がされただけのViewController.swiftは以下の通りです。

3 シリアルキューとコンカレントキューを利用したマルチスレッド処理

シリアルキューを利用したスレッド処理

シリアルキューの作成方法

シリアルキューはdispatch_queue_create関数にキューの名前を指定する文字列とDISPATCH_QUEUE_SERIALを引数として呼び出すことで作成可能です。

Objective-Cでは、第二引数にNULLを指定することでシリアルキューが作成可能でしたが、Swiftでは明示的にDISPATCH_QUEUE_SERIALを指定しないとコンパイルエラーとなります。

シリアルキューへの処理の追加方法

キューの追加はdispath_async関数にキューと実行したい処理を指定して呼び出すことで行えます。

シリアルキューの利用例

シリアルキューを作成し、キューに処理を追加する例は以下の通りです。

シリアルキューに追加する処理は、NSLogの出力とsleepを行うだけとなっています。
for文を回す中で、何回forが回ったかの情報を意味する変数をthredFuncに渡して、その呼び出し処理をキューに追加しています。

アプリケーションを実行後にパラメータ1に5を指定して「実行」ボタンをタップしてみました。
スクリーンショット 2014-11-16 21.09.51

実行時のログは以下のようになりました。

全ての行のスレッドIDが1798967となっており、キューに追加された全ての処理が同じスレッドで実行されています。
シリアルキューはワーカースレッドが1つなので、うんうん!、って感じです。
しかし、1行目と2行目は、0番目の・・・となるはずですし、その他の行は全て5番目の・・・となってしまっています。

iは、キューに追加した時の値ではなく、ワーカースレッドに実行される時の値となってしまっています。

ためしにキューに追加する処理をクロージャーに変更してみました。

この変更後に再度実行してみましたが結果は同じでした。
ブロック構文であれば、ブロックの処理が確定したタイミングでiが固定されます。
クロージャーも同じと思ったのですが、この部分に関しては謎ですよね、値型ですので、一般的なOSの実行スタックに命令が積まれた時点でiは固定されるはずです。Objective-Cと仕様が異なることでハマるポイントにならなければ良いのですが・・・

コンカレントキューを利用したスレッド処理

コンカレントキューの作成方法

コンカレントキューを利用する方法は2種類存在しています。この利用方法もObjective-Cと全く同じです。
1つ目は、dispatch_queue_create関数にキューの名前を指定する文字列とDISPATCH_QUEUE_CONCURRENTを引数として呼び出し作成する方法
2つ目は、dispatch_get_global_queue関数に優先度と0(今後の拡張用のための引数)を指定して呼び出すことでで既存のキューを取得する方法
優先度には以下の値が指定可能です。

  • DISPATCH_QUEUE_PRIORITY_HIGH(優先度高)
  • DISPATCH_QUEUE_PRIORITY_DEFAULT(優先度中)
  • DISPATCH_QUEUE_PRIORITY_LOW(優先度低)
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND(バックグラウンド)

当然ですが、既存のキューを利用する方が消費リソース面等で有利であると言えます.

1つ目の方法の例

2つめの方法の例

コンカレントキューへの処理の追加方法

キューの追加の仕方はシリアルキューと同じでdispath_async関数にキューと実際の処理を指定して呼び出すことで行えます。

コンカレントキューの利用例

シリアルキューの例のキュー生成部分をコンカレントキューの生成or取得に変更するだけです。
とは言え、closureの引数のiをNSLogで出力するままではログが分かりにくいので、iをViewControllerのインスタンスメンバとして宣言し、closureの処理の最後にインクリメントするようにします。

変更後のViewController.swiftは以下のようになりました。

実行時のログは以下のようになりました。

うん、うん、予想通りの動きですね。iと各スレッドの対応がきれいにとれていません。

各スレッドに対応する正しいiの値は
2158779が0
2158805が1
2158781が2
2158780が3
2158782が4
となりますが、コンカレントキューを利用したマルチスレッド処理はスレッドセーフでないことを思い出してください。
キューに追加し、実行された各スレッドの共有リソースであるiはこの例ではスレッドセーフとなっていませんので、これが想定される動作となります。

この現象を回避するための手法は色々ありますが、@synchronizedディレクティブでclosureの処理をクリティカルセクション化してみようと思いましたが、
@synchronizedはXcode6.1のSwiftでは利用できないため、objc_sync_enterとobjc_sync_exitを利用します。
@synchronizedとobjc_sync_enterとobjc_sync_exitは仕様的には同じ物です。

変更後のstartActionは以下のようになりました。

変更後の実行結果のログは以下のようになりました。

各スレッドとiの値の整合性が取れていることが確認できます。
しかしこれでは非同期処理の良さが完全になくなっていますね・・・
時間がかかる処理を並行に行えることにマルチスレッドを用いた非同期処理の意味があります。

という事で、iをインクリメントする処理とその1行前のNSLogだけをクリティカルセクションにして
それ以外のNSLogでiを参照しないように変更してみます。

変更後のstartActionは以下のようになりました。

変更後の実行結果ログは以下のようになりました。

意図した通りにiのインクリメントとその前のNSLogだけがクリティカルセクションになりましたし、非同期で実行している意味が感じられるようになりました。
とはいえ、どの共有リソースをどの処理で保護しないといけないかで、実際のクリティカルセクションの範囲を決めるべきです。変更前のようにclosureの大部分がクリティカルセクションになる処理を実装しなければいけないときもありますが、できるだけクリティカルセクションは小さくなるよう留意することはとても重要です。

コンカレントキューとシリアルキューを併用したマルチスレッド処理

コンカレントキューの最後の例は、クリティカルセクションを利用して実現しましたが、
コンカレントキューとシリアルキューを併用することでも実現可能です。

クリティカルセクション化している処理をシリアルキューに追加するだけです。
と言ってもNSLogで出力していた”%d番目のキューの最後の処理”の文言は変更しないとおかしいですので変更します。ってコンカレントキューの例としてもこの表現はおかしいですね・・・

変更後のstartActionは以下のようになりました。

実行後のログは以下のようになりました。

6行目以降は、コンカレントキューに追加された処理の内部からさらにシリアルキューに追加した処理の実行時のログなので全て同じスレッドで動作するはずなのですが、2206087と2206034の2つのIDの行が存在しています。これはシリアルキュー(ワーカースレッドが1つ)の動きと異なります。

うーん、なんだかすんなり行かないですね・・・
シリアルキューの動作が正しいかどうか、コンカレントキューの5個のスレッドからシリアルキューに追加する処理にスリープを入れてみます。
ワーカースレッドが複数動いているか確認できるようにiの値が0の時は10秒、iが1増える毎に1秒スリープする時間を短くします。
これによって複数のワーカースレッドが動作している場合は追い越されるスレッドが発生するはずです。

変更後のstartActionは以下のようになりました。

実行後のログは以下のようになりました。

うーん、困りますね・・・
今度はシリアルキューで実行されるスレッドのIDが2232483だけです。
ログの内容からもワーカースレッドが1つしか動作していないことが確認できました。
NSLogのバグなんでしょうか・・・

4 メインディスパッチキューを利用したマルチスレッド処理

メインディスパッチキューの利用方法

dispatch_get_main_queue関数を呼び出して、既に存在しているメインディスパッチキューを取得します。
キューに処理を追加する方法はシリアルキュー、コンカレントキューと同じです。

メインディスパッチキューを利用してみる

iOSにおけるマルチスレッド処理で注意しなければならないポイントとして、UIコンポーネントの更新はメインスレッドで行う必要があることがあげられます。

iOSアプリケーションでは、利用者が何も操作しなくてもメインスレッドはRun Loopとして常に動作しています。メインディスパッチキューを利用すればメインスレッドに処理を実行させることが簡単にできます。メインディスパッチキューが利用できないバージョンのiOSではNSObjectクラスのperformSelectorOnMainThread:withObject:waitUntilDone:メソッドを利用する必要がありまましたが、メインディスパッチキューが利用できるようになって格段に処理が簡単になりました。

メインディスパッチキューの実際の利用例

開始ボタンが押されるとラベルのテキストに0をセットし、
パラメータ1は、メインディスパッチキューに登録する処理の数、パラメータ2はその登録する処理内で利用する数値を指定します。

キューに登録する処理は、
ラベルのテキストにラベルのテキスト + パラメータ2に指定した値をセットし、パラメータ2の秒数だけスリープするとの内容です。

コードの変更はまたまたstartActionのみとなります。

実際にパラメータ1に1、パラメータ2に5を指定して「実行」ボタンを押し、処理が終わった時の画面表示は以下のようになりました。
スクリーンショット 2014-11-18 22.07.51

実行時のログは以下のようになりました。

「実行」ボタンをタップした後すぐに「実行」ボタンがタップできる状態になり、ラベルに想定通りの値が表示されましたし、ログの内容自体も想定通りです。

次に、実際にパラメータ1に5、パラメータ2に10を指定して「実行」ボタンを押し、処理が終わった時の画面表示は以下のようになりました。
スクリーンショット 2014-11-18 22.13.16

処理終了時の画面はOKなのですが、あいだの表示がうまくいきません。
ラベルに表示される値は0からいきなり50に変わってしまいます。

実行時のログは以下のようになりました。

ログ上ではちゃんと10、20、30、40、50と増えているのですが・・・


スポンサードリンク

Googleアドセンス

Googleアドセンス




関連記事

Xcode6の正式版がリリースされApp Storeからダウンロードしインストール可能になりました。

ついにSwiftが利用できるXcode6の正式版がリリースされました。 ブラウザからMac App

記事を読む

Swift入門[公式リファレンスのチュートリアルのSteps2の前半]

「Swift」と「Xcode 6」を少しずつさわっていこうと思ってはいるのですが、「Xcode 6

記事を読む

Xcode6(Swift)のデリゲートとプロトコルの使い方

Xcode(Objective-C)のデリゲートの使い方では利用言語がObjective-Cの時のデ

記事を読む

動画で英語を学習できるiOSの無料アプリCapTubeをリリースいたしました。

個人では初となるiOSアプリをリリースいたしました。 何度もリジェクトをくらいながら、開発開始

記事を読む

Swift(Xcode6-Beta2)でMagicalRecord関連の処理を含んだDAOクラスのユニットテスト(UnitTest:XCTestフレームワーク)を実装する時にハマった事

SwiftでMagicalRecord関連の処理を含んだDAOクラスを作成し、ユニットテスト(XCT

記事を読む

Swift入門(Xcode6のXCTestフレームワークで学ぶ) 第一回「Swiftの概要」 

Swiftの簡単な説明 アップルのiOS(iOS8以降)およびOS Xのためのプログラミング言語。

記事を読む

Xcode6-Beta2でSwiftのユニットテスト(Unit Test)をXCTestフレームワークで試してみる。

タイトルの通りですが、Xcode6-Beta2でSwiftのユニットテスト(XCTestフレームワー

記事を読む

2014年の秋のAppleのプレスイベントとXcode6-beta-7のインストール方法

2014年の秋のAppleのプレスイベント いよいよAppleは、米国西海岸時間9月9日午前10時

記事を読む

iOS(Xcode6とSwift)におけるマルチスレッド処理の実装方法その1[NSThreadクラスの簡単な利用例]

前回はObjective-CにおけるNSThreadクラスの簡単な利用例を取り扱いましたが今回は、ほ

記事を読む

Xcode6とSwiftでイメージ(画像)やアニメーションを表示する方法

本エントリーでは、Xcode6(言語はSwift)を利用してイメージ(画像)やアニメーションを表示す

記事を読む

Comment

  1. […] 出来た! 参考にさせていただいた記事、感謝 […]

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

動画で英語を学習できるiOSの無料アプリCapTubeをリリースいたしました。

個人では初となるiOSアプリをリリースいたしました。 何度もリジ

no image
Ruby on rails4系でBootstrapを利用するためのtips

MacでRuby on rails4系のBootstrapを利用しよう

no image
Java、Eclipse、JUnit関連のエントリーの移行のお知らせ

Java、Eclipse、JUnit関連のエントリーは http:/

iOS8開発者向けお勧め本紹介[詳細! Swift iPhoneアプリ開発 入門ノート Swift 1.1+Xcode 6.1+iOS 8.1対応]

iOS7開発者向けお勧め本紹介を以前に紹介させていただきまいたが、今回

Swift入門(Xcode6のXCTestフレームワークで学ぶ) 第二回「関数(メソッド)とクロージャーの利用方法」

前回はSwiftの概要をザックリと説明させていただきました。 今

→もっと見る

Optimization WordPress Plugins & Solutions by W3 EDGE
PAGE TOP ↑