iOS 中常見的幾種鎖-程式碼示例

極客學偉發表於2018-07-16

iOS 中常見的幾種鎖介紹-示例

常用的各類鎖效能比較

常用的各類鎖效能比較

文中Demo 均實現在 XWInterviewDemos

1. iOS中的互斥鎖

在程式設計中,引入物件互斥鎖的概念,來保證共享資料操作的完整性。每個物件都對應於一個可稱為“互斥鎖”的標記,這個標記用來保證在任一時刻,只能有一個執行緒訪問物件。

1.1 @synchronized (self)

- (void)lock1 {
    @synchronized (self) {
        // 加鎖操作
    }
}
複製程式碼

1.2 NSLock

- (void)lock2 {
    NSLock *xwlock = [[NSLock alloc] init];
    XWLogBlock logBlock = ^ (NSArray *array) {
        [xwlock lock];
        for (id obj in array) {
            NSLog(@"%@",obj);
        }
        [xwlock unlock];
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSArray *array = @[@1,@2,@3];
        logBlock(array);
    });
}
複製程式碼

1.3 pthread

pthread除了建立互斥鎖,還可以建立遞迴鎖、讀寫鎖、once等鎖


    __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");
    });
}
複製程式碼

2. iOS中的遞迴鎖

同一個執行緒可以多次加鎖,不會造成死鎖

死鎖->
- (void)lock5 {
    NSLock *commonLock = [[NSLock alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^XWRecursiveBlock)(int);
        
        XWRecursiveBlock = ^(int  value) {
            [commonLock lock];
            if (value > 0) {
                NSLog(@"加鎖層數: %d",value);
                sleep(1);
                XWRecursiveBlock(--value);
            }
            NSLog(@"程式退出!");
            [commonLock unlock];
        };
        
        XWRecursiveBlock(3);
    });
}
複製程式碼
<-死鎖

2.1 NSRecursiveLock

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

2.2 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 (^XWRecursiveBlock)(int);
        
        XWRecursiveBlock = ^(int  value) {
            pthread_mutex_lock(&recursiveMutex);
            if (value > 0) {
                NSLog(@"加鎖層數: %d",value);
                sleep(1);
                XWRecursiveBlock(--value);
            }
            NSLog(@"程式退出!");
            pthread_mutex_unlock(&recursiveMutex);
        };
        
        XWRecursiveBlock(3);
    });
}
複製程式碼

3. 訊號量

訊號量(Semaphore),有時被稱為訊號燈,是在多執行緒環境下使用的一種設施,是可以用來保證兩個或多個關鍵程式碼段不被併發呼叫。在進入一個關鍵程式碼段之前,執行緒必須獲取一個訊號量;一旦該關鍵程式碼段完成了,那麼該執行緒必須釋放訊號量。其它想進入該關鍵程式碼段的執行緒必須等待直到第一個執行緒釋放訊號量

3.1 dispatch_semaphore_t

實現 GCD 下同步
- (void)lock7 {
//    dispatch_semaphore_create //建立一個訊號量 semaphore
//    dispatch_semaphore_signal //傳送一個訊號 訊號量+1
//    dispatch_semaphore_wait   // 等待訊號 訊號量-1
    
    /// 需求: 非同步執行緒的兩個操作同步執行
    
    dispatch_semaphore_t semaphone = dispatch_semaphore_create(0);
    NSLog(@"start");
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"async .... ");
        sleep(5);
        /// 執行緒資源 + 1
        dispatch_semaphore_signal(semaphone);//訊號量+1
    });
    /// 當前執行緒資源數量為 0 ,等待啟用
    dispatch_semaphore_wait(semaphone, DISPATCH_TIME_FOREVER);
    NSLog(@"end");
}
複製程式碼

3.2 pthread

- (void)lock8 {
    __block pthread_mutex_t semaphore = PTHREAD_MUTEX_INITIALIZER;
    __block pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    
    NSLog(@"start");
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        pthread_mutex_lock(&semaphore);
        NSLog(@"async...");
        sleep(5);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&semaphore);
    });
    
    pthread_cond_wait(&cond, &semaphore);
    NSLog(@"end");
}
複製程式碼

4. 條件鎖

4.1 NSCondition

NSCondition 的物件實際上是作為一個鎖和執行緒檢查器,鎖主要是為了檢測條件時保護資料來源,執行條件引發的任務。執行緒檢查器主要是根據條件決定是否繼續執行執行緒,即執行緒是否被阻塞。

  • NSCondition同樣實現了NSLocking協議,所以它和NSLock一樣,也有NSLocking協議的lock和unlock方法,可以當做NSLock來使用解決執行緒同步問題,用法完全一樣。
- (NSMutableArray *)removeLastImage:(NSMutableArray *)images {
    if (images.count > 0) {
        NSCondition *condition = [[NSCondition alloc] init];
        [condition lock];
        [images removeLastObject];
        [condition unlock];
        NSLog(@"removeLastImage %@",images);
        return images;
    }else{
        return NULL;
    }
}
複製程式碼
  • 同時,NSCondition提供更高階的用法。wait和signal,和條件訊號量類似。

  • NSCondition和NSLock、@synchronized等是不同的是,NSCondition可以給每個執行緒分別加鎖,加鎖後不影響其他執行緒進入臨界區。這是非常強大。

- (void)lock10 {
    self.conditionArray = [NSMutableArray array];
    self.xwCondition = [[NSCondition alloc] init];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.xwCondition lock];
        if (self.conditionArray.count == 0) {
            NSLog(@"等待制作陣列");
            [self.xwCondition wait];
        }
        id obj = self.conditionArray[0];
        NSLog(@"獲取物件進行操作:%@",obj);
        [self.xwCondition unlock];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.xwCondition lock];
        id obj = @"極客學偉";
        [self.conditionArray addObject:obj];
        NSLog(@"建立了一個物件:%@",obj);
        [self.xwCondition signal];
        [self.xwCondition unlock];
    });
}
複製程式碼

4.2 NSConditionLock

- (void)lock11 {
    NSConditionLock *conditionLock = [[NSConditionLock alloc] init];
    NSMutableArray *arrayM = [NSMutableArray array];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionLock lock];
        
        for (int i = 0; i < 6; i++) {
            [arrayM addObject:@(i)];
            NSLog(@"非同步下載第 %d 張圖片",i);
            sleep(1);
            if (arrayM.count == 4) {
                [conditionLock unlockWithCondition:4];
            }
        }
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [conditionLock lockWhenCondition:4];
        NSLog(@"已經獲取到4張圖片->主執行緒渲染");
        [conditionLock unlock];
    });
}
複製程式碼

4.2 pthread POSIX Conditions

- (void)lock12 {
    __block pthread_mutex_t mutex;
    __block pthread_cond_t condition;
    
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&condition, NULL);
    
    NSMutableArray *arrayM = [NSMutableArray array];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        pthread_mutex_lock(&mutex);
        
        for (int i = 0; i < 6; i++) {
            [arrayM addObject:@(i)];
            NSLog(@"非同步下載第 %d 張圖片",i);
            sleep(1);
            if (arrayM.count == 4) {
                pthread_cond_signal(&condition);
            }
        }
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        pthread_cond_wait(&condition, &mutex);
        NSLog(@"已經獲取到4張圖片->主執行緒渲染");
        pthread_mutex_unlock(&mutex);
    });
}
複製程式碼

5. 讀寫鎖

讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高併發性,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數為實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。

5.1 dispatch_barrier_async / dispatch_barrier_sync

有一個需求,如圖:

barrierdemo

任務1,2,3 均執行完畢執行任務0,然後執行任務4,5,6.
- (void)lock13 {
    dispatch_queue_t queue = dispatch_queue_create("com.qiuxuewei.brrier", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任務1 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務2 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務3 -- %@",[NSThread currentThread]);
    });
    dispatch_barrier_sync(queue, ^{
        NSLog(@"任務0 -- %@",[NSThread currentThread]);
        for (int i = 0; i < 100; i++) {
            if (i % 30 == 0) {
                NSLog(@"任務0 --- log:%d -- %@",i,[NSThread currentThread]);
            }
        }
    });
    NSLog(@"dispatch_barrier_sync  down!!!");
    dispatch_async(queue, ^{
        NSLog(@"任務4 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務5 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務6 -- %@",[NSThread currentThread]);
    });
}
複製程式碼
dispatch_barrier_asyncdispatch_barrier_sync 的異同
共同點
  • 等待它前面的執行完才執行自己的任務
  • 等待自己任務執行結束才執行後面的任務
不同點
  • dispatch_barrier_async 將自己的任務插入到佇列之後會繼續將後面的操作插入到佇列,按照規則先執行前面佇列的任務,等自己佇列執行完畢,再執行後面佇列的任務
  • dispatch_barrier_sync 將自己的任務插入到佇列之後,先等待自己的任務執行完畢才會執行後面操作插入到佇列,再執行後面佇列的任務。

5.2 pthread

- (void)lock14 {
    __block pthread_rwlock_t rwlock;
    pthread_rwlock_init(&rwlock, NULL);
    __block NSMutableArray *arrayM = [NSMutableArray array];
    
    XWBlock writeBlock = ^ (NSString *str) {
        NSLog(@"開啟寫操作");
        pthread_rwlock_wrlock(&rwlock);
        [arrayM addObject:str];
        sleep(2);
        pthread_rwlock_unlock(&rwlock);
    };
    
    XWVoidBlock readBlock = ^ {
        NSLog(@"開啟讀操作");
        pthread_rwlock_rdlock(&rwlock);
        sleep(1);
        NSLog(@"讀取資料:%@",arrayM);
        pthread_rwlock_unlock(&rwlock);
    };
    
    for (int i = 0; i < 5; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            writeBlock([NSString stringWithFormat:@"%d",i]);
        });
    }
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            readBlock();
        });
    }
}
複製程式碼

6.自旋鎖

bool lock = false; // 一開始沒有鎖上,任何執行緒都可以申請鎖  
do {  
    while(lock); // 如果 lock 為 true 就一直死迴圈,相當於申請鎖
    lock = true; // 掛上鎖,這樣別的執行緒就無法獲得鎖
        Critical section  // 臨界區
    lock = false; // 相當於釋放鎖,這樣別的執行緒可以進入臨界區
        Reminder section // 不需要鎖保護的程式碼        
}

複製程式碼

6.1 OSSpinLock

YYKit作者的文章 不再安全的 OSSpinLock有說到這個自旋鎖存在優先順序反轉的問題。

6.2 os_unfair_lock

自旋鎖已經不再安全,然後蘋果又整出來個 os_unfair_lock_t ,這個鎖解決了優先順序反轉的問題。

    os_unfair_lock_t unfairLock;
    unfairLock = &(OS_UNFAIR_LOCK_INIT);
    os_unfair_lock_lock(unfairLock);
    os_unfair_lock_unlock(unfairLock);
複製程式碼

7. property - atomic / nonatomic

atomic 修飾的物件,系統會保證在其自動生成的 getter/setter 方法中的操作是完整的,不受其他執行緒的影響。例如 A 執行緒在執行 getter 方法時,B執行緒執行了 setter 方法,此時 A 執行緒依然會得到一個完整無損的物件。

atomic

預設修飾符 會保證CPU能在別的執行緒訪問這個屬性之前先執行完當前操作 讀寫速度慢 執行緒不安全 - 如果有另一個執行緒 D 同時在調[name release],那可能就會crash,因為 release 不受 getter/setter 操作的限制。也就是說,這個屬性只能說是讀/寫安全的,但並不是執行緒安全的,因為別的執行緒還能進行讀寫之外的其他操作。執行緒安全需要開發者自己來保證。

nonatomic

不預設 速度更快 執行緒不安全 如果兩個執行緒同時訪問會出現不可預料的結果。

8. Once 原子操作

8.1 GCD


- (id)lock15 {
    static id shareInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!shareInstance) {
            shareInstance = [[NSObject alloc] init];
        }
    });
    return shareInstance;
}
複製程式碼

8.2 pthread

- (void)lock16 {
    pthread_once_t once = PTHREAD_ONCE_INIT;
    pthread_once(&once, lock16Func);
}
void lock16Func() {
     static id shareInstance;
    shareInstance = [[NSObject alloc] init];
}
複製程式碼

相關文章