GCD筆記
總結一下多線程部分,最強大的無疑是GCD,那么先從這一塊部分講起.
Dispatch Queue的種類
GCD 提供有 dispatch queues 來處理代碼塊,這些隊列管理你提供給 GCD 的任務并用 FIFO 順序執行這些任務。這就保證了第一個被添加到隊列里的任務會是隊列中第一個開始的任務,而第二個被添加的任務將第二個開始,如此直到隊列的終點。
所有的調度隊列(dispatch queues)自身都是線程安全的,你能從多個線程并行的訪問它們。當你了解了調度隊列如何為你自己代碼的不同部分提供線程安全后,GCD的優點就是顯而易見的。關于這一點的關鍵是選擇正確類型的調度隊列和正確的調度函數來提交你的工作。
在本節你會看到兩種調度隊列,都是由 GCD 提供的
- Serial Dispatch Queue 等待現在執行中處理結束,也就是我們常說的
串行隊列
串行隊列中的任務一次執行一個,每個任務只在前一個任務完成時才開始。而且,你不知道在一個 Block 結束和下一個開始之間的時間長度.這些任務的執行時機受到 GCD 的控制;唯一能確保的事情是 GCD 一次只執行一個任務,并且按照我們添加到隊列的順序來執行。 - Concurrent Dispatch Queue 不等待現在執行中處理結束,也就是我們常說的
并行隊列
.在并發隊列中的任務能得到的保證是它們會按照被添加的順序開始執行,但這就是全部的保證了。任務可能以任意順序完成,你不會知道何時開始運行下一個任務,或者任意時刻有多少 Block 在運行。再說一遍,這完全取決于 GCD 。
GCD眾多API解析
1.Dispatch_queue_creat
- 生成一個串行隊列,按照順序執行隊列里的任務
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_creat("com.example.gcd.MyConcurrentDispatchQueue",NULL);
- 生成一個并行隊列,同時執行隊列里的任務
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_creat("com.example.gcd.MyConcurrentDispatchQueue",DISPATCH_QUEUE_CONCURRENT);
注意:非ARC情況下,通過creat函數生成的隊列,必須程序員自己來進行內存管理,通過
dispatch_release()
或者dispatch_retain()
來進行內存管理,ARC情況下,無須我們自己處理
2.Main Dispatch Queue/Global Dispatch Queue
Global Dispatch Queue 有4個優先級,分別是高優先級(
High Priority
) \默認優先級(Default Priority
)\低優先級(Low Priority
)和后臺優先級(Background Priority
).
首先,系統提供給你一個叫做 主隊列(main queue) 的特殊隊列。和其它串行隊列一樣,這個隊列中的任務一次只能執行一個。然而,它能保證所有的任務都在主線程執行,而主線程是唯一可用于更新 UI 的線程。這個隊列就是用于發生消息給 UIView 或發送通知的。
系統同時提供給你好幾個并發隊列。它們叫做 全局調度隊列(Global Dispatch Queues) 。目前的四個全局隊列有著不同的優先級:background、low、default 以及 high。要知道,Apple 的 API 也會使用這些隊列,所以你添加的任何任務都不會是這些隊列中唯一的任務。
最后,你也可以創建自己的串行隊列或并發隊列。這就是說,至少有五個隊列任你處置:主隊列、四個全局調度隊列,再加上任何你自己創建的隊列。
以上是調度隊列的大框架!
GCD 的“藝術”歸結為選擇合適的隊列來調度函數以提交你的工作,下面介紹一下系統提供的眾多函數
3.Dispatch_set_target_queue
功能:變更生成的Dispatch Queue 的
執行優先級
要使用該函數.
4.Dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ <#code to be executed after a specified delay#> });
注意:
dispatch_after
函數并不是在指定時間后執行處理,而是在指定時間追加處理到Dispatch Queue
.dispatch_after
工作起來就像一個延遲版的dispatch_async
。你依然不能控制實際的執行時間,且一旦dispatch_after
返回也就不能再取消它。
- 第一個參數是指定時間,用的
dispatch_time_t
類型的值.該值使用dispatch_time
函數或dispatch_walltime
函數作成.
dispatch_time
用于計算相對時間
dispatch_walltime
用于計算絕對時間
ull
代表unsigned long long
數值和NSEC_PER_SEC
的乘積得到單位為秒的數值
數值和NSEC_PER_MSEC
的乘積得到單位為毫秒的數值DISPATCH_TIME_NOW
表示現在的時間關于dispatch_time
,第一個參數通常使用DISPATCH_TIME_NOW
,它是一個表示dispatch_time_t
的宏,表示從現在開始算起 - 第二個參數是第一個參數之后經歷的時長。而我們通常用的
NSEC_PER_SEC
也是一個宏,還有其他的宏:
#define NSEC_PER_SEC 1000000000ull //每秒有多少納秒
#define NSEC_PER_MSEC 1000000ull //每毫秒有多少納秒
#define USEC_PER_SEC 1000000ull //每秒有多少微秒
#define NSEC_PER_USEC 1000ull //每微秒有多少納秒
因為1秒鐘有NSEC_PER_SEC(也即1000000000)納秒,所以NSEC_PER_SEC其實就相當于1秒,那么兩秒就是2 *NSEC_PER_SEC;
同理,NSEC_PER_MSEC就相當于是1毫秒,那么2秒鐘,就應該是2000 * NSEC_PER_MSEC;
USEC_PER_SEC也相當于1毫秒,2秒鐘就是2000 *USEC_PER_SEC
NSEC_PER_USEC相當于 1微妙,所以要表示1秒鐘就是1000000 NSEC_PER_USEC。
所以1秒后執行某段代碼可以這樣寫:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"哈哈"); });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1000 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ NSLog(@"嘿嘿"); });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1000 * USEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"嗯嗯"); });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1000000 * NSEC_PER_USEC)), dispatch_get_main_queue(), ^{ NSLog(@"呃呃"); });
這個API的作用與下面這個方法類似:
[self performSelector:@selector(testClick:) withObject:nil afterDelay:2.0];
不知道何時適合使用 dispatch_after ?
- 自定義串行隊列:在一個自定義串行隊列上使用 dispatch_after 要小心。你最好堅持使用主隊列。
- 主隊列(串行):是使用 dispatch_after 的好選擇;Xcode 提供了一個不錯的自動完成模版。
- 并發隊列:在并發隊列上使用 dispatch_after 也要小心;你會這樣做就比較罕見。還是在主隊列做這些操作吧。
5.Dispatch Group
使用場景:在多個異步任務全部執行完畢后,執行某個任務。如果用同步任務或串行隊列,就沒有意義了,要謹記。
這里有兩種實現方式:
方式一 利用dispatch_group_async
和dispatch_group_notify
配合.
方式二 利用dispatch_group_enter
、dispatch_group_leave
和
dispatch_group_notify
配合,其中需要注意的是有dispatch_group_enter
就必定有一個dispatch_group_leave
與之對應,否則可能會出現令你意想不到的崩潰。
Dispatch Group 會在整個組的任務都完成時通知你。這些任務可以是同步的,也可以是異步的,即便在不同的隊列也行。而且在整個組的任務都完成時,Dispatch Group 可以用同步的或者異步的方式通知你。因為要監控的任務在不同隊列,那就用一個 dispatch_group_t
的實例來記下這些不同的任務。
當組中所有的事件都完成時,GCD 的 API 提供了兩種通知方式。
第一種是 dispatch_group_wait
,它會阻塞當前線程,直到組里面所有的任務都完成或者等到某個超時發生。這恰好是你目前所需要的。
在我們轉向另外一種使用 Dispatch Group 的方式之前,先看一個簡要的概述,關于何時以及怎樣使用有著不同的隊列類型的 Dispatch Group :
- 自定義串行隊列:它很適合當一組任務完成時發出通知。
- 主隊列(串行):它也很適合這樣的情況。但如果你要同步地等待所有工作地完成,那你就不應該使用它,因為你不能阻塞主線程。然而,異步模型是一個很有吸引力的能用于在幾個較長任務(例如網絡調用)完成后更新 UI 的方式。
- 并發隊列:它也很適合 Dispatch Group 和完成時通知。
dispatch_group_notify
以異步的方式工作。當 Dispatch Group 中沒有任何任務時,它就會執行其代碼,那么 completionBlock 便會運行。你還指定了運行 completionBlock 的隊列,此處,主隊列就是你所需要的。
Dispatch_group_wait
該API依然是與dispatch_group配合使用。它會阻塞當前所在的線程,直到前面的blocks 執行完成,或者超時的時候返回。該方法會同步的等待之前比較的block 對象們完成,如果在給定的時間內沒有完成,該方法就會返回。如果在給定的時間超時前完成,則返回0,否則就返回一個非零的值。
示例
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 5; i++)
{
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:i];
NSLog(@"并發%d結束----線程:%@", i,[NSThread currentThread]);
});
}
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
long result = dispatch_group_wait(group, time);
if (result) {
NSLog(@"超時了");
} else {
NSLog(@"執行完畢");
}// 打印結果是:
2016-07-12 14:04:17.980 PractiseProject[4537:155453] 并發0結束----線程:<NSThread: 0x7fbbe8e115e0>{number = 3, name = (null)}
2016-07-12 14:04:18.981 PractiseProject[4537:155441] 并發1結束----線程:<NSThread: 0x7fbbe8f5ee60>{number = 4, name = (null)}
2016-07-12 14:04:19.980 PractiseProject[4537:155338] 超時了
2016-07-12 14:04:19.981 PractiseProject[4537:155456] 并發2結束----線程:<NSThread: 0x7fbbe8f5d260>{number = 5, name = (null)}
2016-07-12 14:04:20.985 PractiseProject[4537:155464] 并發3結束----線程:<NSThread: 0x7fbbe8f60e90>{number = 6, name = (null)}
2016-07-12 14:04:21.984 PractiseProject[4537:155453] 并發4結束----線程:<NSThread: 0x7fbbe8e115e0>{number = 3, name = (null)}
由于dispatch_group_wait
會阻塞線程,在dispatch_group_wait
后面的代碼并不會執行,如果我們在主線程中執行上面的代碼段,則會阻塞UI界面。所以我們應該在子線程中執行上面的代碼片段(用一個dispatch_async
包起來)。如果我們設置wait的時間為永遠的話,由于在子線程中執行的任務總有結束的時候,那么dispatch_group_wait
之后執行的代碼就效果就與上一篇中的dispatch_group_notify
的功能類似啦。
示例:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 5; i++) {
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:i];
NSLog(@"并發%d結束----線程:%@", i,[NSThread currentThread]);
} );
}
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result) {
NSLog(@"超時了");
} else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"執行完畢,回主線程更新UI");
});
}
});
6.Dispatch_barrier_async和Dispatch_barrier_sync
這個函數的意思是:前面的任務執行結束后它才執行,而且它后面的任務等它執行完畢之后才能執行.
該函數同
dispatch_queue_creat
函數生成的Concurrent Dispatch Queue
一起使用.
注意:
1.從Xcode 4開始,我們定義property后,編譯器會自動幫我們添加@synthesize
,但是如果我們同時重寫setter和getter,那么編譯器便不再幫我們添加@synthesize
,我們需要自己添加@synthesize
。
2.dispatch_barrier_async
只能使用dispatch_queue_create
創建的并發隊列,才能正確發揮它的作用。
dispatch_barrier_sync
與dispatch_barrier_async
的功能基本一致,不同之處是,dispatch_barrier_sync
是在當前線程中執行block中的任務,而dispatch_barrier_async
則是在新的線程(有可能是之前使用過的子線程)中執行任務。 它們都是在用dispatch_queue_create
創建的并發隊列上有效果,而在串行隊列或者dispatch_get_global_queue
創建的并發隊列中,作用與dispatch_sync
一致。dispatch_barrier
決定的只是它的任務是否在新的線程中執行,以及它一定在前面幾個任務執行完后執行,并不會影響之前任務的執行順序等。在串行隊列
或者dispatch_get_global_queue
創建的并發隊列中,dispatch_barrier_sync
僅僅相當于dispatch_sync
dispatch_barrier_async
函數會等待追加到Concurrent Dispatch Queue
上的并行執行的處理全部結束之后,再將指定的處理追加到該Concurrent Dispatch Queue
中.然后在由dispatch_barrier_async
函數追加的處理執行完畢后,Concurrent Dispatch Queue
才恢復一般的動作,追加到該Concurrent Dispatch Queue
的處理又開始并行執行.
下面是你何時會——和不會——使用障礙函數的情況:
- 自定義串行隊列:一個很壞的選擇;障礙不會有任何幫助,因為不管怎樣,一個串行隊列一次都只執行一個操作。
- 全局并發隊列:要小心;這可能不是最好的主意,因為其它系統可能在使用隊列而且你不能壟斷它們只為你自己的目的。
- 自定義并發隊列:這對于原子或臨界區代碼來說是極佳的選擇。任何你在設置或實例化的需要線程安全的事物都是使用障礙的最佳候選。
由于上面唯一像樣的選擇是自定義并發隊列,你將創建一個你自己的隊列去處理你的障礙函數并分開讀和寫函數。且這個并發隊列將允許多個多操作同時進行。
7.Dispatch_sync同步函數和Dispatch_async異步函數
dispatch_sync
添加任務到一個隊列并等待直到任務完成。dispatch_async
做類似的事情,但不同之處是它不會等待任務的完成,而是立即繼續“調用線程”的其它任務。
特別注意:在同步函數執行主線程隊列的任務會發生死鎖,特別注意同步函數的死鎖情況
下面是一個快速總覽,關于在何時以及何處使用 dispatch_sync :
- 自定義串行隊列:在這個狀況下要非常小心!如果你正運行在一個隊列并調用dispatch_sync 放在同一個隊列,那你就百分百地創建了一個死鎖。
- 主隊列(串行):同上面的理由一樣,必須非常小心!這個狀況同樣有潛在的導致死鎖的情況。
- 并發隊列:這才是做同步工作的好選擇,不論是通過調度障礙,或者需要等待一個任務完成才能執行進一步處理的情況。
下面是一個關于在 dispatch_async 上如何以及何時使用不同的隊列類型的快速指導:
- 自定義串行隊列:當你想串行執行后臺任務并追蹤它時就是一個好選擇。這消除了資源爭用,因為你知道一次只有一個任務在執行。注意若你需要來自某個方法的數據,你必須內聯另一個 Block 來找回它或考慮使用 dispatch_sync。
- 主隊列(串行):這是在一個并發隊列上完成任務后更新 UI 的共同選擇。要這樣做,你將在一個 Block 內部編寫另一個 Block 。以及,如果你在主隊列調用 dispatch_async 到主隊列,你能確保這個新任務將在當前方法完成后的某個時間執行。
- 并發隊列:這是在后臺執行非 UI 工作的共同選擇。
| 函數\隊列 | 并發隊列 | 自己創建的串行隊列 | 主隊列 |
| 同步 | 不會創建新線程,串行執行 | 不會創建新線程,串行執行 |不會創建新線程,串行執行(可能會發生死鎖,如果最外層函數也是在主線程執行)|
| 異步 | 會創建多條新線程,同時執行多個任務 | 會創建新線程,串行執行任務 |不會創建新線程,串行執行|
8.Dispatch_apply
dispatch_apply函數是dispatch_sync函數和Dispatch Group的關聯API.該函數按指定的次數將指定的Block追加到指定的Dispatch Queue中,并等待全部處理執行結束.
dispatch_apply 表現得就像一個 for 循環,但它能并發地執行不同的迭代。這個函數是同步的,所以和普通的 for 循環一樣,它只會在所有工作都完成后才會返回。
那何時才適合用 dispatch_apply 呢?
- 自定義串行隊列:串行隊列會完全抵消 dispatch_apply 的功能;你還不如直接使用普通的 for 循環。
- 主隊列(串行):與上面一樣,在串行隊列上不適合使用 dispatch_apply 。還是用普通的 for 循環吧。
- 并發隊列:對于并發循環來說是很好選擇,特別是當你需要追蹤任務的進度時。
該方法會等apply 中多次迭代調用的block全都執行完成后,才會返回,所以dispatch_apply會阻塞當前線程,我們得避免在主線程中使用dispatch_apply。另外,說明中已經說的很清楚了,如果我們使用dispatch_get_global_queue創建的串行隊列,那么傳入的block任務是并發執行的。如果我們在串行隊列中執行該方法,會發生死鎖,所以第二個參數,千萬不要傳串行隊列。如果我們使用dispatch_queue_create創建的并發隊列,block任務依然是順序執行的。
下面看一下示例代碼以及運行結果:dispatch_apply / dispatch_queue_create /并發隊列:
dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
dispatch_apply(5, queue, ^(size_t index) {
[NSThread sleepForTimeInterval:index];
NSLog(@"并發%zu---%@",index,[NSThread currentThread]);
});
NSLog(@"done - %@",[NSThread currentThread]);
});
NSLog(@"主線程");
// 輸出結果:
2016-07-12 16:09:33.856 PractiseProject[5496:207665] 主線程
2016-07-12 16:09:33.857 PractiseProject[5496:207710] 并發0---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:34.860 PractiseProject[5496:207710] 并發1---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:36.864 PractiseProject[5496:207710] 并發2---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:39.867 PractiseProject[5496:207710] 并發3---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:43.872 PractiseProject[5496:207710] 并發4---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:43.873 PractiseProject[5496:207710] done - <NSThread: 0x7f950053a920>{number = 3, name = (null)}
dispatch_apply / dispatch_get_global_queue:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
dispatch_apply(5, queue, ^(size_t index) {
[NSThread sleepForTimeInterval:index];
NSLog(@"并發%zu---%@",index,[NSThread currentThread]);
});
NSLog(@"done - %@",[NSThread currentThread]);
});
NSLog(@"主線程");
// 輸出結果:
2016-07-12 16:15:26.634 PractiseProject[5544:210845] 主線程
2016-07-12 16:15:26.634 PractiseProject[5544:210882] 并發0---<NSThread: 0x7f8733816bc0>{number = 3, name = (null)}
2016-07-12 16:15:27.637 PractiseProject[5544:210887] 并發1---<NSThread: 0x7f8731517e10>{number = 2, name = (null)}
2016-07-12 16:15:28.637 PractiseProject[5544:210893] 并發2---<NSThread: 0x7f8731412d50>{number = 4, name = (null)}
2016-07-12 16:15:29.636 PractiseProject[5544:210899] 并發3---<NSThread: 0x7f8731448cc0>{number = 5, name = (null)}
2016-07-12 16:15:30.635 PractiseProject[5544:210882] 并發4---<NSThread: 0x7f8733816bc0>{number = 3, name = (null)}
2016-07-12 16:15:30.635 PractiseProject[5544:210893] done - <NSThread: 0x7f8731412d50>{number = 4, name = (null)}
9.Dispatch_suspend(掛起)/Dispatch_resume(恢復)
10.Dispatch Semaphore
dispatch_semaphore_t信號量是一種老式的線程概念,由非常謙卑的 Edsger W. Dijkstra 介紹給世界。信號量之所以比較復雜是因為它建立在操作系統的復雜性之上。
- (void)downloadImageURLWithString:(NSString *)URLString
{
// 1
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURL *url = [NSURL URLWithString:URLString];
__unused Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *error) {
if (error) {
XCTFail(@"%@ failed. %@", URLString, error);
}
// 2
dispatch_semaphore_signal(semaphore);
}];
// 3
dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, kDefaultTimeoutLengthInNanoSeconds);
if (dispatch_semaphore_wait(semaphore, timeoutTime)) {
XCTFail(@"%@ timed out", URLString);
}
}
在我們使用多線程處理多個并發任務,而這多個并發任務有資源競爭的時候,就需要一種機制,在資源不夠用時,讓新的任務處于等待狀態,當有可用資源時,等待的任務在按序依次執行。
像這一類問題除了可以用NSOperation,設置最大并發數外,還可以使用信號量。
這里涉及到的API有如下幾個:
dispatch_semaphore_t dispatch_semaphore_create(long value);
創建信號量的方法,如果初始值小于0,則會返回NULL,即信號量創建失敗。參數:value 表示初始的信號量個數,可以理解為資源個數,或者最大并發個數。
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
參數:第一個參數為信號量對象,第二個參數是等待超時的時間。
講解:該方法相當于任務開始前的檢查,需要注意的是該方法會阻塞當前線程。如果此時信號量的值大于0,會返回0,并且代碼會繼續往下執行;如果此時信號量的值等于0,那么此時該方法會阻塞當前線程,等待timeout 的時間。如果在超時的時間內,依然沒有可用的資源,那么該方法會返回一個非0的值。
該方法執行時,會使信號量的值減1。
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
該方法應該在任務執行完畢時調用,它會使信號量的值加0。
下面用一段實際代碼演示GCD信號量的使用:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 在子線程中執行,防止阻塞主線程
dispatch_async(queue, ^{
// 創建一個有3個資源的信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
for (int i = 0; i < 6; i++) {
// 檢測還有多少個資源,執行后會使資源數減少1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"開始執行任務%d---%@",i,[NSThread currentThread]);
[NSThread sleepForTimeInterval:6 - i];
NSLog(@"完成任務%d---%@",i,[NSThread currentThread]);
//表示資源使用完畢,會使資源數加1
dispatch_semaphore_signal(semaphore);
});
}
});
// 輸出結果:
2016-07-13 17:23:53.178 PractiseProject[4973:196435] 開始執行任務1---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:23:53.178 PractiseProject[4973:196436] 開始執行任務0---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
2016-07-13 17:23:53.178 PractiseProject[4973:196437] 開始執行任務2---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:23:57.179 PractiseProject[4973:196437] 完成任務2---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:23:57.179 PractiseProject[4973:196437] 開始執行任務3---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:23:58.182 PractiseProject[4973:196435] 完成任務1---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:23:58.182 PractiseProject[4973:196435] 開始執行任務4---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:23:59.179 PractiseProject[4973:196436] 完成任務0---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
2016-07-13 17:23:59.179 PractiseProject[4973:196436] 開始執行任務5---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
2016-07-13 17:24:00.184 PractiseProject[4973:196435] 完成任務4---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:24:00.184 PractiseProject[4973:196437] 完成任務3---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:24:00.184 PractiseProject[4973:196436] 完成任務5---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
11.Dispatch_once
dispatch_once
函數是保證在應用程序執行中只執行一次指定處理的API.
以線程安全的方式執行且僅執行其代碼塊一次。試圖訪問臨界區(即傳遞給 dispatch_once 的代碼)的不同的線程會在臨界區已有一個線程的情況下被阻塞,直到臨界區完成為止。
12.Dispatch I/O
在讀取大文件時,如果將文件分成合適的大小并使用Global Dispatch Queue并列讀取的話,就是使用
dispatch I/O
和Dispatch Data
13.Dispatch Source
GCD 的一個特別有趣的特性是 Dispatch Source,它基本上就是一個低級函數的 grab-bag ,能幫助你去響應或監測 Unix 信號、文件描述符、Mach 端口、VFS 節點,以及其它晦澀的東西。所有這些都超出了本教程討論的范圍,但你可以通過實現一個 Dispatch Source 對象并以一個相當奇特的方式來使用它來品嘗那些晦澀的東西。
有點不知道干嘛的,沒用過,可以看一下例子
GCD 深入理解:第二部分
14.Dispatch_source中的timer
dispatch_source_t 的類型有很多種:
#define DISPATCH_SOURCE_TYPE_DATA_ADD
#define DISPATCH_SOURCE_TYPE_DATA_OR
#define DISPATCH_SOURCE_TYPE_MACH_RECV
#define DISPATCH_SOURCE_TYPE_MACH_SEND
#define DISPATCH_SOURCE_TYPE_PROC
#define DISPATCH_SOURCE_TYPE_READ
#define DISPATCH_SOURCE_TYPE_SIGNAL
#define DISPATCH_SOURCE_TYPE_TIMER
#define DISPATCH_SOURCE_TYPE_VNODE
#define DISPATCH_SOURCE_TYPE_WRITE
#define DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
這里記錄的是dispatch_source 中定時器timer的用法。
這里我就用dispatch_source 封裝一個timer 的方法,可以在傳入兩個block,分別在循環執行,結束時執行。當然咯,下面這個方法還可以再加一個間隔時間參數。
- (void)startTimerWithTimeout:(NSTimeInterval)timeout eventBlock:(void (^)())eventBlock endBlock:(void (^)())endBlock
{
__block NSTimeInterval tempTimeout = timeout;
// 創建一個隊列,不管你創建什么類型的隊列,最終event_handler 都是在子線程中執行的
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 創建一個計時器類型的源
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設置定時器參數,定時器開始的時間、每隔多久執行一次、精度(可以延遲的納秒數,最高精度是0,實際還是會有偏差,感覺沒什么卵用)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"EVET ---- %@",[NSThread currentThread]);
if (tempTimeout <= 0) {
// 倒計時結束,取消源
dispatch_source_cancel(timer);
// 回到主線程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 倒計時結束時界面的UI的更新
if (endBlock) {
endBlock();
}
});
} else {
// 回到主線程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 做界面的UI的更新
if (eventBlock) {
eventBlock();
}
});
tempTimeout--;
}
});
dispatch_resume(timer);
}
封裝好之后,調用起來就非常的Easy啦,并且看起來也挺舒服的。其實你可以把上面的方法封裝成一個工具類方法。
[self startTimerWithTimeout:30 eventBlock:^{
NSLog(@"定時執行---%@",[NSThread currentThread]);
} endBlock:^{
NSLog(@"結束執行---%@",[NSThread currentThread]);
}];
15.Dispatch_source 中神奇的數據合并
上面介紹了dispatch_source 有多種類型,發現一種神奇的類型DISPATCH_SOURCE_TYPE_DATA_ADD,這種類型的source 有什么特別之處呢?假如我們并發執行多個任務,這種類型的source 會在任務完成時,將data 加1,然后如果主線程比較空閑,那么event_handler就會多次調用,而如果主線程恰好比較忙碌,那么就會將任務合并,event_handler調用次數就會比較少。
還是先上一個代碼范例:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
dispatch_source_set_event_handler(source, ^{
unsigned long completion = dispatch_source_get_data(source);
NSLog(@"完成的任務個數:%lu----%@",completion,[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
});
dispatch_resume(source);
dispatch_async(queue, ^{
NSLog(@"網絡線程---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
dispatch_source_merge_data(source, 1);
});
dispatch_async(queue, ^{
NSLog(@"網絡線程---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
dispatch_source_merge_data(source, 1);
});
//打印結果:
2016-07-13 15:38:45.911 PractiseProject[4130:150701] 網絡線程---<NSThread: 0x7f9e12c10c90>{number = 2, name = (null)}
2016-07-13 15:38:45.911 PractiseProject[4130:150694] 網絡線程---<NSThread: 0x7f9e12da6cb0>{number = 3, name = (null)}
2016-07-13 15:38:47.915 PractiseProject[4130:150701] 完成的任務個數:2----<NSThread: 0x7f9e12c10c90>{number = 2, name = (null)}
2016-07-13 15:38:47.915 PractiseProject[4130:150659] 更新UI
// 這也是打印結果:
2016-07-13 15:48:56.601 PractiseProject[4212:155405] 網絡線程---<NSThread: 0x7fe390e09270>{number = 2, name = (null)}
2016-07-13 15:48:56.601 PractiseProject[4212:155411] 網絡線程---<NSThread: 0x7fe390e0f800>{number = 3, name = (null)}
2016-07-13 15:48:59.304 PractiseProject[4212:155405] 完成的任務個數:1----<NSThread: 0x7fe390e09270>{number = 2, name = (null)}
2016-07-13 15:48:59.330 PractiseProject[4212:155377] 更新UI
2016-07-13 15:49:01.309 PractiseProject[4212:155415] 完成的任務個數:1----<NSThread: 0x7fe390e1d630>{number = 4, name = (null)}
2016-07-13 15:49:01.309 PractiseProject[4212:155377] 更新UI
它會根據主線程的繁忙與空閑,以及每個任務完成時的時間,減少返回次數或者每次返回。使用場景主要可以用在同時執行多個任務,任務的完成個數這種情況。
16.Queue-Specific
由于dispatch_get_current_queueAPI的移除,為了能夠判斷當前queue是否是之前創建的queue,我們可以利用dispatch_queue_set_specific和dispatch_get_specific給queue關聯一個context data,后面再利用這個標識獲取到context data。如果可以獲取到說明當前上下文是在自己創建的queue中,如果不能獲取到context data則表示當前是在其他隊列上。使用場景: 自己創建一個隊列,然后保證所有的操作都在該隊列上執行。XMPP中有比較多的dispatch_queue_set_specific和dispatch_get_specific使用案例。
設置標識和關聯的數據:
dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_SERIAL);
const void *queueSpecificKey = @"queueSpecificKey";
dispatch_queue_set_specific(queue, queueSpecificKey, &queueSpecificKey, NULL);
獲取關聯數據:
dispatch_get_specific(queueSpecificKey)
完整的示例:
dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_SERIAL);
// 當然這里也可以是其他類型的隊列
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_CONCURRENT);
const void *queueSpecificKey = @"queueSpecificKey";
dispatch_queue_set_specific(queue, queueSpecificKey, &queueSpecificKey, NULL);
dispatch_async(queue, ^{
NSLog(@"異步任務");
if (dispatch_get_specific(queueSpecificKey)) {
NSLog(@"com.haley.cn---1隊列");
} else {
NSLog(@"---1其他隊列");
}
});
NSLog(@"主線程,主隊列");
if (dispatch_get_specific(queueSpecificKey)) {
NSLog(@"com.haley.cn---2隊列");
} else {
NSLog(@"----2其他隊列");
}
// 打印結果:
2016-07-11 14:30:56.772 PractiseProject[3379:152363] 主線程,主隊列
2016-07-11 14:30:56.772 PractiseProject[3379:152363] ----2其他隊列
2016-07-11 14:30:56.772 PractiseProject[3379:152451] 異步任務
2016-07-11 14:30:56.773 PractiseProject[3379:152451] com.haley.cn---1隊列
dispatch_get_specific
所處的環境如果是在目標對列上時,就可以獲取到關聯的數據,否則就無法獲取關聯數據,返回NULL。看一看XMPP中的使用案例:
- (BOOL)activate:(XMPPStream *)aXmppStream{
__block BOOL result = YES;
dispatch_block_t block = ^{
if (xmppStream != nil) {
result = NO;
} else {
xmppStream = aXmppStream;
[xmppStream addDelegate:self delegateQueue:moduleQueue];
[xmppStream registerModule:self];
} };
if (dispatch_get_specific(moduleQueueTag)){
block();
} else {
dispatch_sync(moduleQueue, block);
return result;
}
為了保證block是在目標隊列上執行,先判斷當前是否在目標隊列上(如果能取到關聯數據,則說明在當前隊列上),如果在目標隊列上,直接執行block,否則就在目標隊列上同步執行。
注意死鎖的情況
情形一:在主線程中調度主隊列完成一個同步任務,會發生死鎖。
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor orangeColor];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"串行----線程:%@",[NSThread currentThread]);
});
}
如上代碼,界面永遠不會加載出來,里面的NSLog永遠也不會執行。原因是ViewDidLoad是在主隊列的主線程中執行,執行到dispatch_sync 時會阻塞住,等待dispatch_sync中的打印任務執行完畢。而dispatch_sync又會等viewDidLoad執行完畢,再開始執行,因此就互相等待發生死鎖。
情形二:在串行隊列的同步任務中再執行同步任務,會發生死鎖。
dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serial_queue, ^{
NSLog(@"串行1----線程:%@",[NSThread currentThread]);
dispatch_sync(serial_queue, ^{
NSLog(@"串行2----線程:%@",[NSThread currentThread]);
});
});
上面示例中的NSLog(@"串行1----線程:%@",[NSThread currentThread]);會打印.
但是NSLog(@"串行2----線程:%@",[NSThread currentThread]);永遠也不會執行。
因為串行隊列一次只能執行一個任務,執行完畢返回后,才會執行下一個任務,而外層任務的完成需要等待內層任務的結束,而內層任務的開始需要等外層任務結束。
其實情形一是情形二的一種特殊情況。
情形三:在串行隊列的異步任務中再嵌套執行同步任務,也會發生死鎖。
dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial_queue, ^{
NSLog(@"串行異步----線程:%@",[NSThread currentThread]);
dispatch_sync(serial_queue, ^{
NSLog(@"串行2----線程:%@",[NSThread currentThread]);
});
[NSThread sleepForTimeInterval:2.0];
});
同樣的,由于串行隊列一次只能執行一個任務,任務結束后,才能執行下一個任務。
所以異步任務的結束需要等里面同步任務結束,而里面同步任務的開始需要等外面異步任務結束,所以就相互等待,發生死鎖了。