GCD(Grand Central Dispatch) 是基於C語言的API,是蘋果公司爲多核的並行運算提出的解決方案。GCD會自動利用更多的CPU內核(好比雙核、四核)。程序員只須要將任務添加到隊列中,而且指定執行任務的函數,不須要別寫任何線程管理的代碼。程序員
學習 GCD 以前,先來了解 GCD 中兩個核心概念:任務
和 隊列
。安全
任務
:就是執行操做的意思,通俗的說就是你在線程中執行的那段代碼。在 GCD 中是放在 block 中的。bash
執行任務有兩種方式:同步執行
和 異步執行
。二者的主要區別是:是否等待隊列的任務執行結束,以及是否具有開啓新線程的能力
。網絡
dispatch_sync
等待
當前隊列的任務執行完畢,才能執行下一個任務dispatch_async
等待
,能夠繼續執行任務。隊列(Dispatch Queue)
:這裏的隊列指執行任務的等待隊列,即用來存聽任務的隊列。隊列是一種特殊的線性表,採用 FIFO(First - In -First - Out)
的原則,即新任務老是被插入到隊列的末尾,而讀取任務的時候老是從隊列的頭部開始讀取。每讀取一個任務,則從隊列中釋放一個任務。隊列的結構可參考下圖:多線程
在 GCD 中有兩種隊列:串行隊列
和 併發隊列
。二者都符合 FIFO
原則。二者的主要區別是:執行順序不一樣,以及開啓線程數不一樣
。併發
Serial Dispatch Queue
每次只有一個 任務
被執行,只申請一個 線程
,一個 任務
執行完畢,再執行下一個Concurrent Dispatch Queue
能夠申請多個
線程
,讓多個
任務
同時執行(注意:併發隊列的併發功能只有在 異步dispatch_async 方法下有效)
GCD使用很簡單,只有三步app
任務
(同步任務 或者 異步任務)隊列
(串行隊列 或者 併發隊列)任務
追加到任務的等待 隊列
中,而後系統就會根據任務類型執行任務(同步執行 或者 異步執行)同步執行任務的建立方法 dispatch_sync
和 異步執行任務的建立方法 dispatch_async
。異步
// 同步任務建立方法
dispatch_sync(queue, ^{
// 這裏放同步任務代碼
});
// 異步執行任務建立方法
dispatch_async(queue, ^{
// 這裏放異步任務代碼
});
複製代碼
GCD 可使用 dispatch_queue_creat
建立隊列async
dispatch_queue_t queue - dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>);
複製代碼
const char * _Nullable label
: 隊列的惟一標識符,用於debug,能夠爲空。dispatch_queue_attr_t _Nullable attr
: 隊列種類。DISPATCH_QUEUE_SERIAL
串行隊列,DISPATCH_QUEUE_CONCURRENT
併發隊列。第二個參數若是傳NULL,會建立串行隊列。ide
對於串行隊列,GCD默認提供了:主隊列 - Main Dispatch Queue
dispatch_get_main_queue()
方法得到主隊列主隊列本質就是一個普通的串行隊列,只是默認狀況下,代碼放在主隊列中,主隊列的代碼會放到主程序中執行,給咱們主隊列特殊的感受。
對於併發隊列,GCD默認提供了:全局併發隊列 - Global Dispatch Queue
。可使用 dispatch_get_global_queue()
方法得到全局併發隊列
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
dispatch_queue_t queue = dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
複製代碼
long identifier
: 標示隊列優先級,通常用 DISPATCH_QUEUE_PRIORITY_DEFAULT
- (void)syncSerial
{
NSLog(@"主線程 - %@", [NSThread currentThread]);
NSLog(@"---begin---");
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i < 10; i ++) {
dispatch_sync(queue, ^{
NSLog(@"同步任務 + 串行隊列:%ld -- %@", (long)i, [NSThread currentThread]);
});
}
NSLog(@"---end---");
}
複製代碼
同步任務只能在當前線程中執行任務,不具有開啓新線程的能力
)同步任務須要等待隊列的任務執行結束
)串行隊列每次只有一個任務被執行,任務一個接一個按順序執行
)。- (void)asyncSerial
{
NSLog(@"主線程 - %@", [NSThread currentThread]);
NSLog(@"---begin---");
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i < 10; i ++) {
dispatch_async(queue, ^{
NSLog(@"異步任務 + 串行隊列:%ld -- %@", (long)i, [NSThread currentThread]);
});
}
NSLog(@"---end---");
}
複製代碼
異步任務具備開闢線程的能力,串行隊列只開啓一個線程
)異步執行不會作任何等待,能夠繼續執行接下來的任務
)串行隊列每次只有一個任務被執行,任務一個接一個按順序執行
)- (void)syncConcurrent
{
NSLog(@"主線程 - %@", [NSThread currentThread]);
NSLog(@"---begin---");
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i ++) {
dispatch_sync(queue, ^{
NSLog(@"同步任務 + 並行隊列:%ld -- %@", (long)i, [NSThread currentThread]);
});
}
NSLog(@"---end---");
}
複製代碼
同步任務只能在當前線程中執行任務,不具有開啓新線程的能力
)同步任務須要等待隊列的任務執行結束
)併發隊列
能夠開啓多個線程,而且同時執行多個任務。可是由於自己不能建立新線程,只有當前線程這一個線程(同步任務不具有開啓新線程的能力
),因此也就不存在併發。並且當前線程只有等待當前隊列中正在執行的任務執行完畢以後,才能繼續接着執行下面的操做(同步任務 須要等待隊列的任務執行結束
)。因此任務只能一個接一個按順序執行,不能同時被執行。- (void)asyncConcurrent
{
NSLog(@"主線程 - %@", [NSThread currentThread]);
NSLog(@"---begin---");
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i ++) {
dispatch_async(queue, ^{
NSLog(@"異步任務 + 並行隊列:%ld -- %@", (long)i, [NSThread currentThread]);
});
}
NSLog(@"---end---");
}
複製代碼
異步任務具有開啓新線程的能力。 併發隊列 可開啓多個線程,同時執行多個任務
)。異步執行不會作任何等待,能夠繼續執行接下來的任務
)同步任務 + 主隊列
在不一樣線程中調用結果也是不同,在主線程中調用會發生死鎖問題,而在其餘線程中調用則不會。
同步任務 + 主隊列
崩潰緣由:主隊列是串行隊列 全部放在主隊列中的任務,都會放到主線程中執行
, 當咱們在主線程
中執行syncMain方法,至關於把 syncMain任務
放到了 主線程
的隊列中。而 同步執行會等待當前隊列中的任務執行完畢,纔會接着執行
。那麼當咱們把 任務1
追加到主隊列中,任務1
就在等待主線程處理完 syncMain任務
。而 syncMain任務
須要等待 任務1
執行完畢,才能接着執行。兩個任務 相互等待
對方執行完畢。
同步任務 + 主隊列
// 使用 NSThread 的 detachNewThreadSelector 方法會建立線程,並自動啓動線程執行 selector 任務
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
複製代碼
全部放在主隊列中的任務,都會放到主線程中執行
) 全部打印都在 ---begin--- 和 ---end--- 之間執行(同步任務須要等待隊列的任務執行結束
)主隊列是 串行隊列,每次只有一個任務被執行,任務一個接一個按順序執行
)爲何如今就不會卡住了呢?
由於 syncMain任務
放到 子線程
裏; 而 任務1到10
都在追加到主隊列中,會在 主線程
中執行。主隊列如今沒有正在執行的任務,因此會直接執行主隊列的 任務1
,等 任務1
執行完畢,再接着執行 任務2
。因此這裏不會卡住線程,也就不會形成死鎖問題。
如下demo默認都在主隊列,由於主隊列是串行隊列,代碼自上到下依次執行。默認異步操做都是長耗時操做。
- (void)demo1
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
打印順序:1 5 2 死鎖
複製代碼
代碼塊B
爲同步任務,等待,產生阻塞。因此,任務4
要等到代碼塊B
執行完畢才能執行。代碼塊B
要執行完畢,必須等到 任務3
執行完畢.任務3
要等到 任務4
執行完畢才執行任務4
等塊B
,塊B
等 任務3
,任務3
等 任務4
。死鎖。- (void)demo2
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_sync(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
打印順序: 12453
複製代碼
任務1 代碼塊A 任務5
。代碼塊A
是同步任務,等待。 因此打印 1 代碼塊A 5代碼塊A
內: 隊列中依次加入 任務2 代碼塊B 任務4
。代碼塊B
是異步任務,不等待,耗時。因此打印 2 4 3代碼塊B
執行完畢後 打印5- (void)demo3
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
打印結果:15234
複製代碼
任務1 代碼塊A 任務5
。代碼塊A
是異步任務,不等待,耗時。因此打印 1 5代碼塊A
內:並行隊列,申請新線程。新線程依次加入任務2 代碼塊B 任務4
。代碼塊B
同步任務,等待阻塞當前子線程。打印 2 3 4- (void)demo4
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
打印結果:12453
複製代碼
任務1 代碼塊A 任務5
。代碼塊A
是同步任務,等待。因此打印 1 代碼塊A 5代碼塊A
內:並行隊列,加入 任務2 代碼塊B 任務4
。代碼塊B
異步任務,不等待,耗時,申請新的線程。應用場景:在指定時間以後執行某個任務。
dispatch_after
方法並非在指定時間以後纔開始執行處理,而是在指定時間以後將任務追加到主隊列中。嚴格來講,這個時間並非絕對準確的,但想要大體延遲執行任務,dispatch_after
方法是頗有效的。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2秒後輸出");
});
複製代碼
應用場景:單例,method-Swizzling
dispatch_once
能保證某段代碼在程序運行過程當中只被執行 1 次,而且即便在多線程的環境下,dispatch_once
也能夠保證線程安全。
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執行 1 次的代碼(這裏面默認是線程安全的)
});
}
複製代碼
應用場景:得到網絡數據後提早算出各個控件的大小
dispatch_apply 按照指定的次數將指定的任務追加到指定的隊列中,並等待所有隊列執行結束。
- (void)apply
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", 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_group_wait
方法。
應用場景:線程同步
異步任務+併發隊列會開闢線程,多個任務同時進行,各任務也會由於任務複雜度和cpu的調度致使執行完畢亂序。如何設置讓任務順序執行完畢,柵欄函數就是很好的解決辦法。
dispatch_barrier_async
用於提交異步執行柵欄函數塊任務,並當即返回。該函數不會阻塞線程
,只會阻塞任務
執行。柵欄函數提交以後並不會當即執行,而是會直接返回。等待在柵欄函數以前提交的任務都執行完成以後,柵欄函數任務會自動執行,在柵欄函數以後提交的任務必須在柵欄函數執行完成以後纔會執行.
dispatch_barrier_sync
用於提交同步執行柵欄函數塊任務,並不會當即返回,須要等待柵欄函數提交的任務執行完畢以後纔會返回。與dispatch_barrier_async不一樣,因爲該函數須要同步執行,在該柵欄函數任務執行完成以前,函數不會返回,因此此時後邊的任務都會被阻塞
.
- (void)barrierSync
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"任務1 -- %@", [NSThread currentThread]);
});
dispatch_barrier_sync(queue, ^{
NSLog(@"dispatch_barrier_sync -- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務2 -- %@", [NSThread currentThread]);
});
NSLog(@"end -- %@", [NSThread currentThread]);
}
複製代碼
- (void)barrierAsync
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"任務1 -- %@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async -- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務2 -- %@", [NSThread currentThread]);
});
NSLog(@"end -- %@", [NSThread currentThread]);
}
複製代碼
比較
測試1
和
測試2
:
dispatch_barrier_sync
與 dispatch_barrier_async
的共同點:任務begin
任務1
)先執行完。barrier operation
)執行完以後再執行後面的任務(任務2
任務end
)。dispatch_barrier_sync
與 dispatch_barrier_async
的不一樣點:dispatch_barrier_sync
須要等待本身的任務(barrier operation
)執行完畢後,纔會插入後面的任務(任務2
任務end
),而後執行後面的任務。因此 任務end
比 barrier operation
後打印。說白了,會阻塞線程。dispatch_barrier_async
將本身的任務插入到隊列以後,不會等待本身的任務(barrier operation)執行結果,它會繼續插入後面的任務。因此 任務end
先打印。dispatch_barrier_sync線程
與 任務線程
不一致,會怎樣?- (void)barrierSync
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
// 新建線程
dispatch_queue_t queue2 = dispatch_queue_create("com.akironer2", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"任務1 -- %@", [NSThread currentThread]);
});
// 阻塞新建線程
dispatch_barrier_sync(queue2, ^{
NSLog(@"dispatch_barrier_sync -- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務2 -- %@", [NSThread currentThread]);
});
NSLog(@"end -- %@", [NSThread currentThread]);
}
複製代碼
使用
dispatch_barrier_async
測試,也有一樣的結果
dispatch_barrier 線程
與 任務線程
不一致,barrier沒有發揮做用。全局併發隊列
使用柵欄函數,有什麼效果?應用場景:分別異步執行2個耗時任務,而後當2個耗時任務都執行完畢後再回到主線程執行任務。
dispatch_group_async
先把任務放到隊列中,而後將隊列放入隊列組中dispatch_group_notify
監聽 group 中任務的完成狀態,當全部的任務都執行完成後,追加任務
到 group
中,並執行任務。- (void)groupAsync
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_group_async(group, queue, ^{
sleep(5);
NSLog(@"任務1 -- %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"任務2 -- %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(4);
NSLog(@"任務3 -- %@", [NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"end -- %@", [NSThread currentThread]);
});
}
打印:
22:23:12.180761+0800 GCD[2123:330417] begin -- <NSThread: 0x60000212ad40>{number = 1, name = main}
22:23:15.182107+0800 GCD[2123:330584] 任務2 -- <NSThread: 0x600002150080>{number = 3, name = (null)}
22:23:16.180985+0800 GCD[2123:330585] 任務3 -- <NSThread: 0x60000215c180>{number = 4, name = (null)}
22:23:17.182406+0800 GCD[2123:330596] 任務1 -- <NSThread: 0x600002122ec0>{number = 5, name = (null)}
22:23:17.182667+0800 GCD[2123:330596] end -- <NSThread: 0x600002122ec0>{number = 5, name = (null)}
複製代碼
暫停當前線程,等待指定的group中的任務執行完成以後,才往下執行任務。相比於dispatch_group_notify
, dispatch_group_wait
會阻塞線程。
- (void)groupWait
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_group_async(group, queue, ^{
sleep(5);
NSLog(@"任務1 -- %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"任務2 -- %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(4);
NSLog(@"任務3 -- %@", [NSThread currentThread]);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"end -- %@", [NSThread currentThread]);
}
打印:
22:31:45.390633+0800 GCD[2157:336097] begin -- <NSThread: 0x6000001d6d00>{number = 1, name = main}
22:31:48.392504+0800 GCD[2157:336257] 任務2 -- <NSThread: 0x6000001a4640>{number = 6, name = (null)}
22:31:49.392455+0800 GCD[2157:336255] 任務3 -- <NSThread: 0x6000001dc400>{number = 5, name = (null)}
22:31:50.395577+0800 GCD[2157:336256] 任務1 -- <NSThread: 0x6000001ac380>{number = 4, name = (null)}
22:31:50.395978+0800 GCD[2157:336256] end -- <NSThread: 0x6000001ac380>{number = 4, name = (null)}
複製代碼
dispatch_group_enter
標誌着一個任務追加到 group,執行一次,至關於 group 中未執行完畢任務數 +1dispatch_group_leave
標誌着一個任務離開了 group,執行一次,至關於 group 中未執行完畢任務數 -1。dispatch_group_wait
解除阻塞,以及執行追加到 dispatch_group_notify
中的任務。- (void)groupWait
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
sleep(5);
NSLog(@"任務1 -- %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"任務2 -- %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"end -- %@", [NSThread currentThread]);
});
}
打印:
22:42:10.338809+0800 GCD[2188:343124] begin -- <NSThread: 0x60000338adc0>{number = 1, name = main}
22:42:13.340545+0800 GCD[2188:343288] 任務2 -- <NSThread: 0x6000033e8340>{number = 5, name = (null)}
22:42:15.343788+0800 GCD[2188:343293] 任務1 -- <NSThread: 0x600003304000>{number = 7, name = (null)}
22:42:15.344152+0800 GCD[2188:343293] end -- <NSThread: 0x600003304000>{number = 7, name = (null)}
複製代碼
應用場景:
信號量是基於計數器的一種多線程同步機制,用來管理對資源的併發訪問。信號量內部有一個能夠原子遞增或遞減的值。若是一個動做嘗試減小信號量的值,使其小於0,那麼這個動做將會被阻塞,直到有其餘調用者(在其餘線程中)增長該信號量的值。
dispatch_semaphore_create
:建立一個 Semaphore 並初始化信號的總量dispatch_semaphore_signal
:發送一個信號,讓信號總量加 1dispatch_semaphore_wait
:可使總信號量減 1。信號量小於等於 0 則會阻塞當前線程,直到信號量大於0或者通過輸入的時間值;若信號量大於0,則會使信號量減1並返回,程序繼續住下執行。在 dispatch_semaphore_wait
和 dispatch_semaphore_signal
這兩個函數中間的執行代碼,每次只會容許限定數量的線程進入,這樣就有效的保證了在多線程環境下,只能有限定數量的線程進入。
注意:信號量的使用前提是:想清楚你須要處理哪一個線程等待(阻塞),又要哪一個線程繼續執行,而後使用信號量。
- (void)semaphoreAync
{
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("com.akironer", 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);
}
}
複製代碼
任務1
追加到隊列以後,不作等待,接着執行 dispatch_semaphore_wait
,semaphore 減 1,此時 semaphore == -1,當前線程進入阻塞狀態。任務1
開始執行。執行到 dispatch_semaphore_signal
以後,總信號量加 1,此時 semaphore == 0,正在被阻塞的線程恢復繼續執行。- (void)saleTickets
{
NSLog(@"begin -- %@", [NSThread currentThread]);
self.ticketCount = 20;
// queue1 表明售賣窗口1
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 表明售賣窗口2
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketsSemaphore];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketsSemaphore];
});
}
- (void)saleTicketsSemaphore
{
while (1) {
// 至關於加鎖
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) {
self.ticketCount --;
NSLog(@"剩餘票數:%ld 窗口:%@", (long)self.ticketCount, [NSThread currentThread]);
sleep(0.1);
} else {
NSLog(@"賣完了%@", [NSThread currentThread]);
// 至關於解鎖
dispatch_semaphore_signal(self.semaphore);
break;
}
// 至關於解鎖
dispatch_semaphore_signal(self.semaphore);
}
}
複製代碼
- (void)dispatchAsyncLimit
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
//任務1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任務1執行--%@", [NSThread currentThread]);
sleep(1);
NSLog(@"任務1完成--%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
//任務2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任務2執行--%@", [NSThread currentThread]);
sleep(1);
NSLog(@"任務2完成--%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
//任務3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任務3執行--%@", [NSThread currentThread]);
sleep(1);
NSLog(@"任務3完成--%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
}
![](https://user-gold-cdn.xitu.io/2020/4/26/171b23c361a2d077?w=2136&h=224&f=png&s=203093)
複製代碼
因爲設定的信號值爲2,先執行2個線程,等執行完一個,纔會繼續執行下一個,保證同一時間執行的線程數不超過2
應用場景:GCDTimer
NSTimer是依賴Runloop的,而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);
複製代碼