LoginSignup
176
175

More than 5 years have passed since last update.

Swiftでマルチスレッド時の同期処理はどうやるのか

Last updated at Posted at 2014-07-23
  • このエントリはSwift 1.0の時に初版を書きました
  • このエントリはSwift version 2.1 / Xcode 7.1.1 で動作確認済みです

Swift Book にはスレッドや同期(synchronization)への言及がないからどうしたものかなと思っていたのだけど、objcにAPIをつかえば実現はできそうだ。

参考: What is the Swift equivalent to Objective-C's “@synchronized”?

demo: https://github.com/gfx/Swift-MultiThreading

objc_sync_enter() / objc_sync_exit() を使う

これらはobjcの低レベルAPIで、@synchronized ブロックの実装のためにコンパイラが使う関数だけど、Swiftからも呼び出せる。

このままだと使いにくいのでRAIIパターンをつかって使いやすくしよう。

class AutoSync {
    let object : AnyObject

    init(_ obj : AnyObject) {
        object = obj
        objc_sync_enter(object)
    }

    deinit {
        objc_sync_exit(object)
    }
}

これを使うときは単にインスタンスを作成するだけでよく、スコープの終わりになると自動的に解放される。

func f() {
    let lock = AutoSync(self) // lockは自動的に解放される
    self.value++
}

Swift 2.1 追記

これをこのまま使うと、 let lock で「lock は使われないので _ に変えろ」といわれる。しかし、その通りに書き換えるとスレッドがロックされなくなる。最適化によってオブジェクトが消えるためと思われるが、RAIIパターンがまともに使えなくなるのでこれはコンパイラのバグであろう。

GCDのserial queueを使う

これは『Effective Objective C』でも紹介されている方法で、うまくつかうと @synchronized や NSLock よりも高速にできるらしい。ただしXcode 6.3の時点では、最適化(-O)した状態だとどちらも同程度の速度だったので、速度の面でどちらにすべきということはない。

func f() {
    struct _IncrementWithSyncQueue {
        static let s = dispatch_queue_create("com.github.gfx.ios.test.increment_with_sync_queue", DISPATCH_QUEUE_SERIAL)
    }

    dispatch_sync(_IncrementWithSyncQueue.s) {
        self.value += 1
    }
}

いずれにせよ、Objective-C時代と大きく変わるわけではなさそうだ。

176
175
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
176
175