読者です 読者をやめる 読者になる 読者になる

Pebble's Diary

プログラマーの作業メモ

SwiftでAtomic操作

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するような使い方ではないデータ型で、値を設定するのは特定の一つのスレッドだが、読み取りは、任意のスレッドが行うようなケースである。