Xcode
Swift

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

More than 3 years have passed since last update.


  • このエントリは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時代と大きく変わるわけではなさそうだ。