Swiftで変数にatomicにアクセスしたい場合、幾つか方法があるが、OSSpinLockを使う方法をここでは紹介する。 他の方法との時間の比較も時間があればやりたい。
まずは、排他処理なしの場合に動作がダメなことを確認する
import Foundation let sz = 1000 var counter = 0 @objc class myClass { func myFunc() { for i in 0 ..< sz { counter = counter + 1 } } } var myObj = myClass() for i in 0 ..< sz { NSThread.detachNewThreadSelector("myFunc", toTarget: myObj, withObject: nil) } sleep(3) NSLog("%d", counter) // 860876
1000*1000=1000000にならないといけない値が860876になっている。
そこでSpinLockを入れてみる。
import Foundation var sp:OSSpinLock = OS_SPINLOCK_INIT // <--- 追加 let sz = 1000 var counter = 0 @objc class myClass { func myFunc() { for i in 0 ..< sz { OSSpinLockLock(&sp) // <--- 追加 counter = counter + 1 OSSpinLockUnlock(&sp) // <--- 追加 } } } var myObj = myClass() for i in 0 ..< sz { NSThread.detachNewThreadSelector("myFunc", toTarget: myObj, withObject: nil) } sleep(3) NSLog("%d", counter) // 1000000
値が1000000となり完全に排他的にアクセスできていることがわかる。
参考URL mikeash.com: Friday Q&A 2015-02-06: Locks, Thread Safety, and Swift
ここではcounter変数の読み出しと書き込みを排他して行っているが、Objective-Cのプロパティで使ったatomicに比べて多くのことをやりすぎている。Objective-Cのプロパティのatomicは変数を中途半端に読み出すことがないことを保証するものである。 単純なatomic操作のみを実装するとこんな感じになる。
class AtomicValue<T> { private var _lock:OSSpinLock = OS_SPINLOCK_INIT private var _value:T init(_ value:T){ _value = value } func load() -> T { OSSpinLockLock(&_lock) let value = _value OSSpinLockUnlock(&_lock) return value } func store(value:T){ OSSpinLockLock(&_lock) _value = value OSSpinLockUnlock(&_lock) } } let sz = 1000 var counter:AtomicValue<Int> = AtomicValue(0) @objc class myClass { func myFunc() { for i in 0 ..< sz { counter.store(counter.load()+1) } } } var myObj = myClass() for i in 0 ..< sz { NSThread.detachNewThreadSelector("myFunc", toTarget: myObj, withObject: nil) } sleep(3) NSLog("%d", counter.load()) // 270099
この例では、1000000になっていないので、一見ロジックがダメなようにも見えるが、increment、decrementする変数に対しては、 AtomicValueクラスを用いても意味がなく、OSAtomicIncrement32Barrierなどを用いるべきなのである。 AtomicValueクラスを用いるケースとしては、increment,decrementするような使い方ではないデータ型で、値を設定するのは特定の一つのスレッドだが、読み取りは、任意のスレッドが行うようなケースである。
iOSでは非推奨らしい
swift メンテナによると、どうやらiOSではspinlockは非推奨らしい。
https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000321.html