iOS Sharing #03 | 2019-04-06

Adrenine發表於2019-03-22

目錄

1、atomic關鍵字內部使用的是什麼鎖?

2、序列同步、序列非同步、併發同步、併發非同步各自會開幾條執行緒?

3、為什麼需要在主執行緒更新UI?

4、iOS中如何用多執行緒實現多讀單寫?

5、iOS多執行緒中有多少種方式可以做到等待前面執行緒執行完畢再執行後面的執行緒?


1、atomic關鍵字內部使用的是什麼鎖?

答:

首先了解一些基本概念:

  • 臨界區:指的是一塊對公共資源進行訪問的程式碼,並非一種機制或是演算法。

  • 自旋鎖:是用於多執行緒同步的一種鎖,執行緒反覆檢查鎖變數是否可用。由於執行緒在這一過程中保持執行,因此是一種忙等待。一旦獲取了自旋鎖,執行緒會一直保持該鎖,直至顯式釋放自旋鎖。 自旋鎖避免了程式上下文的排程開銷,因此對於執行緒只會阻塞很短時間的場合是有效的。

  • 互斥鎖(Mutex):是一種用於多執行緒程式設計中,防止兩條執行緒同時對同一公共資源(比如全域性變數)進行讀寫的機制。該目的通過將程式碼切片成一個一個的臨界區而達成。

  • 讀寫鎖:是計算機程式的併發控制的一種同步機制,也稱“共享-互斥鎖”、多讀者-單寫者鎖) 用於解決多執行緒對公共資源讀寫問題。讀操作可併發重入,寫操作是互斥的。 讀寫鎖通常用互斥鎖、條件變數、訊號量實現。

  • 訊號量(semaphore):是一種更高階的同步機制,互斥鎖可以說是semaphore在僅取值0/1時的特例。訊號量可以有更多的取值空間,用來實現更加複雜的同步,而不單單是執行緒間互斥。

  • 條件鎖:就是條件變數,當程式的某些資源要求不滿足時就進入休眠,也就是鎖住了。當資源被分配到了,條件鎖開啟,程式繼續執行。

  • 死鎖:指兩個或兩個以上的程式在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去,這些永遠在互相等待的程式稱為死鎖程式。

  • 輪詢(Polling):一種CPU決策如何提供周邊裝置服務的方式,又稱“程控輸出入”。輪詢法的概念是,由CPU定時發出詢問,依序詢問每一個周邊裝置是否需要其服務,有即給予服務,服務結束後再問下一個周邊,接著不斷周而復始。

鎖的型別:

  • 互斥鎖

    • NSLock
    • pthread_mutex
    • pthread_mutex(recursive)遞迴鎖
    • @synchronized
  • 自旋鎖

    • OSSpinLock
    • os_unfair_lock
  • 讀寫鎖

    • pthread_rwlock
  • 遞迴鎖

    • NSRecursiveLock

    • pthread_mutex(recursive)(見上)

  • 條件鎖

    • NSCondition
    • NSConditionLock
  • 訊號量

    • dispatch_semaphore

time

//10000000
OSSpinLock:                 112.38 ms
dispatch_semaphore:         160.37 ms
os_unfair_lock:             208.87 ms
pthread_mutex:              302.07 ms
NSCondition:                320.11 ms
NSLock:                     331.80 ms
pthread_rwlock:             360.81 ms
pthread_mutex(recursive):   512.17 ms
NSRecursiveLock:            667.55 ms
NSConditionLock:            999.91 ms
@synchronized:             1654.92 ms

//1000
OSSpinLock:                   0.02 ms
dispatch_semaphore:           0.03 ms
os_unfair_lock:               0.04 ms
pthread_mutex:                0.06 ms
NSLock:                       0.06 ms
pthread_rwlock:               0.07 ms
NSCondition:                  0.07 ms
pthread_mutex(recursive):     0.09 ms
NSRecursiveLock:              0.12 ms
NSConditionLock:              0.18 ms
@synchronized:                0.33 ms
複製程式碼

atomic

atomic使用的是自旋鎖,主要用於賦值操作等輕量操作(雜湊表,引用計數,弱引用指標賦值),而互斥鎖一般都是鎖執行緒,比如單例。


2、序列同步、序列非同步、併發同步、併發非同步各自會開幾條執行緒?

答:

首先了解一些基本概念:

  • 同步:只能在當前執行緒中執行任務,不具備開啟新執行緒的能力
  • 非同步:可以在新的執行緒中執行任務,具備開啟新執行緒的能力
  • 序列:一個任務執行完畢後,再執行下一個任務
  • 併發:多個任務併發(同時)執行

iOS Sharing #03 | 2019-04-06
iOS Sharing #03 | 2019-04-06

iOS Sharing #03 | 2019-04-06

所以:

  • 序列同步不開新執行緒
  • 序列非同步開啟一條新執行緒
  • 併發同步不開新執行緒
  • 併發非同步開啟多條

3、為什麼需要在主執行緒更新UI?

答:
  • 安全

在非主執行緒中更新UI就會有多個執行緒同時操作一個控制元件的可能,造成最後更新的結果不符合預期

  • 效率

多執行緒本身就是為了併發處理以達到高效的目的,但是重新整理UI使用併發會造成安全問題,要解決上面的安全問題,那就需要給控制元件加鎖,但是加鎖必然會造成額外的開銷,同時開新的執行緒本身就有一定的開銷,所以不如直接在主執行緒中執行更新操作。


4、iOS中如何用多執行緒實現多讀單寫?

答:
@interface CustomDictionary ()

//多執行緒需要訪問的資料量
@property (nonatomic, strong) NSMutableDictionary *dataDic;

@end

//模擬場景,允許多個執行緒同時訪問字典,但是隻有一個執行緒可以寫字典
@implementation CustomDictionary {
    //定義一個併發佇列
    dispatch_queue_t _concurrent_queue;

}

- (instancetype)init {
    if (self = [super init]) {
        _concurrent_queue = dispatch_queue_create("com.mf.read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        _dataDic = @{}.mutableCopy;
    }
    
    return self;
}

// 讀取資料,併發操作
- (id)objectForKey:(NSString *)key {
    __block id obj;
    //同步讀取資料
    dispatch_sync(_concurrent_queue, ^{
        obj = [self.dataDic objectForKey:key];
    });
    
    return obj;
    
}

// 寫入資料,非同步柵欄
- (void)setObject:(id)obj forKey:(NSString *)key {
    //非同步柵欄呼叫設定資料
    dispatch_barrier_async(_concurrent_queue, ^{
        [self.dataDic setObject:obj forKey:key];
    });
}

@end

複製程式碼

5、iOS多執行緒中有多少種方式可以做到等待前面執行緒執行完畢再執行後面的執行緒?

答:
  • barrier
dispatch_queue_t queue = dispatch_queue_create("com.mf.barrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    NSLog(@"1");
});
    
dispatch_async(queue, ^{
    NSLog(@"2");
});
    
dispatch_barrier_async(queue, ^{
    NSLog(@"等待任務1,2上面執行完畢");
});
    
dispatch_async(queue, ^{
    NSLog(@"3");
});
    
dispatch_async(queue, ^{
    NSLog(@"4");
});
複製程式碼
  • group notify
// 全域性變數group
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 進入組(進入組和離開組必須成對出現, 否則會造成死鎖)
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
    NSLog(@"1");
    //離開組
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
    NSLog(@"2");
    dispatch_group_leave(group);
});

dispatch_group_notify(group, queue, ^{  // 監聽組裡所有執行緒完成的情況
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務1,2已完成");
    });    
});
複製程式碼
  • NSOperationQueue Dependency
//建立佇列
    NSOperationQueue *queue=[[NSOperationQueue alloc] init];
    //建立操作
    NSBlockOperation *operation1=[NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"執行第1次操作,執行緒:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2=[NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"執行第2次操作,執行緒:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation3=[NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"執行第3次操作,執行緒:%@",[NSThread currentThread]);
    }];
    //新增依賴
    [operation1 addDependency:operation2];
    [operation2 addDependency:operation3];
    //將操作新增到佇列中去
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
複製程式碼
  • semaphore
/*
比如說我們需要請求三張元素圖,拼合成一張海報。我們需要先對元素圖進行請求而後才能合成海報,這就形成了依賴關係。我們通過semaphore限制資源數為3,供請求元素圖使用,待請求完成後,釋放訊號量,便能走到合成的耗時操作。
*/

//建立訊號量,引數:訊號量的初值,如果小於0則會返回NULL
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
    
//元素圖1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    //等待降低訊號量
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"請求第1張元素圖");
    sleep(1);
    NSLog(@"第1張元素圖Get");
    //提高訊號量
    dispatch_semaphore_signal(semaphore);
});
    
//元素圖2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"請求第2張元素圖");
    sleep(1);
    NSLog(@"第2張元素圖Get");
    dispatch_semaphore_signal(semaphore);
});
    
//元素圖3
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"請求第3張元素圖");
    sleep(1);
    NSLog(@"第3張元素圖Get");
    dispatch_semaphore_signal(semaphore);
});
    
//合成海報
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"合成海報");
    sleep(1);
    NSLog(@"海報Get");
    dispatch_semaphore_signal(semaphore);
});
複製程式碼

注意:
正常的使用順序是先降低然後再提高,這兩個函式通常成對使用。


倉庫

本篇相關程式碼


聯絡方式

郵箱: adrenine@163.com

郵箱: holaux@gmail.com

郵箱: ledahapple@icloud.com

相關文章