淺談iOS中的鎖的介紹及實戰使用

前言

  • 這篇文章咱們來聊聊鎖,先來看看分析各類鎖以前的性能的圖表:

同步鎖

1.@synchronized (self)

實現鎖的優勢就是咱們不須要在代碼中顯式的建立鎖對象,即可以實現鎖的機制git

@synchronized (self) {
    //TODO:加鎖操做
}
複製代碼

互斥鎖

互斥鎖是用來保證共享數據操做的完整性。每一個對象都對應於一個可稱爲「互斥鎖」的標記,這個標記用來保證在任一時刻,只能有一個線程訪問對象github

1.NSLock

NSLock遵循NSLocking協議數組

@protocol NSLocking
- (void)lock;//加鎖
- (void)unlock;//解鎖
@end
複製代碼

NSLock鎖較爲經常使用,一般是添加在一個線程中,要注意的是添加鎖的線程不要是屢次執行的,由於添加鎖以後,其餘線程要等待鎖執行以後才能執行,因此添加鎖的的代碼最好是不耗時markdown

- (BOOL)tryLock;//嘗試加鎖
//指定Date以前嘗試加鎖,若是在指定時間以前都不能加鎖
- (BOOL)lockBeforeDate:(NSDate *)limit;
複製代碼

使用示例多線程

NSLock *lock = [NSLock new];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"線程1 嘗試加速ing...");
    [lock lock];
    sleep(3);//睡眠5秒
    NSLog(@"線程1");
    [lock unlock];
    NSLog(@"線程1解鎖成功");
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"線程2 嘗試加速ing...");
    BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
    if (x) {
        NSLog(@"線程2");
        [lock unlock];
    }else{
        NSLog(@"失敗");
    }
});
複製代碼

2.pthread

pthread除了建立互斥鎖,還能夠建立遞歸鎖、讀寫鎖、once等鎖async

- (void)lock{
    __block pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"+++++ 線程1 start");
        pthread_mutex_lock(&mutex);
        sleep(2);
        pthread_mutex_unlock(&mutex);
        NSLog(@"+++++ 線程1 end");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"----- 線程2 start");
        pthread_mutex_lock(&mutex);
        sleep(3);
        pthread_mutex_unlock(&mutex);
        NSLog(@"----- 線程2 end");
    });
}
複製代碼

條件鎖

1.NSCondition

NSCondition 的對象其實是做爲一個鎖和線程檢查器,鎖主要是爲了檢測條件時保護數據源,執行條件引起的任務。線程檢查器主要是根據條件決定是否繼續運行線程,即線程是否被阻塞。函數

- (NSArray*)removeLastImage:(NSMutableArray *)images {
    if (images.count > 0) {
        NSCondition *condition = [[NSCondition alloc] init];
        [condition lock];
        [images removeLastObject];
        [condition unlock];
        return images.copy;
    }else{
        return NULL;
    }
}
複製代碼

NSCondition能夠給每一個線程分別加鎖,加鎖後不影響其餘線程進入臨界區oop

- (void)testLock{
    self.conditionArray = [NSMutableArray array];
    self.condition = [[NSCondition alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.condition lock];
        if (self.conditionArray.count == 0) {
            NSLog(@"等待制做數組");
            [self.condition wait];
        }
        NSLog(@"獲取對象進行操做:%@",self.conditionArray[0]);
        [self.condition unlock];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.condition lock];
        id obj = @"xxxxxxx";
        [self.conditionArray addObject:obj];
        NSLog(@"建立了一個對象:%@",obj);
        [self.condition signal];
        [self.condition unlock];
    });
}
複製代碼
  • 等待2秒
NSCondition *cLock = [NSCondition new];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"start");
    [cLock lock];
    [cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
    NSLog(@"線程1");
    [cLock unlock];
});
複製代碼
  • 喚醒一個等待線程
NSCondition *cLock = [NSCondition new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"線程1加鎖成功");
    [cLock wait];
    NSLog(@"線程1");
    [cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"線程2加鎖成功");
    [cLock wait];
    NSLog(@"線程2");
    [cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(2);
    NSLog(@"喚醒一個等待的線程");
    [cLock signal];
});
複製代碼
  • 喚醒全部等待的線程
.........    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(2);
    NSLog(@"喚醒全部等待的線程");
    [cLock broadcast];
});
複製代碼

2.NSContionLock

只有 condition 參數與初始化時候的 condition 相等,才能正確進行加鎖操做。而 unlockWithCondition: 並非當 Condition 符合條件時才解鎖,而是解鎖以後,修改 Condition 的值。性能

- (void)testLock{
    NSConditionLock *conditionLock = [[NSConditionLock alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0;i < 6;i++) {
            [conditionLock lock];
            NSLog(@"thread1:%d",i);
            sleep(2);
            [conditionLock unlockWithCondition:i];
        }
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionLock lockWhenCondition:2];
        NSLog(@"thread2");
        [conditionLock unlock];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionLock lockWhenCondition:3];
        NSLog(@"thread3");
        [conditionLock unlock];
    });
}
複製代碼

遞歸鎖

1.NSRecursiveLock

NSRecursiveLock 是遞歸鎖,他和 NSLock 的區別在於,NSRecursiveLock 能夠在一個線程中重複加鎖(反正單線程內任務是按順序執行的,不會出現資源競爭問題),NSRecursiveLock 會記錄上鎖和解鎖的次數,當兩者平衡的時候,纔會釋放鎖,其它線程才能夠上鎖成功。spa

- (void)lock4 {
    NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^TestMethod)(int);
        TestMethod = ^(int value) {
            [recursiveLock lock];
            if (value > 0) {
                NSLog(@"加鎖層數: %d",value);
                TestMethod(--value);
            }
            NSLog(@"程序退出!");
            [recursiveLock unlock];
        };
        TestMethod(3);
    });
}
複製代碼

pthread

- (void)lock6 {
    __block pthread_mutex_t recursiveMutex;
    pthread_mutexattr_t recursiveMutexattr;
    pthread_mutexattr_init(&recursiveMutexattr);
    pthread_mutexattr_settype(&recursiveMutexattr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&recursiveMutex, &recursiveMutexattr);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^TestMethod)(int);
        TestMethod = ^(int  value) {
            pthread_mutex_lock(&recursiveMutex);
            if (value > 0) {
                NSLog(@"加鎖層數: %d",value);
                sleep(1);
                TestMethod(--value);
            }
            NSLog(@"程序退出!");
            pthread_mutex_unlock(&recursiveMutex);
        };
        TestMethod(3);
    });
}
複製代碼

自旋鎖

是用於多線程同步的一種鎖,線程反覆檢查鎖變量是否可用。因爲線程在這一過程當中保持執行,所以是一種忙等待。一旦獲取了自旋鎖,線程會一直保持該鎖,直至顯式釋放自旋鎖。 自旋鎖避免了進程上下文的調度開銷,所以對於線程只會阻塞很短期的場合是有效的。

1.OSSpinLock

__block OSSpinLock oslock = OS_SPINLOCK_INIT;
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"線程1 準備上鎖");
    OSSpinLockLock(&oslock);
    sleep(4);
    NSLog(@"線程1");
    OSSpinLockUnlock(&oslock);
    NSLog(@"線程1 解鎖成功");
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"線程2 準備上鎖");
    OSSpinLockLock(&oslock);
    NSLog(@"線程2");
    OSSpinLockUnlock(&oslock);
    NSLog(@"線程2 解鎖成功");
});
複製代碼

信號量

1.dispatch_semaphore

若是獲取不到鎖,會將當前線程阻塞、休眠,直到其餘線程釋放鎖時,會喚醒當前線程。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"task A");
    dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"task B");
    dispatch_semaphore_signal(semaphore);
});
複製代碼

分佈鎖

1.NSDistributedLock

NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/lock.lock"];
while (![lock tryLock]){
    sleep(1);
}
//do something
[lock unlock];
複製代碼

死鎖

死鎖是因爲多個線程(進程)在執行過程當中,由於爭奪資源而形成的互相等待現象,你能夠理解爲卡主了。產生死鎖的必要條件有四個:

  • 互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。若是此時還有其它進程請求資源,則請求者只能等待,直至佔有資源的進程用畢釋放。
  • 請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對本身已得到的其它資源保持不放。
  • 不可剝奪條件:指進程已得到的資源,在未使用完以前,不能被剝奪,只能在使用完時由本身釋放。
  • 環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。
NSLock *rLock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    static void (^TestBlock)(int);
    TestBlock = ^(int value) {
        [rLock lock];
        if (value > 0) {
            NSLog(@"線程%d", value);
            TestBlock(value - 1);
        }
        [rLock unlock];
    };
    TestBlock(4);
});
複製代碼

最多見的就是 同步函數 + 主隊列 的組合,本質是隊列阻塞。
死鎖是因爲阻塞閉環形成的,那麼咱們只用消除其中一個因素,就能打破這個閉環,避免死鎖。

參考文檔

理解GCD死鎖
iOS 中幾種經常使用的鎖總結
iOS 中常見的幾種鎖-代碼示例
iOS進階-細數iOS中的鎖

關於鎖介紹和實戰使用介紹就到此完畢,後面有相關再補充,寫文章不容易,還請點個**小星星**傳送門

相關文章
相關標籤/搜索