>100 Views
May 25, 13
スライド概要
Objective-C の @property でおなじみの atomic について、iphone_dev_jp 東京 iPhone/Mac 勉強会で発表しました。
atomic でどんな作用が期待できるのか。それがスレッドセーフになり得るのか。スレッドセーフを実現するための手法とは。今回はそんな、マルチスレッド周りのお話です。
※ Docswell での公開に移行する直前の Slideshare での閲覧数は 2,084 でした。
正統派趣味人プログラマー。プログラミングとは幼馴染です。
Objective-C - atomicity 〜 誰もが知ってるかもしれない最初の話 〜 EZ-NET 熊⾕友宏 @EasyStyleGK http://program.station.ez-net.jp/
今回は、ここのお話
⾃⼰紹介 EZ-NET 熊⾕友宏 http://program.station.ez-net.jp/ @EasyStyleGK iOS アプリ 制作中 EZ-NET IP Phone ⾳で再配達ゴッド ⾳で再配達 ⾳でダイヤル いつもの電卓 for iPad いつもの電卓 for iPhone
はじまり ⽬次 1. nonatomic ってなんだろう 2. なぜ今頃 atomic の話題なのか 3. @property (atomic)って何をしてくれるの︖ 4. 値が壊れる︖ 5. スレッドセーフを考慮する
第1章 nonatomic って なんだろう
nonatomic と⾔えば 1. プロパティ定義で使うキーワード 2. atomic と nonatomic とがある 3. 省略時は atomic になる そのプロパティが原⼦性を 保証するかを⽰すキーワード
原⼦性とは これ以上分解できない単位
つまり atomic なプロパティとは そのプロパティの処理を ひとまとまりとして実⾏する ということ︖
つまり atomic なプロパティとは つまりスレッドセーフ ということ︖ 必ずしもそうとは限らない
第2章 なぜ今頃 atomic の話題なのか
それは無計画さが招いた課題 ⾃作アプリの複数スレッドを跨ぐ処理が いよいよ制御しきれなくなったため︕ 1. EXC_BAD_ACCESS で落ちる 2. 計算中に結果を取得される 3. 状態に基づく処理中に状態が変わる スレッドセーフを考えなくてはいけない
スレッドセーフってなんだろう 1. 他スレッドで使⽤中のインスタンスが 解放されないようにする 2. 複数スレッドからのアクセス時に、 値に⽭盾が出ない事を保証する 3. ⼀連の処理をブロックして、⽭盾のな い処理を保証する など、いろいろ
スレッドセーフというのは 複数スレッドを使った平⾏処理を ⽭盾しないように制御する考え⽅ atomic はそのうちのひとつ
第3章 @property (atomic) って 何をしてくれるの?
プロパティに atomic を指定したとき 原⼦性をコンパイラが ⾃動で保証してくれるの︖ それとも⾃分で原⼦性を 保証しなければいけないの︖
@synthesize であれば ある程度は⾃動で保証してくれる このあたりの感覚は @property (copy) と同じ
@synthesize での atomic 制御 プリミティブ型の場合 1. インスタンス変数に書き込んでいる間、 他からの読み書きはブロックされる 読み書きする値が 正しいことを保証する
@synthesize での atomic 制御 オブジェクト型の場合 1. インスタンス変数に書き込んでいる間、 他からの読み書きはブロックされる 2. ゲッターでは、ブロックの中でインスタ ンスを retain して autorelease する 値の正確性と合わせて インスタンスの⽣存を保証する
@synthesize での atomic 制御 @synthesize で保証されるのは 該当する ivar の整合性だけ インスタンス全体の 整合性は保証されない
オブジェクト全体の整合性を保証したいなら メソッドでの処理も考慮した インスタンス全体の整合性を保つ制御が必要 A. 同時実⾏されたくないもの同⼠をロック B. クラスを Immutable で設計する C. インスタンスを扱うスレッドを統⼀する こういったことに配慮しながら クラスを設計する必要がある
第4章 値が壊れる?
同時アクセスされることで case 1: クラス全体の整合性が 崩れることは想像に易しい
クラス全体の整合性破壊 インスタンス { } スレッド 1 スレッド 2 int _a; int _b; [obj setA:3 B:5]; 8 [1] a ⇦ 3 - (void)setA:(int)a B:(int)b { [2] b ⇦ 5 _a = a; _b = b; [obj setA:1 B:3]; } 4 [3] a ⇦ 1 - (int)total [4 { [5] b ⇦ 3 ] return _a + _b; } [obj total]; ⏎1+5 6 ?
同時アクセスされることで case 2: @synthesize なプロパティが 壊れるってどういうこと︖
プロパティの整合性破壊︖
インスタンス
{
}
スレッド 1
スレッド 2
long long _val;
obj.val = 1;
- (void)setVal:(long long)val
1
[1] val ⇦ 1
{
_val = val;
}
- (long long)val
-1 { [2] val ⇦ -1
return _val;
}
obj.val = -1
obj.val;
[3
]
⏎ -1
!?
4294967295
プロパティの整合性破壊… プログラムの1⾏が 実⾏時の1ステップではない
プロパティの整合性破壊︕
インスタンス
{
}
スレッド 1
スレッド 2
long long _val;
- (void)setVal:(long long)val
1
[1] 下位ビット
{
_val = val;
[2] 上位ビット
}
- (long long)val
{
-1
下位ビット
[3]
return
_val;
}
[6] 上位ビット
obj.val = 1;
obj.val = -1
obj.val;
[4] 下位ビット
[5] 上位ビット
!
4294967295
同時アクセスされることで つまり プリミティブ型だって壊れる もちろん構造体も壊れる
同時アクセスされることで case 3: インスタンスが 解放されることも
インスタンスの予期しない解放 インスタンス スレッド 1 @interface MyClass { NSString* _string; } スレッド 2 string = [obj.string retain]; @property (nonatomic,readwrite,rcopy) NSString* string; [1] ⏎ _string @end obj.string = ssss; [2] [_string release] @implementation MyClass = [ssss copy] [4] _stringstring @synthesize = _string; @end [3] [string retain] Bad Access !
平⾏処理は危険がいっぱい スレッドセーフってとっても⼤事
第5章 スレッドセーフを考慮する
おさらい 同時アクセスが引き起こす不都合 A. プリミティブ型のプロパティが扱うデー タが壊れる B. プロパティから取得したオブジェクトが 予期せず解放される C. インスタンスへの同時アクセスにより その整合性が崩れる
スレッドセーフの実現⽅法 同時アクセスによる 不都合からプログラムを守るために A. atomic と @synthesize を使⽤する B. 返すインスタンスは確実に retain する C. 関係する範囲を把握して 不整合が起こらないようにロックする D. 実⾏するスレッドをひとつに統⼀する E. クラスを Immutable で設計する
同時アクセスからの保護 実例をいくつか
ブロックの⽅法 Objective-C で使えるロックの紹介 a) @synchronized (self) ― 続くブロック {} を再帰ロック ― 指定したインスタンスがキーになる b) NSRecursiveLock ― -lock から -unlock までを再帰ロック ― pthread_mutex の Objective-C 版 c) セマフォ ― dispatch_semaphore でロック ― タイムアウトの指定も可能
実例 case A: 読み書きでの 値の破壊を防ぐ
読み書きでの値の破壊を防ぐ プリミティブ型の @property (atomic) を @synthesize したときに採られる⽅法 1. セッターとゲッターを 同じキーでロックする 同時アクセスを防ぎ 値の⽭盾を起こさない
atomic キーワードを使⽤する
インスタンス
スレッド 1
@interface MyObject : NSObject
{
long long _val;
Lock!
}
スレッド 2
obj.val = 1;
1
[1] 下位ビット
@property (atomic,readwrite) long long val;
[1] 上位ビット
@end
Lock!
obj.val = -1
-1 @implementation
[2] 下位ビット MyObject
[2] 上位ビット
@synthesize val =Lock!
_val;
@end
[3] 下位ビット
[3] 上位ビット
obj.val;
-1
OK!
内部的な実装は次のような感じに Setter Getter - (void)setVal:(long long)val { [_lock lock]; - (long long)val { _val = val; } [_lock unlock]; } [_lock lock]; @try { return _val; } @finally { [_lock unlock]; } @synthesize で⽣成される内部ロックは @synchronized (self) とはまったく別のもの
実例 case B: インスタンスを 確実に retain する
インスタンスを確実に retain する オブジェクト型の @property (atomic) を @synthesize したときに採られる⽅法 1. セッターとゲッターを 同じキーでロックする 2. ゲッターのロック内でインスタンス を retain & autorelease する インスタンスを確実に確保して 呼び出し元が正しく受け取れるようにする
インスタンスを確実に retain する インスタンス スレッド 1 @interface MyClass スレッド 2 { NSString* _string; Lock! string = [obj.string retain]; } [1] [_string retain] KEEP! @property (atomic,readwrite,copy) NSString* string; [1] [_string autorelease] @end [1] ⏎ _string @implementation MyClass Lock! obj.string = ssss; release] [2] [_string @synthesize string = _string; [2] _string = [ssss retain] @end OK! [3] [string retain]
内部的な実装は次のような感じに Setter Getter - (void)setString: (NSString*)string { [_lock lock]; - (NSString*)string { [_lock lock]; _string = [string copy]; } [_lock unlock]; } @try { [_string retain]; [_string autorelease]; return _string; } @finally { [_lock unlock]; } 親インスタンスの dealloc で _string が 解放されるところまでは保護されない
実例 case C: 関係する範囲をロックして 不整合が起きないようにする
関係する範囲をロックして不整合を防ぐ オブジェクト全体の 整合性を保護する⽅法 1. 同時アクセスされたくない部分を 同じキーでロックする 2. プロパティも含めて保護するときは @synthesize は使わない 計算処理と結果取得を同じキーでロックすれば 計算途中の結果取得を防⽌できる
整合性に関係する範囲をロックする インスタンス スレッド 1 - (void)setA:(int)a B:(int)b スレッド 2 { @synchronized (self) Lock! { [obj setA:3 B:5]; _a = a; 8 [1] a ⇦ 3 _b = b; } [1] b ⇦ 5 } -Lock! (int)total [obj setA:1 B:3]; { 4 a⇦1 [2] @synchronized (self) [obj total]; { [2] b ⇦ 3 return _aLock! + _b; } [3 ⏎ 1 + 3 4 } ] OK!
実例 case D: 実⾏するスレッドを ひとつに統⼀する
実⾏するスレッドをひとつに統⼀する iOS の UI 制御でも採られている⽅法 1. ある⼀連の機能を必ず同じスレッド で実⾏するようにする 2. 実⾏スレッドではランループが必要 同時アクセスを起こさせないため ロックやデータの⽭盾を気にしなくて済む
実⾏するスレッドをひとつに統⼀する
インスタンス
@interface MyClass
{
NSString* _string;
}
スレッド 1
スレッド 2
string = [obj.string retain];
[1] ⏎ _string
@property (nonatomic,readwrite,copy) NSString* string;
[obj performSelector:@selector(setString:)
onThread:スレッド2
withObject:ssss
@implementation MyClass
waitUntilDone:NO];
[2] [string retain]
No Lock.
@synthesize string = _string;
[3] [_string release]
@end
@end
[4] _string = [ssss copy]
OK
!
実例 case E: クラスを Immutable で設計する
クラスを Immutable で設計する インスタンス⽣成後に 値を変更できないようにする⽅法 1. インスタンス⽣成後に ivar の値を編集 できないクラスを作る 2. 値の設定はインスタンス⽣成のときだけ 値が「変化する途中」が存在しないため 同時アクセスで値が⽭盾しない
クラスを Immutable で設計する Immutable クラスの特徴 内部でのロックなく 複数スレッドで使⽤可能 ただしインスタンスの受け渡し時には 原⼦性を保護する必要がある
クラスを Immutable で設計する クラス定義 @interface MyClass : NSObject @property (nonatomic,readonly) long long a; @property (nonatomic,readonly) long long b; @property (nonatomic,readonly) long long total; - (id)initWithA:(long long)a B:(long long)b; @end 値は init メソッドで設定して インスタンス⽣成後は値の取得だけができる
クラスを Immutable で設計する クラス実装 @implementation MyClass - (id)initWithA:(long long)a B:(long long)b { self = [super init]; if (self) { _a = a; _b = b; } return self; } - (long long)total { return _a + _b; }
クラスを Immutable で設計する
インスタンス
スレッド 1
@interface TestClass
スレッド 2
{
MyClass* _obj;
test.obj = [[MyClass alloc] initWithA:3 B:5];
Lock!
}
8
[1] _obj = [obj retain]
@property (atomic,readwrite,retain) MyClass* obj;
Lock!
test.obj = [[MyClass alloc] initWithA:1 B:3];
4 @end
[2] _obj = [obj retain]
Lock!
@implementation MyClass
[3] [_obj retain]
[test.obj total];
OK!
@synthesize obj = _obj;
[3] [_obj autorelease]
@end
[3] ⏎ _obj
4
[2] [test.obj total]
他のケース 他にもこんなところで 配慮されていたり
1.予期しないインスタンスの解放を防ぐ performSelector:onThread:withObject :... によるインスタンスの⽣存保証 1. 引数 withObject に渡されたインスタン スが渡された時点で retain される 2. 別スレッドでの実⾏が終わると、インス タンスは release される 他のスレッドで実⾏されるまでの間に インスタンスが解放されないようにする
2.予期しない値の変化を防ぐ 渡された NSString を 複製してインスタンス変数に持つ⽅法 1. NSString のインスタンスは編集可能な 場合がある (NSMutableString) 2. そこで copy して、渡されたものとは 別の NSString を持つようにする 複製を内部に持つ事で 外部での値変更の影響を受けない
スレッドセーフ このような配慮で 複数スレッドに対応させている
あとは宣伝 ...
⾳で再配達ゴッド • 不在票の再配達⼿配を お⼿伝いするアプリです。 • 有料版は iPhone の携帯回線だ けでも⼿配ができます。 • 無料版はトーンにした操作⾳を 固定電話に聞かせて⼿配します。 本日リリースしました。