セマフォ:dispatch_semaphore_t
ミューテックス:pthread_mutex、@ synchronized、NSLock
条件付きロック:NSConditionLock、NSCondition
再帰ロック:NSRecursiveLock
スピンロック:OSSpinLock(非推奨、優先度逆転によるデッドロックの問題が発生)
リードライトロック:atomic(iOS10以降はos_unfair_lockで実装)
スピンロックとミューテックスの違い
スピンロックはビジーループ:ビジーループとは、ロックされたリソスにアクセスする際に、呼び出し側スレッドがスリープせず、リソスのロックが解放されるまで継続的にループを実行することを指します。
ミューテックスはスリープ:スリープとは、ロックされたリソスにアクセスする際に、呼び出し側スレッドがスリープ状態になり、CPUは他のスレッドをスケジュールできます。リソスのロックが解放されるまでスリープ状態が継続され、解放されるとスリープ中のスレッドが喚醒されます。
性能順位:
一、セマフォdispatch_semaphore_t:高性能
// セマフォを作成し、初期値を設定
dispatch_semaphore_t dispatch_semaphore_create(long value);
// セマフォをデクリメント(-1)、セマフォが0以下の場合はスレッドをブロック
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
// セマフォをインクリメント(+1)
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
リソスの同期アクセス制御
// セマフォを作成し、初期値を0に設定
dispatch_semaphore_t semaphone = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // スレッド1
sleep(2);
NSLog(@"非同期処理1.... %@",[NSThread currentThread]);
dispatch_semaphore_signal(semaphone); // セマフォ+1
});
dispatch_semaphore_wait(semaphone, DISPATCH_TIME_FOREVER); // セマフォ-1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// スレッド2
sleep(2);
NSLog(@"非同期処理2.... %@",[NSThread currentThread]);
dispatch_semaphore_signal(semaphone); // セマフォ+1
});
最大同時実行数制御
// セマフォを作成し、初期値を5に設定(最大同時実行数5)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"i = %d %@",i,[NSThread currentThread]);
// ここで非同期で画像をダウンロードする処理をシミュレート
sleep(arc4random()%6);
dispatch_semaphore_signal(semaphore);
});
}
二、ミューテックスpthread_mutex_t
@property(nonatomic, assign)pthread_mutex_t _mutex;
// 初期化
pthread_mutex_init(&_mutex, NULL);
// ロック
pthread_mutex_lock(&_mutex);
// 操作
// アンロック
pthread_mutex_unlock(&_mutex);
// 破棄
pthread_mutex_destroy(&_mutex);
```三、ミューテックスNSLock(pthread_mutex_tのカプセル化)`
// 初期化
__block NSLock *lock = [[NSLock alloc] init];
// ロック
[lock lock];
// アンロック
[lock unlock];
四、条件付きロックNSCondition(pthread_mutex_tのカプセル化)
- (void)conditionLock {
NSCondition *conditionLock = [[NSCondition alloc] init];
__block NSString *food;
// 消費者1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionLock lock];
if (!food) { // 現在の料理がない場合(スレッドブロック条件を判断)
NSLog(@"料理を待っています");
[conditionLock wait]; // 料理がないので待機!(条件を満たしたらスレッドをブロック)
}
// 料理ができたので食事を開始!(スレッドが解放され、処理を継続)
NSLog(@"食事を開始:%@",food);
[conditionLock unlock];
});
// 生産者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionLock lock];
NSLog(@"シェフが料理を作成中...");
sleep(5);
food = @"四菜一汁";
NSLog(@"シェフが料理を完成:%@",food);
[conditionLock signal];
// [conditionLock broadcast];
[conditionLock unlock];
});
}
五、条件付きロックNSConditionLock(NSConditionのカプセル化)
// セマフォに類似
NSConditionLock * conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1];
NSLog(@"スレッド1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
NSLog(@"スレッド2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"スレッド3");
[conditionLock unlock];
});
出力結果:
スレッド3 スレッド2 スレッド1 または スレッド2 スレッド1 スレッド3
分析:
-lockWhenCondition: ロックし、スレッドをブロック。conditionがプロパティと等しい場合に喚醒される -unlockWithCondition: アンロックし、conditionプロパティを変更
[conditionLock lock] は条件なしで、多スレッド環境での[conditionLock lockWhenCondition:2]の実行順序は不定
六、再帰ロックNSRecursiveLock(pthread_mutex_tのカプセル化)
七、ミューテックス@synchronized : 性能は低いが、より簡潔で読みやすく使いやすい
@synchronized (self) {
}
その他
バリア関数によるマルチスレッド同期制御 dispatch_barrier_sync と dispatch_barrier_async
// 并行キュー
dispatch_queue_t queue = dispatch_queue_create("com.gcd.brrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"タスク1 -- %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"タスク2 -- %@",[NSThread currentThread]);
});
// バリア関数、同期バリアと非同期バリアを変更し、"バリア終了"の印刷位置を観察
dispatch_barrier_sync(queue, ^{
for (int i = 0; i < 4; i++) {
NSLog(@"タスク3 --- ログ:%d -- %@",i,[NSThread currentThread]);
}
});
// ここで出力を実行
NSLog(@"バリア終了");
dispatch_async(queue, ^{
sleep(1);
NSLog(@"タスク4 -- %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"タスク5 -- %@",[NSThread currentThread]);
});
アトミック操作atomic、原理はプロパティのset/getメソッドにロックが追加され、atomicは読み取り/書き込み操作の安全性を保証(操作の原子性)しますが、マルチスレッドの安全性は保証しません; nonatomicは読み取り/書き込み操作の安全性を保証しません(操作は非原子性)、マルチスレッドの安全性も保証しません;しかしnonatomicはatomicより性能が高く、マルチスレッド操作を伴わない場合はnonatomicの使用が良い選択です。性能を確保しつつデータの安全性を確保できます;開発で大量のマルチスレッド操作を扱う場合は、atomicを使用する必要があります。性能と安全性のバランスを見つけることが、プログラマーの腕の見せ所です!!!
// atomic属性を追加
@property (atomic, copy) NSString *name;
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{// スレッド1
self.name = @"田中";//setter操作
});
dispatch_async(concurrentQueue, ^{// スレッド2
self.name = @"佐藤";//setter操作
});
dispatch_async(concurrentQueue, ^{// スレッド3
self.name = @"山田";//setter操作
// ここで少し時間のかかる操作があり、完了後にnameの値を利用したい
sleep(2);
NSLog(@"山田を呼び出してください、name = %@",self.name);//getter操作
});
dispatch_async(concurrentQueue, ^{// スレッド4
self.name = @"鈴木";//setter操作
});
出力結果:山田を呼び出してください、name = 鈴木
拡張:
一、サードパーティライブラリ SDWebImage でのロック:
@property (nonatomic, strong) dispatch_semaphore_t lock;
遅延ロード
- (dispatch_semaphore_t)lock {
if (!_lock) {
_lock = dispatch_semaphore_create(1);
}
return _lock;
}
マクロ定義
#ifndef SD_LOCK
#define SD_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#endif
#ifndef SD_UNLOCK
#define SD_UNLOCK(lock) dispatch_semaphore_signal(lock);
#endif
ロック
SD_LOCK(self.lock);
アンロック
SD_UNLOCK(self.lock);