読者です 読者をやめる 読者になる 読者になる

ゆるい感じのプログラムを書きたい。

プログラムの敷居を下げて、多くの人が開発出来るように色々書いていきます!

【iPhoneアプリ】これを使えるようにならないと「マルチスレッド」について 実装編

今回も、「マルチスレッド」についてです。

前回、概要編との事で記載していましたが

今回は実際にプログラムを書いて、動かしてみる実装編を記載して行きます。

GCD実用例の前準備

GDCを用いた実用例の紹介の前に以下の準備をします。


 ・スレッドテスト開始用ボタンの描画
 ・スレッドテスト開始用ボタンのアクション処理


まず、始めに「スレッドテスト開始用ボタン」を以下のコードを
記述して描画させます。

■ViewController.m

#import "ViewController.h"
@implementation ViewController{
    MultiThread *Multi;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

  Multi = [[MultiThread alloc] init];
    UIButton* bt  = [Multi ThreadButton];
    
    bt.center = self.view.center;
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self.view addSubview:bt];    
}


次に「スレッドテスト開始用ボタン」のアクション処理を
以下のコードを記述して行います。

■MultiThread.h

#import <Foundation/Foundation.h>

@interface MultiThread : NSObject{
    UIButton* bt;
}

- (UIButton*)ThreadButton;

@end

■MultiThread.m

#import "MultiThread.h"

@implementation MultiThread

- (UIButton*)ThreadButton{
    bt = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [bt setTitle:@"テスト" forState:UIControlStateNormal];
    bt.frame = CGRectMake(0,0,200,200);
    [bt addTarget:self action:@selector(thread_Button:) forControlEvents:UIControlEventTouchUpInside];
    return bt;
}

- (IBAction)thread_Button:(UIButton*)sender
{
 //スレッドの処理をこの部分に記載します。
}

@end

GCDの実用例

実用例として以下の項目を記載します。

 ・並列なキューで同期処理(dispatch_sync)
 ・並列なキューで非同期処理(dispatch_async)
 ・直列なキューで同期処理(dispatch_sync)
 ・直列なきユーで非同期処理(dispatch_async)


■並列なキューで同期処理(dispatch_sync)

・並列なキューの為、実行完了する順番は保証されない。
・dispatch_syncの為、別スレッド処理が完了するまで、次のスレッドは処理しない。
・dispatch_syncの為、全体のキューの処理完了を待って、次の処理を行います。


プログラムはこんな感じです。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
 for (NSInteger i = 0 ; i < 100; ++i) {
   dispatch_sync(globalQueue, ^{
     NSLog(@"%d", i);
   });
 }

 NSLog(@"here!!");
}


結果はこんな感じです。

labo[6961:70b] 96
labo[6961:70b] 97
labo[6961:70b] 98
labo[6961:70b] 99
labo[6961:70b] here!!

追加したタスクが実行完了するまで次に進まない為、"here!!"は最後に出力されます。


イメージはこんな感じです。

f:id:kassans:20140314125012p:plain


また、並列なのに順番に処理をしているイメージはこんな感じです。

f:id:kassans:20140314125442p:plain

▼使いどころ
 ・グローバルキューで複数のスレッドで順番通りにタスクを処理させ
  後ほど、処理を行ったタスクをメインキューで使う必要がある場合に使います。


■並列なキューで非同期処理(dispatch_async)

・並列なキューの為、実行の順番は保証されません。
・dispatch_asyncの為、別スレッド完了を待たず、スレッドは処理します。
・dispatch_asyncの為、全体のキューの処理完了を待たず、次の処理を行います。

プログラムはこんな感じです。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
 for (NSInteger i = 0 ; i < 100; ++i) {
   dispatch_async(globalQueue, ^{
     NSLog(@"%d", i);
   });
 }

 NSLog(@"here!!");
}


結果はこんな感じです。

labo[6976:70b] here!!
labo[6976:1303] 0
labo[6976:3807] 1
labo[6976:3a03] 2
labo[6976:3b03] 3

順番に実行され"here!!"が全てのタスクの完了より前に来ています。


イメージはこんな感じです。

f:id:kassans:20140314124553p:plain

▼使いどころ
 ・あるタスクで処理に時間もかかり、その後の処理でこのタスクの結果を
  用いないといったケースで利用します。


■直列なキューで同期処理(dispatch_sync)

 ・直列なキューの為、タスクが同時に一つしか実行しないので競合が起きない。
 ・dispatch_syncの為、全体のキューの終了完了まで次のステップに進みません。

プログラムはこんな感じです。

dispatch_queue_t privateQueue = dispatch_queue_create("jp.test.sample", DISPATCH_QUEUE_SERIAL);

 for (NSInteger i = 0 ; i < 100; ++i) {
   dispatch_sync(globalQueue, ^{
     NSLog(@"%d", i);
   });
 }

 NSLog(@"here!!");
}


結果はこんな感じです。

labo[1812:70b] 96
labo[1812:70b] 97
labo[1812:70b] 98
labo[1812:70b] 99
labo[1812:70b] here!!

出力は順番になっており、追加したタスクが実行完了するまで次に進まない為、
"here!!"は最後に出力されています。


イメージはこんな感じです。

f:id:kassans:20140314125114p:plain

▼使いどころ
 ・ファイルはDBなど不整合が起きうる箇所で、かつその読み込んだ内容を
  次で使う場合に使います


■直列なキューで非同期処理(dispatch_async)

 ・直列なキューの為、タスクが同時に一つしか実行しないので順番が前後しません。
 ・dispatch_asyncの為、全体のキューの処理完了を待たず、次の処理を行います。


プログラムはこんな感じです。

dispatch_queue_t privateQueue = dispatch_queue_create("jp.test.sample", DISPATCH_QUEUE_SERIAL);

 for (NSInteger i = 0 ; i < 100; ++i) {
   dispatch_async(globalQueue, ^{
     NSLog(@"%d", i);
   });
 }

 NSLog(@"here!!");
}


結果はこんな感じです。

labo[1789:70b] here!!
labo[1789:1303] 0
labo[1789:1303] 1
labo[1789:1303] 2
labo[1789:1303] 3

出力は順番になっており、"here!!"が先に表示され、タスクを追加し終わると
次に進んでます。


イメージはこんな感じです。

f:id:kassans:20140314124448p:plain

▼使いどころ
 ・処理内容に一意性が求められる時に使います。
 ・例えばファイルへのアクセスなどが該当します。
 ・ファイルへのアクセスなどは時間がかかるのでasyncで非同期実行する
  といった使い方ができます。

メインスレッドとグローバルスレッドの組み合わせ方

次に、メインスレッドグローバルスレッドの組み合わせを紹介していきます。


以下のように処理分散させたい場合

・メインスレッドで描画処理
・グローバルスレッドで別処理


こんな感じのプログラムを書いてみます。

dispatch_queue_t main = dispatch_get_main_queue();
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

dispatch_async(globalQueue, ^{
        
    int count = 0, i;
    
		//描画に必要な処理1
		for(i = 0; i < 5; i++){
        NSLog(@"HeavyProcessing_1 = %d", count);
        count++;
    }
    
    //メインスレッドで途中を描画
    dispatch_async(main, ^{
         NSLog(@"Main_1 = %d", count);
    });
    
    //描画に必要な処理2
    for(i = 0; i < 5; i++){
        NSLog(@"HeavyProcessing_2 = %d", count);
        count++;
    }
    
    //メインスレッドで描画完了
    dispatch_async(main, ^{
         NSLog(@"Main_2 = %d", count);
    });

});
	 //通常の処理
   NSLog(@"here!!");


結果はこんな感じです。

labo[1969:70b] here!!
labo[1969:1303] HeavyProcessing_1 = 0
labo[1969:1303] HeavyProcessing_1 = 1
labo[1969:1303] HeavyProcessing_1 = 2
labo[1969:1303] HeavyProcessing_1 = 3
labo[1969:1303] HeavyProcessing_1 = 4
labo[1969:70b] Main_1 = 5
labo[1969:1303] HeavyProcessing_2 = 5
labo[1969:1303] HeavyProcessing_2 = 6
labo[1969:1303] HeavyProcessing_2 = 7
labo[1969:1303] HeavyProcessing_2 = 8
labo[1969:1303] HeavyProcessing_2 = 9
labo[1969:70b] Main_2 = 10

グローバルスレッドがasync(非同期処理)のため、先に通常処理が行われ
次にグローバルスレッドの処理が行われています。

グローバルキューでは、重い処理を行った後にメインスレッド
呼ばれている事が分かります。



このため、グローバルスレッド「重たい処理」メインスレッド「描画処理」
分散処理の実装が可能となります。