iOS探索 多線程之GCD應用

歡迎閱讀iOS探索系列(按序閱讀食用效果更加)程序員

寫在前面

GCD全稱是Grand Central Dispatch,它是純 C 語言,而且提供了很是多強大的函數面試

GCD的優點:安全

  • GCD 是蘋果公司爲多核的並行運算提出的解決方案
  • GCD 會自動利用更多的CPU內核(好比雙核、四核)
  • GCD 會自動管理線程的生命週期(建立線程、調度任務、銷燬線程)
  • 程序員只須要告訴 GCD 想要執行什麼任務,不須要編寫任何線程管理代碼

咱們要關注的點就是:GCD的核心——將任務添加到隊列,而且指定執行任務的函數bash

1、dispatch_block_t

dispatch_block_t block = ^{
    NSLog(@"GCD的基本使用");
};
dispatch_queue_t queue = dispatch_queue_create("com.Felix", NULL);
dispatch_async(queue, block);
複製代碼

這串代碼最能體現GCD的核心:網絡

  • dispatch_block_t使用block封裝任務
  • dispatch_queue_t建立隊列
  • dispatch_async將任務添加到隊列

上述代碼一般也寫成這種形式多線程

dispatch_queue_t queue = dispatch_queue_create("com.Felix", NULL);
dispatch_async(queue, ^{
    NSLog(@"GCD的基本使用");
});
複製代碼

2、dispatch_sync & dispatch_async

多線程執行任務分爲dispatch_sync同步執行任務和dispatch_async異步執行:併發

  • dispatch_sync同步執行
    • 必須等待當前語句執行完畢,纔會執行下一條語句
    • 不會開啓線程
    • 在當前線程執行block的任務
  • dispatch_async異步執行
    • 不用等待當前語句執行完畢,就能夠執行下一條語句
    • 會開啓線程執行block任務
    • 異步是多線程的代名詞

3、dispatch_queue_t

多線程中隊列分爲 串行隊列( Serial Dispatch Queue)和併發隊列( Concurrent Dispatch Queue):

  • 串行隊列:線程執行只能依次逐一前後有序的執行,等待上一個執行完再執行下一個
    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL)建立串行隊列
    • 亦可使用dispatch_queue_create("xxx", NULL)建立串行隊列(GCD底層會講到)
  • 主隊列:綁定主線程,全部任務都在主線程中執行、通過特殊處理的串行的隊列
    • 使用dispatch_get_main_queue()獲取主隊列
  • 併發隊列:線程能夠同時一塊兒執行,不須要等待上一個執行完就能執行下一個任務
    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);建立併發隊列
  • 全局隊列:系統提供的併發隊列
    • 最簡單的是使用dispatch_get_global_queue(0, 0)獲取系統提供的併發隊列
    • 第一個參數是優先級枚舉值,默認優先級爲DISPATCH_QUEUE_PRIORITY_DEFAULT=0
    • 優先級從高到低依次爲DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULTDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUND

串行/併發和同步/異步的排列組合

主隊列和全局隊列單獨考慮,組合結果以總結表格爲準 app

1.串行+同步

任務一個接一個執行,不開闢線程異步

- (void)test {
    NSLog(@"主線程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"串行&同步線程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步線程0-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步線程1-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼

2.串行+異步

任務一個接一個執行,會開闢線程socket

- (void)test {
    NSLog(@"主線程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"串行&異步線程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&異步線程0-<NSThread: 0x6000009b8880>{number = 6, name = (null)}
// 串行&異步線程1-<NSThread: 0x6000009b8880>{number = 6, name = (null)}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼

3.併發+同步

任務一個接一個執行,不開闢線程

- (void)test {
    NSLog(@"主線程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"併發&同步線程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步線程0-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步線程1-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼

4.併發+異步

任務亂序執行,開闢線程

- (void)test {
    NSLog(@"主線程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"併發&異步線程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600002a9cd40>{number = 1, name = main}
// 併發&異步線程1-<NSThread: 0x600002a9ca40>{number = 5, name = (null)}
// 併發&異步線程0-<NSThread: 0x600002add3c0>{number = 4, name = (null)}
// ...亂序輸出
--------------------輸出結果:-------------------
複製代碼

下面來看一下主隊列全局隊列的使用狀況:

5.主隊列+同步

相互等待,形成死鎖

- (void)test {
    NSLog(@"主線程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"主隊列&同步線程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600001980d40>{number = 1, name = main}
// 崩潰...
--------------------輸出結果:-------------------
複製代碼

6.主隊列+異步

任務一個接一個執行,不開闢線程

- (void)test {
    NSLog(@"主線程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"主隊列&異步線程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600001980d40>{number = 1, name = main}
// 主隊列&異步線程0-<NSThread: 0x600001980d40>{number = 1, name = main}
// 主隊列&異步線程1-<NSThread: 0x600001980d40>{number = 1, name = main}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼

7.全局隊列+同步

任務一個接一個執行,不開闢線程(同併發+同步)

- (void)test {
    NSLog(@"主線程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"全局隊列&同步線程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600001980d40>{number = 1, name = main}
// 全局隊列&同步線程0-<NSThread: 0x60000099c080>{number = 1, name = main}
// 全局隊列&同步線程1-<NSThread: 0x60000099c080>{number = 1, name = main}
// ...按順序輸出
--------------------輸出結果:-------------------
複製代碼

8.全局隊列+異步

任務亂序執行,開闢線程(同併發+異步)

- (void)test {
    NSLog(@"主線程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"全局隊列&異步線程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------輸出結果:-------------------
// 主線程-<NSThread: 0x600001cd4ec0>{number = 1, name = main}
// 全局隊列&異步線程2-<NSThread: 0x600001c8eb00>{number = 3, name = (null)}
// 全局隊列&異步線程3-<NSThread: 0x600001c82b80>{number = 7, name = (null)}
// ...亂序輸出
--------------------輸出結果:-------------------
複製代碼

總結一下:

執行\隊列 串行隊列 併發隊列 主隊列 全局隊列
同步執行 按序執行,不開闢線程 按序執行,不開闢線程 死鎖 按序執行,不開闢線程
異步執行 按序執行,開闢線程 亂序執行,開闢線程 按序執行,不開闢線程 亂序執行,開闢線程

4、dispatch_after

dispatch_after表示在某隊列中的block延遲執行

應用場景:在主隊列上延遲執行一項任務,如viewDidload以後延遲1s,提示一個alertview(是延遲加入到隊列,而不是延遲執行)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"2秒後輸出");
});
複製代碼

5、dispatch_once

dispatch_once保證在App運行期間,block中的代碼只執行一次

應用場景:單例method-Swizzling

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    //建立單例、method swizzled或其餘任務
});
複製代碼

6、dispatch_apply

dispatch_apply將指定的Block追加到指定的隊列中重複執行,並等到所有的處理執行結束——至關於線程安全的for循環

應用場景:用來拉取網絡數據後提早算出各個控件的大小,防止繪製時計算,提升表單滑動流暢性

  • 添加到串行隊列中——按序執行
  • 添加到主隊列中——死鎖
  • 添加到併發隊列中——亂序執行
  • 添加到全局隊列中——亂序執行
- (void)test {
    /** param1:重複次數 param2:追加的隊列 param3:執行任務 */
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
    NSLog(@"dispatch_apply前");
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"dispatch_apply的線程%zu-%@", index, [NSThread currentThread]);
    });
    NSLog(@"dispatch_apply後");
}
--------------------輸出結果:-------------------
// dispatch_apply前
// dispatch_apply的線程0-<NSThread: 0x6000019f8d40>{number = 1, name = main}
// ...是否按序輸出與串行隊列仍是併發隊列有關
// dispatch_apply後
--------------------輸出結果:-------------------
複製代碼

7、dispatch_group_t

dispatch_group_t:調度組將任務分組執行,能監放任務組完成,並設置等待時間

應用場景:多個接口請求以後刷新頁面

1.dispatch_group_async

dispatch_group_notifydispatch_group_async執行結束以後會受到通知

- (void)test {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"請求一完成");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"請求二完成");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新頁面");
    });
}
--------------------輸出結果:-------------------
// 請求二完成
// 請求一完成
// 刷新頁面
--------------------輸出結果:-------------------
複製代碼

2.dispatch_group_enter & dispatch_group_leave

dispatch_group_enterdispatch_group_leave成對出現,使進出組的邏輯更加清晰

- (void)test {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"請求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"請求二完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新頁面");
    });
}
--------------------輸出結果:-------------------
// 請求二完成
// 請求一完成
// 刷新頁面
--------------------輸出結果:-------------------
複製代碼

調度組要注意搭配使用,必須先進組再出組,缺一不可

3.dispatch_group_wait使用

long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

  • group:須要等待的調度組
  • timeout:等待的超時時間(即等多久)
    • 設置爲DISPATCH_TIME_NOW意味着不等待直接斷定調度組是否執行完畢
    • 設置爲DISPATCH_TIME_FOREVER則會阻塞當前調度組,直到調度組執行完畢
  • 返回值:爲long類型
    • 返回值爲0——在指定時間內調度組完成了任務
    • 返回值不爲0——在指定時間內調度組沒有按時完成任務

將上述調度組代碼進行改寫

- (void)test {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"請求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"請求二完成");
        dispatch_group_leave(group);
    });
    
    long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//    long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
    NSLog(@"timeout=%ld", timeout);
    if (timeout == 0) {
        NSLog(@"按時完成任務");
    } else {
        NSLog(@"超時");
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新頁面");
    });
}
--------------------輸出結果:-------------------
// timeout=49
// 請求一完成
// 請求二完成
// 超時
// 刷新頁面
--------------------輸出結果:-------------------
複製代碼

8、dispatch_barrier_sync & dispatch_barrier_async

應用場景:同步鎖

前文已經提過 併發執行異步隊列會開闢線程,而任務也會由於任務複雜度和cpu的調度致使各個亂序執行完畢,好比上圖中的 任務3明明是先於 任務4執行,可是晚於 任務4執行完畢

此時GCD就提供了兩個API——dispatch_barrier_syncdispatch_barrier_async,使用這兩個API就能將多個任務進行分組——等柵欄前追加到隊列中的任務執行完畢後,再將柵欄後的任務追加到隊列中。簡而言之,就是先執行柵欄前任務,再執行柵欄任務,最後執行柵欄後任務

1.串行隊列使用柵欄函數

- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"開始——%@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延遲2s的任務1——%@", [NSThread currentThread]);
    });
    NSLog(@"第一次結束——%@", [NSThread currentThread]);
    
// dispatch_barrier_async(queue, ^{
// NSLog(@"----------柵欄任務----------%@", [NSThread currentThread]);
// });
// NSLog(@"柵欄結束——%@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"延遲1s的任務2——%@", [NSThread currentThread]);
    });
    NSLog(@"第二次結束——%@", [NSThread currentThread]);
}
複製代碼

不使用柵欄函數

開始——<NSThread: 0x600001068900>{number = 1, name = main}
第一次結束——<NSThread: 0x600001068900>{number = 1, name = main}
第二次結束——<NSThread: 0x600001068900>{number = 1, name = main}
延遲2s的任務1——<NSThread: 0x600001025ec0>{number = 3, name = (null)}
延遲1s的任務2——<NSThread: 0x600001025ec0>{number = 3, name = (null)}
複製代碼

使用柵欄函數

開始——<NSThread: 0x6000001bcf00>{number = 1, name = main}
第一次結束——<NSThread: 0x6000001bcf00>{number = 1, name = main}
柵欄結束——<NSThread: 0x6000001bcf00>{number = 1, name = main}
第二次結束——<NSThread: 0x6000001bcf00>{number = 1, name = main}
延遲2s的任務1——<NSThread: 0x6000001fcf00>{number = 5, name = (null)}
----------柵欄任務----------<NSThread: 0x6000001bcf00>{number = 1, name = main}
延遲1s的任務2——<NSThread: 0x6000001fcf00>{number = 5, name = (null)}
複製代碼

柵欄函數的做用是將隊列中的任務進行分組,因此咱們只要關注任務1任務2

結論:因爲串行隊列異步執行任務是一個接一個執行完畢的,因此使用柵欄函數沒意義

2.併發隊列使用柵欄函數

- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"開始——%@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延遲2s的任務1——%@", [NSThread currentThread]);
    });
    NSLog(@"第一次結束——%@", [NSThread currentThread]);
    
// dispatch_barrier_async(queue, ^{
// NSLog(@"----------柵欄任務----------%@", [NSThread currentThread]);
// });
// NSLog(@"柵欄結束——%@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"延遲1s的任務2——%@", [NSThread currentThread]);
    });
    NSLog(@"第二次結束——%@", [NSThread currentThread]);
}
複製代碼

不使用柵欄函數

開始——<NSThread: 0x600002384f00>{number = 1, name = main}
第一次結束——<NSThread: 0x600002384f00>{number = 1, name = main}
第二次結束——<NSThread: 0x600002384f00>{number = 1, name = main}
延遲1s的任務2——<NSThread: 0x6000023ec300>{number = 5, name = (null)}
延遲2s的任務1——<NSThread: 0x60000238c180>{number = 7, name = (null)}
複製代碼

使用柵欄函數

開始——<NSThread: 0x600000820bc0>{number = 1, name = main}
第一次結束——<NSThread: 0x600000820bc0>{number = 1, name = main}
柵欄結束——<NSThread: 0x600000820bc0>{number = 1, name = main}
第二次結束——<NSThread: 0x600000820bc0>{number = 1, name = main}
延遲2s的任務1——<NSThread: 0x600000863c80>{number = 4, name = (null)}
----------柵欄任務----------<NSThread: 0x600000863c80>{number = 4, name = (null)}
延遲1s的任務2——<NSThread: 0x600000863c80>{number = 4, name = (null)}
複製代碼

結論:因爲併發隊列異步執行任務是亂序執行完畢的,因此使用柵欄函數能夠很好的控制隊列內任務執行的順序

3.dispatch_barrier_sync/dispatch_barrier_async區別

  • dispatch_barrier_async:前面的任務執行完畢纔會來到這裏
  • dispatch_barrier_sync:做用相同,可是這個會堵塞線程,影響後面的任務執行

將案例二中的dispatch_barrier_async改爲dispatch_barrier_sync

開始——<NSThread: 0x600001040d40>{number = 1, name = main}
第一次結束——<NSThread: 0x600001040d40>{number = 1, name = main}
延遲2s的任務1——<NSThread: 0x60000100ce40>{number = 6, name = (null)}
----------柵欄任務----------<NSThread: 0x600001040d40>{number = 1, name = main}
柵欄結束——<NSThread: 0x600001040d40>{number = 1, name = main}
第二次結束——<NSThread: 0x600001040d40>{number = 1, name = main}
延遲1s的任務2——<NSThread: 0x60000100ce40>{number = 6, name = (null)}
複製代碼

結論:dispatch_barrier_async能夠控制隊列中任務的執行順序,而dispatch_barrier_sync不只阻塞了隊列的執行,也阻塞了線程的執行(儘可能少用)

4.柵欄函數注意點

  1. 儘可能使用自定義的併發隊列
    • 使用全局隊列起不到柵欄函數的做用
    • 使用全局隊列時因爲對全局隊列形成堵塞,可能導致系統其餘調用全局隊列的地方也堵塞從而致使崩潰(並非只有你在使用這個隊列)
  2. 柵欄函數只能控制同一併發隊列:打個比方,平時在使用AFNetworking作網絡請求時爲何不能用柵欄函數起到同步鎖堵塞的效果,由於AFNetworking內部有本身的隊列

9、dispatch_semaphore_t

應用場景:同步當鎖, 控制GCD最大併發數

  • dispatch_semaphore_create():建立信號量
  • dispatch_semaphore_wait():等待信號量,信號量減1。當信號量< 0時會阻塞當前線程,根據傳入的等待時間決定接下來的操做——若是永久等待將等到信號(signal)才執行下去
  • dispatch_semaphore_signal():釋放信號量,信號量加1。當信號量>= 0 會執行wait以後的代碼

下面這段代碼要求使用信號量來按序輸出(固然柵欄函數能夠知足要求)

- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"當前%d----線程%@", i, [NSThread currentThread]);
        });
        // 使用柵欄函數
        // dispatch_barrier_async(queue, ^{});
    }
}
複製代碼

利用信號量的API來進行代碼改寫

- (void)test {
    // 建立信號量
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"當前%d----線程%@", i, [NSThread currentThread]);
            // 打印任務結束後信號量解鎖
            dispatch_semaphore_signal(sem);
        });
        // 因爲異步執行,打印任務會較慢,因此這裏信號量加鎖
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }
}
複製代碼

輸出結果

當前0----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前1----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前2----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前3----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前4----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前5----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前6----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前7----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前8----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
當前9----線程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
複製代碼

若是當建立信號量時傳入值爲1又會怎麼樣呢?

  • i=0時有可能先打印,也可能會先發出wait信號量-1,可是wait以後信號量爲0不會阻塞線程,因此進入i=1
  • i=1時有可能先打印,也可能會先發出wait信號量-1,可是wait以後信號量爲-1阻塞線程,等待signal再執行下去
當前1----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前0----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前2----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前3----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前4----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前5----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前6----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前7----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
當前8----線程<NSThread: 0x600001448d40>{number = 3, name = (null)}
當前9----線程<NSThread: 0x60000140c240>{number = 6, name = (null)}
複製代碼

結論:

  • 建立信號量時傳入值爲1時,能夠經過兩次才堵塞
  • 傳入值爲2時,能夠經過三次才堵塞

10、dispatch_source

應用場景:GCDTimer

1.定義及使用

dispatch_source是一種基本的數據類型,能夠用來監聽一些底層的系統事件

  • Timer Dispatch Source:定時器事件源,用來生成周期性的通知或回調
  • Signal Dispatch Source:監聽信號事件源,當有UNIX信號發生時會通知
  • Descriptor Dispatch Source:監聽文件或socket事件源,當文件或socket數據發生變化時會通知
  • Process Dispatch Source:監聽進程事件源,與進程相關的事件通知
  • Mach port Dispatch Source:監聽Mach端口事件源
  • Custom Dispatch Source:監聽自定義事件源

主要使用的API:

  • dispatch_source_create: 建立事件源
  • dispatch_source_set_event_handler: 設置數據源回調
  • dispatch_source_merge_data: 設置事件源數據
  • dispatch_source_get_data: 獲取事件源數據
  • dispatch_resume: 繼續
  • dispatch_suspend: 掛起
  • dispatch_cancle: 取消

2.自定義定時器

在iOS開發中通常使用NSTimer來處理定時邏輯,但NSTimer是依賴Runloop的,而Runloop能夠運行在不一樣的模式下。若是NSTimer添加在一種模式下,當Runloop運行在其餘模式下的時候,定時器就掛機了;又若是Runloop在阻塞狀態,NSTimer觸發時間就會推遲到下一個Runloop週期。所以NSTimer在計時上會有偏差,並非特別精確,而GCD定時器不依賴Runloop,計時精度要高不少

@property (nonatomic, strong) dispatch_source_t timer;
//1.建立隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.建立timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.設置timer首次執行時間,間隔,精確度
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//4.設置timer事件回調
dispatch_source_set_event_handler(_timer, ^{
    NSLog(@"GCDTimer");
});
//5.默認是掛起狀態,須要手動激活
dispatch_resume(_timer);
複製代碼

使用dispatch_source自定義定時器注意點:

  • GCDTimer須要強持有,不然出了做用域當即釋放,也就沒有了事件回調
  • GCDTimer默認是掛起狀態,須要手動激活
  • GCDTimer沒有repeat,須要封裝來增長標誌位控制
  • GCDTimer若是存在循環引用,使用weak+strong或者提早調用dispatch_source_cancel取消timer
  • dispatch_resumedispatch_suspend調用次數須要平衡
  • source掛起狀態下,若是直接設置source = nil或者從新建立source都會形成crash。正確的方式是在激活狀態下調用dispatch_source_cancel(source)釋放當前的source

GCD-API總結

API 說明
dispatch_sync() 同步執行
dispatch_async() 異步執行
dispatch_queue_create() 建立隊列
dispatch_get_main_queue() 獲取主隊列
dispatch_get_global_queue() 獲取全局隊列
dispatch_after() 延時執行
dispatch_once() 一次性執行
dispatch_apply() 提交隊列
dispatch_group_create() 建立調度組
dispatch_group_async() 執行進組任務
dispatch_group_enter()/
dispatch_group_leave()
將調度組中的任務未執行完畢的任務數目加減1
(兩個函數要配合使用)
dispatch_group_wait() 設置等待時間(成功爲0)
dispatch_barrier_sync() 同步柵欄函數
dispatch_barrier_async() 異步柵欄函數
dispatch_group_notify() 監聽隊列組執行完畢
dispatch_semaphore_creat() 建立信號量
dispatch_semaphore_wait() 等待信號量
dispatch_semaphore_signal() 釋放信號量
dispatch_source_create 建立源
dispatch_source_set_event_handler 設置源事件回調
dispatch_source_merge_data 源事件設置數據
dispatch_source_get_data 獲取源事件數據
dispatch_resume 繼續
dispatch_suspend 掛起
dispatch_cancle 取消

寫在後面

下篇文章將探索GCD的底層原理,GCD應用中的部分坑點會在多線程面試題中進行補充

相關文章
相關標籤/搜索