libro
www.tuyano.com
初心者のためのObjective-Cプログラミング入門

NSTimerとNSThread (6/6)

作成:2011-03-03 09:17
更新:2011-11-13 22:10

■オブジェクトの排他的処理

複数スレッドを実行するようになると、クローズアップされてくるのが「オブジェクトの競合」という問題です。これは、複数のスレッドがオブジェクトを奪い合う、ということです。

例えば、先の例では、2つのスレッドそれぞれにMyTestClassインスタンスを用意し、その中のメソッドを実行していました。が、これが同じインスタンスを渡したらどうなっていたでしょう。つまり、2つのスレッドで、同じインスタンス内のメソッドが呼び出されていたら?

そのインスタンス内のインスタンス変数などは両方のスレッドからアクセスされ書き換えることになります。片方のスレッドが値を書き換えることで、もう一方のスレッドに影響をあたえることもないとはいえません。

特に厳密な計算が必要となるような処理では、「処理の途中で、別のスレッドがやってきて変数を勝手に書き換えてしまう」なんてことが起こったら大変です。そこで、重要な処理では「排他的処理」を行うことが大切になります。

排他的処理とは、「一報が処理している間、それ以外の処理は行えないようにする」というやり方です。あるオブジェクトがあった場合、そのオブジェクトを利用出来るのは常に1つのスレッドだけ。そのスレッドが使い終わったら、次のスレッドが利用する。そういうやり方です。これは、以下のような構文を使うことで可能になります。
@synchronized(オブジェクト){
    ……実行する処理……
}
この@synchronizedというのは「コンパイラディレクティブ」というものです。これは、その後の{}内にある処理を実行している間、引数に指定したオブジェクトをロックし、自身以外のスレッドからアクセスできないようにする働きがあります。こうすることで、そのオブジェクトが常に1つのスレッドしか使えないようにするわけですね。

では、先程のMyTestClassで、スレッドで実行するprintMessage:を書き換え、スレッド実行中はMyTestClassインスタンスを外部から利用できないようにしてみましょう。そして、main関数では、同じMyTestClassを使って複数のスレッドを実行させてみます。

これを実行してみると、まず最初に1つ目のスレッドの処理が行われ、「end...」が表示されてスレッドが終了してから、2つ目のスレッドの処理が実行されることがわかります。スレッド実行中、MyTestClassインスタンスにもう一方のスレッドがアクセスできず、終わるまでずっと待たされていることがわかりますね。

スレッドの競合を防ぐための処理法は他にもあるのですが、まずはもっとも簡単でわかりやすい@synchronizedだけ覚えておくと、ずいぶんと役に立つでしょう。ただし、これを利用するときには、「どこからどこまで、排他的処理をすべきか」を考えるようにしてください。例えば、このサンプルのようにスレッドで行う処理をまるごと排他処理にしてしまうと、なんのためにマルチスレッドにしたのがわからなくなってしまいますから。

※プログラムリストが表示されない場合

AddBlockなどの広告ブロックツールがONになっていると、プログラムリスト等が表示されない場合があります。これらのツールをOFFにしてみてください。

●プログラム・リスト●

※MyTestClass.m

#import "MyTestClass.h"

@implementation MyTestClass

+(MyTestClass*)myTestClassToEndCount:(int)n {
	MyTestClass* obj = [[MyTestClass alloc] init];
	[obj setEndCount:n];
	return obj;
}
-(void)setEndCount:(int)n {
	endcount = n;
}
-(void)printMessage:(NSString*)s {
	@synchronized(self){
		BOOL flg = YES;
		count = 0;
		while (flg) {
			[NSThread sleepForTimeInterval:1.0];
			NSLog(@"%@:%i",s,++count);
			if (endcount <= count) {
				flg = NO;
				NSLog(@"end...");
			}
		}
	}
}

@end


※main関数の修正

#import <Foundation/Foundation.h>
#import "MyTestClass.h"

int main (int argc, const char * argv[]) {
    @autoreleasepool {
        MyTestClass* obj = [MyTestClass
                            myTestClassToEndCount:5];
        [NSThread detachNewThreadSelector:
         @selector(printMessage:)
                                 toTarget:obj withObject:@"first"];
        [NSThread detachNewThreadSelector:
         @selector(printMessage:)
                                 toTarget:obj withObject:@"second"];
        NSLog(@"start!!");
        
        // 終了しないようにしておく
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

※関連コンテンツ

「初心者のためのObjective-Cプログラミング入門」に戻る