atomicって何なのさ?(2)
atomicってのは広義にはマルチスレッドで同時に実行されても大丈夫てことです。
つまりatomicはその中で何かを守ってるってことでもあります。
まず、これのアセンブラコードを見てみます。
@interface AClass : NSObject @property (copy) NSString *name; // objcオブジェクト @property NSInteger age; // スカラ(整数) @property CGFloat height; // スカラ(浮動小数点) @property NSPoint point; // 構造体 @end @implementation AClass @synthesize name; @synthesize age; @synthesize height; @synthesize point; @end
とりあえずセッタだけ見てみます。
"-[AClass setName:]": ## @"\01-[AClass setName:]" Ltmp23: .cfi_startproc ## BB#0: pushq %rbp Ltmp24: .cfi_def_cfa_offset 16 Ltmp25: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp26: .cfi_def_cfa_register %rbp movq %rdx, %rax movq _OBJC_IVAR_$_AClass.name(%rip), %rdx movq %rax, %rcx movl $1, %r8d movl $1, %r9d popq %rbp jmp _objc_setProperty ## objc_setPropertyを呼び出す Ltmp27: .cfi_endproc
"-[AClass setAge:]": ## @"\01-[AClass setAge:]" Ltmp37: .cfi_startproc ## BB#0: pushq %rbp Ltmp38: .cfi_def_cfa_offset 16 Ltmp39: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp40: .cfi_def_cfa_register %rbp movq _OBJC_IVAR_$_AClass.age(%rip), %rax movq %rdx, (%rdi,%rax) ## 直接値を代入 popq %rbp ret Ltmp41: .cfi_endproc
"-[AClass setHeight:]": ## @"\01-[AClass setHeight:]" Ltmp51: .cfi_startproc ## BB#0: pushq %rbp Ltmp52: .cfi_def_cfa_offset 16 Ltmp53: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp54: .cfi_def_cfa_register %rbp movd %xmm0, %rax movq _OBJC_IVAR_$_AClass.height(%rip), %rcx movq %rax, (%rdi,%rcx) ## 直接値を代入 popq %rbp ret Ltmp55: .cfi_endproc
"-[AClass setPoint:]": ## @"\01-[AClass setPoint:]" Ltmp65: .cfi_startproc ## BB#0: pushq %rbp Ltmp66: .cfi_def_cfa_offset 16 Ltmp67: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp68: .cfi_def_cfa_register %rbp subq $16, %rsp movq %rdi, %rax movsd %xmm0, -16(%rbp) movsd %xmm1, -8(%rbp) movq _OBJC_IVAR_$_AClass.point(%rip), %rdi addq %rax, %rdi leaq -16(%rbp), %rsi movl $16, %edx movl $1, %ecx xorl %r8d, %r8d callq _objc_copyStruct ## objc_copyStructを呼び出す addq $16, %rsp popq %rbp ret Ltmp69: .cfi_endproc
objcオブジェクトと構造体は何やら特別な関数を呼び出していますが、スカラに関してはただ単に値を代入しているだけです。
とりあえず、この違いは置いておき、objcオブジェクトのなにが守られているのか見てみます。
objc_setProperty関数はこのへんにあります。
void objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, BOOL shouldCopy) { // Retain release world id oldValue, *slot = (id*) ((char*)self + offset); // atomic or not, if slot would be unchanged, do nothing. if (!shouldCopy && *slot == newValue) return; if (shouldCopy) { newValue = (shouldCopy == OBJC_PROPERTY_MUTABLECOPY ? [newValue mutableCopyWithZone:NULL] : [newValue copyWithZone:NULL]); } else { newValue = objc_retain(newValue); } if (!atomic) { oldValue = *slot; *slot = newValue; } else { // このへんから注目 (A) spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)]; _spin_lock(slotlock); // スピンロック oldValue = *slot; // (1) *slot = newValue; // (2) _spin_unlock(slotlock); } objc_release(oldValue); // (3) }
(A)の部分がatomicでのキモです。(1)(2)が複数スレッドで同時に実行されないようにブロックしています。
言ってしまえば、この部分のみがatomicです。
やっている事は、(1)で古い値を退避して、(2)で新しい値を代入しています。
では、この部分がatomicでなければどういう問題が発生するのか。
Thread A | Thread B |
(1) | |
(1) | |
(2) | |
(2) |
の様に実行されると、退避された古い値は、Thread A、Thread Bで同じオブジェクトになってしまいます。
つまり、(3)が両方のスレッドで実行されると、古いオブジェクトが余分にリリースされ、Thread Aでの値はReleaseされずに捨てられてしまうのです。
objcオブジェクトのプロパティにおけるatomicはこれを防いでいる訳です。
構造体に関しては上と同じページにobjc_copyStruct関数があります。これを見ると、memmove(3)関数でメモリ上のデータをコピーしているため、複数スレッドで同時に実行されると値そのものが破壊される可能性があります。objc_copyStruct関数はそれをブロックしているのです。
で、最後にスカラですが、これは、(おそらく、ほぼ)atomicな movq 命令を使用している上、retainCountなどは関係ないので、直接代入でatomicとして機能しています。