ひとつのアプリケーションに複数のスレッドがあると、同じリソースに対する複数スレッドから変更が意図せず干渉する場合がある。基本的かつ有効な方針は、共有リソースを減らし、スレッド間のやり取りを最小化することだ。
不変オブジェクトはスレッドセーフであるので、スレッド間で安全に受け渡しすることができる。また、そもそもあるオブジェクトが単一のスレッドからしか利用されない場合は、当たり前ではあるがなにも問題はない。Foudation の基本的なクラスはだいたいスレッドセーフになっているが、そうでないものもあるので注意が必要。
しかし、完全に干渉のない設計が常にできるわけではないのでその場合には同期ツールを使う。同期ツールには以下のようなものがある。
単一のスレッドで複数のロックを同時に取得しようとする場合は、常にデッドロックが発生する可能性がある。できるだけそういう処理を避ける。
ハードウェア命令とメモリバリアにより、特定の操作を必ず完了してから、その操作の影響を受けるメモリへのアクセスが再開されるような仕組みになっている。
対応している演算は以下のとおり
var value: Int32 = 0 OSAtomicTestAndSet(0, &value) value // value is now 128. value = 0 OSAtomicTestAndSet(7, &value) value // value is now 1. value = 0 OSAtomicTestAndSet(15, &value) value // value is now 256. OSAtomicCompareAndSwap32(256, 512, &value) value // value is now 512. OSAtomicCompareAndSwap32(256, 1024, &value) value // value is still 512.
いろいろなロック
どのアプリケーションからも利用できる。
var mutex = pthread_mutex_t() func initialize() { pthread_mutex_init(&mutex, nil) } func destroy() { pthread_mutex_destroy(&mutex) } func lockingFunction() { pthread_mutex_lock(&mutex) defer { pthread_mutex_unlock(&mutex)} // // ロックを利用できる // }
var lock = NSLock() func lockingFunction() { lock.lock() defer { lock.unlock() } // // ロックを利用できる // }
tryLock() -> Bool
とか lockBeforeDate
など便利なやつもいる。
同一スレッドによる複数回ロック取得が可能なロック。ロック数をカウントしているので、対応するアンロックがすべて走ってようやくロックが解除される。使い方は NSLock
と同じ。再帰呼び出しが発生する場合はこれを使うべし。ただ再帰呼び出しが発生しないように実装できるならばその限りでない。
NSConditionLock
は特定の値を使用してロックおよびロック解除できるミューテックスロックを定義する。
let condLock = NSConditionLock(condition: 0) while true { condLock.lock() condLock.unlockWithCondition(1) // // ロックを利用できる // }
条件変数は、必要な順序に合わせて操作を進めるために使用出来るロック。ある条件で待機しているスレッドはその条件のシグナルが別スレッドから明示的に送られるまでブロックされたままになる。
let cond = NSCondition() dispatch_async(dispatch_get_global_queue(0, 0), { sleep(2) cond.signal() }) while true { cond.wait() // // 処理 // cond.unlock() break }
ドキュメントの中では用語の定義が以下のようになされている。