1 GCD簡述
Grand Central Dispatch(GCD)
是Apple
開發(fā)的一個多核編程的較新的解決方法.它主要用于優(yōu)化應用程序以支持多核處理器以及其他對稱多處理系統(tǒng).它是一個在線程池模式的基礎上執(zhí)行的并發(fā)任務.在Mac OS X 10.6
雪豹中首次推出,也可在iOS 4
及以上版本使用.
GCD優(yōu)點
-
GCD
可用于多核的并行運算 -
GCD
會自動利用更多的CPU
內(nèi)核(比如雙核庸蔼、四核) -
GCD
會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務祥山、銷毀線程) - 程序員只需要告訴
GCD
想要執(zhí)行什么任務,不需要編寫任何線程管理代碼
2 GCD任務和隊列
任務
任務: 執(zhí)行操作的意思,就是說你在線程中執(zhí)行的那段代碼.在GCD
中是放在block
中的.執(zhí)行任務有兩種方式同步執(zhí)行和異步執(zhí)行
- 同步執(zhí)行(
sync
):
(1) 同步添加任務到指定的隊列中,在添加的任務執(zhí)行結(jié)束之前,會一直等待,直到隊列里面的任務完成之后再繼續(xù)執(zhí)行
(2) 能在當前線程中執(zhí)行任務,不具備開啟新線程的能力 - 異步執(zhí)行(
async
):
(1) 異步添加任務到指定的隊列中,它不會做任何等待,可以繼續(xù)執(zhí)行任務
(2) 可以在新的線程中執(zhí)行任務,具備開啟新線程的能力
注意: <u>異步執(zhí)行(async
)雖然具有開啟新線程的能力,但是并不一定開啟新線程.這跟任務所指定的隊列類型有關(下面會講)</u>
任務的創(chuàng)建分為:同步任務dispatch_sync
和異步任務dispatch_async
dispatch_sync(queue, ^{
// 同步執(zhí)行任務
// code snippet
});
dispatch_async(queue, ^{
// 異步執(zhí)行任務
// code snippet
});
隊列(Dispatch Queue)
隊列:隊列指執(zhí)行任務的等待隊列,即用來存放任務的隊列.隊列是一種特殊的線性表,采用FIFIO
(先進先出)的原則.
GCD
隊列分為兩種:串行隊列和并行隊列
主要區(qū)別: 執(zhí)行順序不同,以及開啟線程數(shù)不同.
- 串行隊列(
Serial Dispatch Queue
):每次只有一個任務被執(zhí)行.讓任務一個接著一個地執(zhí)行.(只開啟一個線程,一個任務執(zhí)行完畢后,再執(zhí)行下一個任務) - 并發(fā)隊列(
Concurrent Dispatch Queue
):可以讓多個任務并發(fā)(同時)執(zhí)行.(可以開啟多個線程,并且同時執(zhí)行任務)
注意:<u>并發(fā)隊列 的并發(fā)功能只有在異步(dispatch_async
)方法下才有效.其他線程下,串行執(zhí)行任務</u>
隊列的創(chuàng)建:dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
- 參數(shù)0: 隊列的唯一標識符,隊列的名稱推薦使用應用程序id這種逆序全程域名
- 參數(shù)1: 用來識別是串行隊列還是并發(fā)隊列 (
DISPATCH_QUEUE_SERIAL
,DISPATCH_QUEUE_CONCURRENT
)
// 串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊列
dispatch_queue_t concurrentlQueue = dispatch_queue_create("com.appleid.functionB", DISPATCH_QUEUE_CONCURRENT);
// 主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局并發(fā)隊列 (參數(shù)0: 填寫默認 , 參數(shù)1: 填寫0)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3 隊列和同步,異步任務組合
區(qū)別 | 并發(fā)隊列 | 串行隊列 | 主隊列 |
---|---|---|---|
同步(sync) | 沒有開啟新線程,串行執(zhí)行任務 | 沒有開啟新線程,串行執(zhí)行任務 | 死鎖卡住不執(zhí)行 |
異步(async) | 有開啟新線程,并發(fā)執(zhí)行任務 | 有開啟新線程(1條),串行執(zhí)行任務 | 沒有開啟新線程,串行執(zhí)行任務 |
4 GCD方法
4.1 dispatch_after:延時執(zhí)行方法
主要:<u>dispatch_after
方法并不是在指定時間之后才開始執(zhí)行處理,而是在指定時間之后將任務追加到主隊列中.準確來說,這個時間并不是絕對準確的,但想要大致延遲執(zhí)行任務,dispatch_after 方法是很有效的</u>
- (void)after {
NSLog(@"當前線程%@", [NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"線程after:%@", [NSThread currentThread]); // 打印當前線程
});
}
4.2 dispatch_once:只執(zhí)行一次
在創(chuàng)建單例、或者有整個程序運行過程中只執(zhí)行一次的代碼時,就可以使用dispatch_once
方法.dispatch_once
方法能保證某段代碼在程序運行過程中只被執(zhí)行1
次,并且即使在多線程的環(huán)境下, dispatch_once
也可以保證線程安全.
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行一次, 默認線程安全
// code snippet
});
}
4.3 dispatch_barrier_async: 柵欄函數(shù)
對這個函數(shù)的調(diào)用總是在
block
被提交之后立即返回,并且從不等block
待被調(diào)用.當barrier block
到達私有并發(fā)隊列的前端時,它不會立即執(zhí)行.相反,隊列將等待,直到當前執(zhí)行的塊完成執(zhí)行.此時,barrier block
自己執(zhí)行.在barrier block
之后提交的任何block
都不會執(zhí)行,直到barrier block
完成.
您指定的隊列應該是您自己使用dispatch_queue_create
函數(shù)創(chuàng)建的并發(fā)隊列.如果傳遞給此函數(shù)的隊列是一個串行隊列或一個全局并發(fā)隊列,則此函數(shù)的行為與dispatch_async
函數(shù)類似.
dispatch_barrier_sync
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務1, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"任務2, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務3, %@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
[NSThread sleepForTimeInterval:2- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務1, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"任務2, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務3, %@", [NSThread currentThread]);
});
dispatch_barrier_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務4 barrier, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務5, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務6, %@", [NSThread currentThread]);
});
NSLog(@"任務7, %@", [NSThread currentThread]);
}];
NSLog(@"barrier任務4, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務5, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務6, %@", [NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
任務2, <NSThread: 0x139040570>{number = 4, name = (null)}
任務3, <NSThread: 0x139458a90>{number = 6, name = (null)}
任務1, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任務4 barrier, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任務7, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任務5, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任務6, <NSThread: 0x139458a90>{number = 6, name = (null)}
dispatch_barrier_async
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務1, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"任務2, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務3, %@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務4 barrier, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務5, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務6, %@", [NSThread currentThread]);
});
NSLog(@"任務7, %@", [NSThread currentThread]);
}
執(zhí)行結(jié)果:
任務7, <NSThread: 0x10360aea0>{number = 1, name = main}
任務2, <NSThread: 0x1035a4f90>{number = 3, name = (null)}
任務1, <NSThread: 0x105e79130>{number = 6, name = (null)}
任務3, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任務4 barrier, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任務5, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任務6, <NSThread: 0x105e79130>{number = 6, name = (null)}
4.4 dispatch_apply:快速迭代(高效for循環(huán))
此函數(shù)將多個調(diào)用的
block
提交給調(diào)度隊列,并等待任務block
的所有迭代完成后再返回.如果目標隊列是由dispatch_get_global_queue
返回的并發(fā)隊列,則可以并發(fā)調(diào)用該block
,因此它必須是reentrant
安全的.在并發(fā)隊列中使用此函數(shù)可以作為一種有效的并行for循環(huán).
迭代的當前索引被傳遞給block
的每次調(diào)用.
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// dispatch_apply是同步的
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"同步index:%zu %@", index, [NSThread currentThread]);
});
// 如果想異步,包裝一層
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"異步index:%zu %@", index, [NSThread currentThread]);
});
});
}
4.5 dispatch_group: 隊列組
- 調(diào)用隊列組的
dispatch_group_async
先把任務放到隊列中,然后將隊列放入隊列組中.或者使用隊列組的dispatch_group_enter讥耗、dispatch_group_leave
組合來實現(xiàn) - 調(diào)用隊列組的
dispatch_group_notify
回到指定線程執(zhí)行任務.或者使用dispatch_group_wait
回到當前線程繼續(xù)向下執(zhí)行(會阻塞當前線程)
-
dispatch_group_notify
: 監(jiān)聽group
中任務的完成狀態(tài),當所有的任務都執(zhí)行完成后,追加任務到group
中,并執(zhí)行任務
- (void)group {
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務1, %@", [NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務2, %@", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步任務 1打厘、任務 2 都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務
[NSThread sleepForTimeInterval:2];
NSLog(@"任務3, %@", [NSThread currentThread]);
NSLog(@"group任務完成");
});
}
執(zhí)行結(jié)果:
任務2, <NSThread: 0x281f87280>{number = 5, name = (null)}
任務1, <NSThread: 0x281fa0c00>{number = 8, name = (null)}
任務3, <NSThread: 0x281fc4d80>{number = 1, name = main}
group任務完成
-
dispatch_group_wait
:暫停當前線程(阻塞當前線程),等待指定的group
中的任務執(zhí)行完成后,才會往下繼續(xù)執(zhí)行
- (void)group {
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務1, %@", [NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務2, %@", [NSThread currentThread]);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group任務完成");
}
執(zhí)行結(jié)果:
任務2, <NSThread: 0x2817f9540>{number = 4, name = (null)}
任務1, <NSThread: 0x2817ff9c0>{number = 6, name = (null)}
group任務完成
dispatch_group_enter(), dispatch_group_leave
- (void)group1 {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務1, %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務2, %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步任務 1蚪缀、任務 2 都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務
[NSThread sleepForTimeInterval:2];
NSLog(@"任務3, %@", [NSThread currentThread]);
NSLog(@"group任務完成");
});
}
執(zhí)行結(jié)果:
任務2, <NSThread: 0x281df3e40>{number = 4, name = (null)}
任務1, <NSThread: 0x281de3f80>{number = 7, name = (null)}
任務3, <NSThread: 0x281db0d80>{number = 1, name = main}
group任務完成
4.6 dispatch_semaphore: 信號量
dispatch_semaphore
是GCD
中的信號量,持有計數(shù)的信號, dispatch Semaphore
中,使用計數(shù)來完成這個功能,計數(shù)小于0時等待
,不可通過.計數(shù)為0或大于0時
可通過.
主要使用:
- 保持線程同步,將異步執(zhí)行任務轉(zhuǎn)換為同步執(zhí)行任務
- 保證線程安全,為線程加鎖
dispatch_semaphore
三個方法:
-
dispatch_semaphore_create
: 創(chuàng)建一個semaphore
并初始化信號的總量 -
dispatch_semaphore_signal
: 發(fā)送一個信號,信號計數(shù)+ 1
-
dispatch_semaphore_wait
: 可以使總信號量- 1
,信號總量小于0
時就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行
- (void)semaphor {
NSLog(@"當前線程:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務1, %@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
NSLog(@"當前線程1:%@", [NSThread currentThread]);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任務完成");
}
執(zhí)行結(jié)果:
當前線程:<NSThread: 0x282784d80>{number = 1, name = main}
當前線程1:<NSThread: 0x282784d80>{number = 1, name = main}
任務1, <NSThread: 0x2827d6cc0>{number = 6, name = (null)}
任務完成
從打印結(jié)果可知執(zhí)行流程為:
-
semaphore
開始計數(shù)為0 -
異步任務
加入隊列之后,不等待繼續(xù)執(zhí)行, 執(zhí)行到dispatch_semaphore_wait
方法, 信號量計數(shù)- 1
為-1
小于0
,當前線程進入等待狀態(tài) -
任務1
執(zhí)行開始執(zhí)行, 執(zhí)行完成后,執(zhí)行dispatch_semaphore_signal
,信號量計數(shù)+ 1
為0
,阻塞線程恢復繼續(xù)執(zhí)行
完整代碼見GitHub->多線程(附大廠面試講解)
如有不足之處,歡迎予以指正, 如果感覺寫的不錯,記得給個贊呦!