Objective-C atomicity #idevjp

>100 Views

May 25, 13

スライド概要

Objective-C の @property でおなじみの atomic について、iphone_dev_jp 東京 iPhone/Mac 勉強会で発表しました。

atomic でどんな作用が期待できるのか。それがスレッドセーフになり得るのか。スレッドセーフを実現するための手法とは。今回はそんな、マルチスレッド周りのお話です。

※ Docswell での公開に移行する直前の Slideshare での閲覧数は 2,084 でした。

profile-image

正統派趣味人プログラマー。プログラミングとは幼馴染です。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

Objective-C - atomicity 〜 誰もが知ってるかもしれない最初の話 〜 EZ-NET 熊⾕友宏 @EasyStyleGK http://program.station.ez-net.jp/

2.

今回は、ここのお話

3.

⾃⼰紹介 EZ-NET 熊⾕友宏 http://program.station.ez-net.jp/ @EasyStyleGK iOS アプリ 制作中 EZ-NET IP Phone ⾳で再配達ゴッド ⾳で再配達 ⾳でダイヤル いつもの電卓 for iPad いつもの電卓 for iPhone

4.

はじまり ⽬次 1. nonatomic ってなんだろう 2. なぜ今頃 atomic の話題なのか 3. @property (atomic)って何をしてくれるの︖ 4. 値が壊れる︖ 5. スレッドセーフを考慮する

5.

第1章 nonatomic って なんだろう

6.

nonatomic と⾔えば 1. プロパティ定義で使うキーワード 2. atomic と nonatomic とがある 3. 省略時は atomic になる そのプロパティが原⼦性を 保証するかを⽰すキーワード

7.

原⼦性とは これ以上分解できない単位

8.

つまり atomic なプロパティとは そのプロパティの処理を ひとまとまりとして実⾏する ということ︖

9.

つまり atomic なプロパティとは つまりスレッドセーフ ということ︖ 必ずしもそうとは限らない

10.

第2章 なぜ今頃 atomic の話題なのか

11.

それは無計画さが招いた課題 ⾃作アプリの複数スレッドを跨ぐ処理が いよいよ制御しきれなくなったため︕ 1. EXC_BAD_ACCESS で落ちる 2. 計算中に結果を取得される 3. 状態に基づく処理中に状態が変わる スレッドセーフを考えなくてはいけない

12.

スレッドセーフってなんだろう 1. 他スレッドで使⽤中のインスタンスが 解放されないようにする 2. 複数スレッドからのアクセス時に、 値に⽭盾が出ない事を保証する 3. ⼀連の処理をブロックして、⽭盾のな い処理を保証する など、いろいろ

13.

スレッドセーフというのは 複数スレッドを使った平⾏処理を ⽭盾しないように制御する考え⽅ atomic はそのうちのひとつ

14.

第3章 @property (atomic) って 何をしてくれるの?

15.

プロパティに atomic を指定したとき 原⼦性をコンパイラが ⾃動で保証してくれるの︖ それとも⾃分で原⼦性を 保証しなければいけないの︖

16.

@synthesize であれば ある程度は⾃動で保証してくれる このあたりの感覚は @property (copy) と同じ

17.

@synthesize での atomic 制御 プリミティブ型の場合 1. インスタンス変数に書き込んでいる間、 他からの読み書きはブロックされる 読み書きする値が 正しいことを保証する

18.

@synthesize での atomic 制御 オブジェクト型の場合 1. インスタンス変数に書き込んでいる間、 他からの読み書きはブロックされる 2. ゲッターでは、ブロックの中でインスタ ンスを retain して autorelease する 値の正確性と合わせて インスタンスの⽣存を保証する

19.

@synthesize での atomic 制御 @synthesize で保証されるのは 該当する ivar の整合性だけ インスタンス全体の 整合性は保証されない

20.

オブジェクト全体の整合性を保証したいなら メソッドでの処理も考慮した インスタンス全体の整合性を保つ制御が必要 A. 同時実⾏されたくないもの同⼠をロック B. クラスを Immutable で設計する C. インスタンスを扱うスレッドを統⼀する こういったことに配慮しながら クラスを設計する必要がある

21.

第4章 値が壊れる?

22.

同時アクセスされることで case 1: クラス全体の整合性が 崩れることは想像に易しい

23.

クラス全体の整合性破壊 インスタンス { } スレッド 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 ?

24.

同時アクセスされることで case 2: @synthesize なプロパティが 壊れるってどういうこと︖

25.
[beta]
プロパティの整合性破壊︖
インスタンス
{
}

スレッド 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

26.

プロパティの整合性破壊… プログラムの1⾏が 実⾏時の1ステップではない

27.
[beta]
プロパティの整合性破壊︕
インスタンス
{
}

スレッド 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

28.

同時アクセスされることで つまり プリミティブ型だって壊れる もちろん構造体も壊れる

29.

同時アクセスされることで case 3: インスタンスが 解放されることも

30.

インスタンスの予期しない解放 インスタンス スレッド 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 !

31.

平⾏処理は危険がいっぱい スレッドセーフってとっても⼤事

32.

第5章 スレッドセーフを考慮する

33.

おさらい 同時アクセスが引き起こす不都合 A. プリミティブ型のプロパティが扱うデー タが壊れる B. プロパティから取得したオブジェクトが 予期せず解放される C. インスタンスへの同時アクセスにより その整合性が崩れる

34.

スレッドセーフの実現⽅法 同時アクセスによる 不都合からプログラムを守るために A. atomic と @synthesize を使⽤する B. 返すインスタンスは確実に retain する C. 関係する範囲を把握して 不整合が起こらないようにロックする D. 実⾏するスレッドをひとつに統⼀する E. クラスを Immutable で設計する

35.

同時アクセスからの保護 実例をいくつか

36.

ブロックの⽅法 Objective-C で使えるロックの紹介 a) @synchronized (self) ― 続くブロック {} を再帰ロック ― 指定したインスタンスがキーになる b) NSRecursiveLock ― -lock から -unlock までを再帰ロック ― pthread_mutex の Objective-C 版 c) セマフォ ― dispatch_semaphore でロック ― タイムアウトの指定も可能

37.

実例 case A: 読み書きでの 値の破壊を防ぐ

38.

読み書きでの値の破壊を防ぐ プリミティブ型の @property (atomic) を @synthesize したときに採られる⽅法 1. セッターとゲッターを 同じキーでロックする 同時アクセスを防ぎ 値の⽭盾を起こさない

39.
[beta]
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!

40.

内部的な実装は次のような感じに 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) とはまったく別のもの

41.

実例 case B: インスタンスを 確実に retain する

42.

インスタンスを確実に retain する オブジェクト型の @property (atomic) を @synthesize したときに採られる⽅法 1. セッターとゲッターを 同じキーでロックする 2. ゲッターのロック内でインスタンス を retain & autorelease する インスタンスを確実に確保して 呼び出し元が正しく受け取れるようにする

43.

インスタンスを確実に 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]

44.

内部的な実装は次のような感じに 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 が 解放されるところまでは保護されない

45.

実例 case C: 関係する範囲をロックして 不整合が起きないようにする

46.

関係する範囲をロックして不整合を防ぐ オブジェクト全体の 整合性を保護する⽅法 1. 同時アクセスされたくない部分を 同じキーでロックする 2. プロパティも含めて保護するときは @synthesize は使わない 計算処理と結果取得を同じキーでロックすれば 計算途中の結果取得を防⽌できる

47.

整合性に関係する範囲をロックする インスタンス スレッド 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!

48.

実例 case D: 実⾏するスレッドを ひとつに統⼀する

49.

実⾏するスレッドをひとつに統⼀する iOS の UI 制御でも採られている⽅法 1. ある⼀連の機能を必ず同じスレッド で実⾏するようにする 2. 実⾏スレッドではランループが必要 同時アクセスを起こさせないため ロックやデータの⽭盾を気にしなくて済む

50.
[beta]
実⾏するスレッドをひとつに統⼀する
インスタンス
@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
!

51.

実例 case E: クラスを Immutable で設計する

52.

クラスを Immutable で設計する インスタンス⽣成後に 値を変更できないようにする⽅法 1. インスタンス⽣成後に ivar の値を編集 できないクラスを作る 2. 値の設定はインスタンス⽣成のときだけ 値が「変化する途中」が存在しないため 同時アクセスで値が⽭盾しない

53.

クラスを Immutable で設計する Immutable クラスの特徴 内部でのロックなく 複数スレッドで使⽤可能 ただしインスタンスの受け渡し時には 原⼦性を保護する必要がある

54.

クラスを 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 メソッドで設定して インスタンス⽣成後は値の取得だけができる

55.

クラスを 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; }

56.
[beta]
クラスを 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]

57.

他のケース 他にもこんなところで 配慮されていたり

58.

1.予期しないインスタンスの解放を防ぐ performSelector:onThread:withObject :... によるインスタンスの⽣存保証 1. 引数 withObject に渡されたインスタン スが渡された時点で retain される 2. 別スレッドでの実⾏が終わると、インス タンスは release される 他のスレッドで実⾏されるまでの間に インスタンスが解放されないようにする

59.

2.予期しない値の変化を防ぐ 渡された NSString を 複製してインスタンス変数に持つ⽅法 1. NSString のインスタンスは編集可能な 場合がある (NSMutableString) 2. そこで copy して、渡されたものとは 別の NSString を持つようにする 複製を内部に持つ事で 外部での値変更の影響を受けない

60.

スレッドセーフ このような配慮で 複数スレッドに対応させている

61.

あとは宣伝 ...

62.

⾳で再配達ゴッド • 不在票の再配達⼿配を お⼿伝いするアプリです。 • 有料版は iPhone の携帯回線だ けでも⼿配ができます。 • 無料版はトーンにした操作⾳を 固定電話に聞かせて⼿配します。 本日リリースしました。