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として機能しています。