iOSにおけるスレッド同期とロックメカニズム

セマフォ: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);

タグ: iOS スレッド同期 ロックメカニズム GCD セマフォ

5月24日 18:18 投稿