iOSのロック

35698 ワード

転載元: http://www.jianshu.com/p/4f0ce4380dda
生活の中の鍵はどこにでもあります。鍵の役割はもちろん、ここではiOSの錠を締めます。
技能表
atomic(醤油くん)@synchronizedNSLockNSConditionLockNSRecursive LockNSConditiondispatch_semaphoreOspinLockオズunfair_ロックPOSIX LOCKNSDistributedLock(醤油君)atoomic
ロックといえばスレッドの安全を訴えざるを得ません。スレッドの安全といえば、nonatomicとatomicの愛と恨みを持たざるを得ません。私たちはよくこのような説明を見ます。「nonatomicは非原子性非スレッドの安全で、atomicは原子性スレッドの安全ですが、atomicは本当にスレッドの安全ですか?」
まずnonatomicとatomicをかき集めてみます。何をしますか?
nonatomic/atomic = getter + setter + ivar
nonatomicで生成されたgetter、setterはロックをかけていません。atomicで生成されたgetter、setterはロックがあります。したがって、setter/getterによって、ivar賦値/採値がatomicによって修飾された属性ではない場合、この属性は読み書きが安全である。しかし、読み書きの安全はスレッドの安全を表していません。スレッドの安全とは何ですか?
スレッドセキュリティとは、マルチスレッドアクセス時に、一つのスレッドがこのクラスのあるデータにアクセスすると、他のスレッドがアクセスできなくなり、スレッドが読み終わるまで他のスレッドが使用できます。データの不一致やデータの汚染はありません。 スレッドが安全でないということは、データのアクセス保護を提供しないことであり、複数のスレッドが相次いでデータを変更することによって得られたデータは汚いデータである可能性があります。 【百科事典から引用】
atomic非スレッド安全検証
@interface ViewController ()

@property (strong) NSString *info;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            self.info = @"a";
            NSLog(@"A--info:%@", self.info);
        }
    });

    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            self.info = @"b";
            NSLog(@"B--info:%@", self.info);
        }
    });    

}

@end
スレッドセキュリティ定義によれば、atomicがスレッドセキュリティA出力であれば永遠にA--info:aB出力は永遠にB--info:bコンソール出力を見に来ました。
atoomic
OK、atoomic非スレッド安全検証完了、以下にロックを言います。
@synchronized
@synchronizedはiOSで最も一般的なロックです。使い方は簡単です。
    //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            @synchronized (self) {
                _info = @"a";
                NSLog(@"A--info:%@", _info);
            }
        }
    });

    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            @synchronized (self) {
                _info = @"b";
                NSLog(@"B--info:%@", _info);
            }
        }
    });
これにより、Aの出力は全部A--info:aBの出力は全部B--info:bでも@synchronized()括弧の中に同じデータを書けばいいですか?このデータの住所がどんどん変わったら?例えばこうです
   //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            @synchronized (_info) {
                _info = @"a";
                NSLog(@"A--info:%@", _info);
            }
        }
    });

    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            @synchronized (_info) {
                _info = @"b";
                NSLog(@"B--info:%@", _info);
            }
        }
    });
またコンソール出力を見にきます。
@synchronized
@synchronized()かっこの中には住所の不変のデータしか書けません。@synchronizedは暗黙的に異常処理を追加し、異常が発生した時に自動的に反発ロックを解除し、性能が比較的低いです。
NSロック
NSLockはiOSのもう一つの一般的なロックであり、NSLock.hに入るとNSObjectからNSLockingプロトコルを継承していることがわかる。このほか、NSLock.hではNSContinditionLock、NSRecursive Lock、NSContinditionの3つのカテゴリーが見られます。彼らもNSObjectから引き継ぎ、NSLockingプロトコルを遵守します。
NSLockingプロトコルは2つの例示的な方法を定義しており、ロックとアンロックに対応しています。
@protocol NSLocking

- (void)lock;
- (void)unlock;

@end
NSLock、NSConditionLock、NSRecursive Lock、NSConditionに対応した例は、ロック/アンロックによりロックされます。
コードはこのように書くとスレッドの安全を確保できます。
    //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lock];
            _info = @"a";
            NSLog(@"A--info:%@", _info);
            [_lock unlock];
        }
    });

    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lock];
            _info = @"b";
            NSLog(@"B--info:%@", _info);
            [_lock unlock];
        }
    });
注意:ロックとロックは同じスレッドで操作しなければなりません。そうでなければ、結果が確定しないと、デッドロックを引き起こすこともあります。
この他にもNSLockは他の2つの方法を提供しています。
- (BOOL)tryLock; 
- (BOOL)lockBeforeDate:(NSDate *)limit;
NSConditionLock
NSConditionLockにはこのようないくつかの方法があります。
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
ロックを初期化する時にconditionをあげます。属性conditionはreadonlyです。この時もこの属性に疑似コードを割り当てています。
- (instancetype)initWithCondition:(NSInteger)condition {
        if (self =[ [NSConditionLock alloc] init]) {
                [self setValue:@condition forKey:@"condition"];
        }
        return self;
}
conditionを利用してロックをかけたり、ロックを解除したりする時の疑似コードはこのようです。
- (void)lockWhenCondition:(NSInteger)condition {
        if (_condition == condition) [self lock];
}

- (void)unlockWithCondition:(NSInteger)condition {
      [self setValue:@condition forKey:@"condition"];
      [self unlock];
}
コンバージョンが条件ロックを実現する時(実装されなくても良いし、プロトコル方法ロックを直接呼び出してもいいです。)、条件に合っているだけでロックがかかりますが、アンロックは無条件で、任意のコンバージョンでロックが解除されます。このとき設定されたコンバージョンは次の条件ロックのコンバージョンです。スレッド安全例コード
   //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lockWhenCondition:0];
            _info = @"a";
            NSLog(@"A--info:%@--condition:%zd", _info, _lock.condition);
            [_lock unlockWithCondition:1];

        }
    });

    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lockWhenCondition:1];
            _info = @"b";
            NSLog(@"B--info:%@--condition:%zd", _info, _lock.condition);
            [_lock unlockWithCondition:0];
        }
    });
この特性を利用して依存関係を設定できます。通常- (void)lock を選択します  - (void)unlockWithCondition:(NSInteger)condition 配合使用、- (void)lockWhenCondition:(NSInteger)condition と- (void) unlock 配合使用はもちろん、混用も可能です。
NSRecursive Lock
NSRecursive Lockを中国語に訳して再帰ロックといい、同じ方法の内部に複数回ロックされている場面をその名の通り処理できます。
static int i = 10;

- (void)recursiveLock {
    [_lock lock];
    NSLog(@"NSRecursiveLock--%zd", i--);
    if (i >= 0) {
        [self recursiveLock];
    }
    [_lock unlock];
}
ここのロックをNSLockに換えると、明らかに必死です。他のロックとは違って、NSRecursive Lockは複数回ロックすることができますが、全てのロックが解除された場合にのみ、他のスレッドがNSRecursive Lockに再アクセスできます。
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self recursiveLock];
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lock];
            NSLog(@"lock");
            [_lock unlock];
            NSLog(@"unlock");
        }
    });
NSRecursive Lock
NSCodition
NSConditionにはこれらの方法があります。
- (void)wait; //    
- (BOOL)waitUntilDate:(NSDate *)limit; //        
- (void)signal; //         
- (void)broadcast; //        
NSConditionは、スレッドの吊り上げと起動を手動で制御することができ、この特性設定に依存することが明らかになっている。
基本的な使い方:
    //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [_lock lock];
        NSLog(@"A    ");
        [_lock wait];
        NSLog(@"A    ");
        [_lock unlock];
        NSLog(@"A    ");
    });

    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [_lock lock];
        NSLog(@"B    ");
        [_lock wait];
        NSLog(@"B    ");
        [_lock unlock];
        NSLog(@"B    ");
    });


    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        [_lock signal];
    });
NSConditionn-1
もし[_lock signal]両替したら[_lock broadcast]NSConditionn-2
dispatch_semaphore
dispatch_semaphoreは信号量でロックします。
スレッドセキュリティの例コード:
- (void)semaphore {
    dispatch_semaphore_t dsema = dispatch_semaphore_create(1);

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
            _info = @"a";
            NSLog(@"A--info:%@", _info);
            dispatch_semaphore_signal(dsema);
        }
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
            _info = @"b";
            NSLog(@"B--info:%@", _info);
            dispatch_semaphore_signal(dsema);
        }
    });
}
/*! 
 * @param value
 *       ,           NULL
 * @result
 *            ,    NULL
 */
dispatch_semaphore_t dispatch_semaphore_create(long value)

/*!
 * @discussion
 *     1,      0,               timeout
 * @param dsema
 *    
 * @param timeout
 *     
 *    dispatch_time_t,      DISPATCH_TIME_NOW、DISPATCH_TIME_FOREVER
 * @result
 *        0,timeout   0
 */
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

/*!
 * @discussion
 *     1,          0,         
 * @param dsema 
 *    
 * @result
 *          0,    0
 */
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
OKです。3つの関数は何のために使いますか?試水してみます。
タイムアウト、スレッド起動
- (void)semaphore {
    dispatch_semaphore_t dsema = dispatch_semaphore_create(0);

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            long a = dispatch_semaphore_wait(dsema, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
            NSLog(@"a--%ld", a);
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        sleep(2);
        long b = dispatch_semaphore_signal(dsema);
        NSLog(@"b--%ld", b);
    });
}
semaphore-1
スレッドが起動されていません。タイムアウトしていません。
- (void)semaphore {
    dispatch_semaphore_t dsema = dispatch_semaphore_create(0);

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            long a = dispatch_semaphore_wait(dsema, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
            NSLog(@"a--%ld", a);
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        sleep(1);
        long b = dispatch_semaphore_signal(dsema);
        NSLog(@"b--%ld", b);
    });
}
semaphore-2
結局は思った通り、何も話し続けることができませんでした。口を多く開けてください。スレッドが起動されますか?それともタイムアウトされますか?
OSPinLock
OSPinLockスピンロックは、ヘッドファイルを導入する必要があります。
    //     unlock 0,lock  0
    OSSpinLock spinLock = OS_SPINLOCK_INIT;
    //   
    OSSpinLockLock(&spinLock);
    //   
    OSSpinLockUnlock(&spinLock);
    //     
    BOOL b = OSSpinLockTry(&spinLock);
- (void)OSSpinLock {
    OSSpinLock spinLock = OS_SPINLOCK_INIT;
    NSLog(@"   :%zd", spinLock);
    OSSpinLockLock(&spinLock);
    NSLog(@"   :%zd", spinLock);
    OSSpinLockUnlock(&spinLock);
    NSLog(@"   :%zd", spinLock);
}
OSPinLock-1
スクリーンショットをもう一枚見に来てください。
OSPinLock-2
OSPinLock is deprecated in iOS 10.0-Use os_unfair_ロック()from instead
スピンロックに優先度反転問題があるため(YYYKit著者のこの記事を参照することができます。 安全ではないOSPinLock)は、iOS 10.0でのオウunfair_ロック()置換
オズunfair_ロック
オズunfair_ロックiOS 10.0新しく発売されたロックは、OSPinLock優先度反転問題を解決するために使用されます。
    //    
    os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
    //   
    os_unfair_lock_lock(unfairLock);
    //   
    os_unfair_lock_unlock(unfairLock);
    //     
    BOOL b = os_unfair_lock_trylock(unfairLock);
POSIX LOCK
POSIX LOCKはC言語レベルのロックであり、顔写真ファイルの安全例コードを導入する必要があります。
static pthread_mutex_t lock;
- (void)pLock {

    pthread_mutex_init(&lock, NULL);

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            pthread_mutex_lock(&lock);
            _info = @"a";
            NSLog(@"A--info:%@", _info);
            pthread_mutex_unlock(&lock);
        }
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            pthread_mutex_lock(&lock);
            _info = @"b";
            NSLog(@"B--info:%@", _info);
            pthread_mutex_unlock(&lock);
        }
    });
}
POSIX LOCKはpthread_だけではありません。mutext pthread_もありますcond_tなどは、あまり使わないので、ここではあまり紹介しません。
NSDistributedLock
NSDistributedLock分散錠は、MAC OSの開発に使われ、醤油が通ります。
デッド・ロック
いわゆるデッドロックとは、2つ以上のプロセスが実行中に競合資源や通信によって引き起こされるブロック現象を指し、外力がないと進められなくなります。この時点でシステムがデッドロック状態またはシステムによってデッドロックが発生します。これらは永遠にお互いを待つプロセスをデッドロックプロセスと呼びます。\プロセスは運行中にデッドロックが発生する可能性がありますが、デッドロックの発生も一定の条件を備えていなければなりません。デッドロックの発生には以下の4つの必要条件が必要です。1)相互反発条件:プロセスが割り当てられたリソースを排他的に使用すること、すなわち一定期間内にあるリソースは一つのプロセスだけで占有されること。この時点で他のプロセスがリソースを要求している場合、要求者はリソースを占有するプロセスが終了するまで待つしかない。2)要求と保持条件:プロセスは少なくとも1つのリソースを保持しているが、他のプロセスによって占有されている新しいリソース要求を提出している。このとき、プロセスがブロックされていることを要求するが、自分がすでに獲得した他のリソースに対してはそのまま保持している。3)条件を奪わない:プロセスによって得られた資源は、使用される前に奪われてはいけない。使用が終わった時に自分で釈放するしかない。4)ループ待ち条件:デッドロックが発生した時、必然的に1つのプロセス——リソースのリングチェーンが存在すること。すなわち、プロセスセット{P 0、P 1、P 2、・・、Pn}のP 0は、1つのP 1が占有するリソースを待っている。P 1はP 2によって占有されるリソースを待っています。…PnはP 0によって占有されたリソースを待っています。 【百科事典から引用】