GCD簡介
Grand Central Dispatch (GCD)是Apple開發的一個多核編程的較新的解決方法。它主要用于優化應用程序以支持多核處理器以及其他對稱多處理系統。它是一個在線程池模式的基礎上執行的并行任務。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。 -----百度百科
GCD好處
- GCD 會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
- GCD更接近底層,性能較高
GCD的兩個概念
1.任務
任務就是我們實際要執行的代碼塊。而執行這代碼的方式有兩種:同步執行(sync), 異步執行(async)。
- 同步執行(sync)
- 不具備開啟新線程的能力,只能在當前線程中執行任務
- 同步任務添加到隊列中,在添加的任務執行結束之前,會一直等待,直到隊列里面的任務完成之后才會繼續執行
- 異步執行(async)
- 可以開啟新線程,可以在新線程中執行任務
- 異步任務添加到隊列中,會繼續執行任務
異步執行async雖然有開啟新線程的能力,但是不一定會開啟新線程,這根任務所指定的隊列類型有關。
2.隊列
隊列是指執行任務的等待隊列,即用來存放任務的隊列。隊列遵循FIFO(先進先出),新任務總是插到隊列的末尾,讀取任務總是從隊列的頭部開始讀取,沒讀取一個任務,就會釋放一個任務。GCD中,有兩種隊列:串行隊列,并發隊列,兩者都遵循FIFO原則。
- 串行隊列(Serial Dispatch Queue)
- 每次只有一個任務被執行,讓任務一個接著一個執行。(只開啟一個線程)
- 并發隊列(Concurrent Dispatch Queue)
- 可以讓多個任務并發(同時)執行。(可以開啟多個線程,并且同時執行任務)
并發隊列的并發功能只有在異步(dispatch_async)函數下才有效
GCD使用步驟
1.創建隊列(串行或并發)
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
label
的參數表示隊列的唯一標識符,用于debug,可以傳空,推薦使用域名反寫,attr
用來決定是串行隊列,還是并發隊列。 DISPATCH_QUEUE_SERIAL
表示串行隊列,DISPATCH_QUEUE_CONCURRENT
表示并發隊列。
dispatch_queue_t queue = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
- 串行隊列,GCD提供了一個特殊的串行隊列:主隊列(Main Dispatch Queue)
- 所有放在主隊列中任務,都會放到主線程中執行
- 可以使用
dispatch_get_main_queue()
來獲取主隊列
// 主隊列的獲取方法
dispatch_queue_t queue = dispatch_get_main_queue();
- 并發隊列,GCD提供了全局并發隊列(Global Dispatch Queue)
- 可以使用
dispatch_get_global_queue
來獲取。
- 可以使用
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
identifier的優先級從高到低:
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
flags:默認傳0
2.將任務添加到任務的等待隊列中
GCD提供了同步執行任務的創建方法dispatch_sync
和異步執行任務的創建方法dispatch_async
。
// 同步執行任務創建方法
dispatch_sync(queue, ^{
// 這里放同步執行任務代碼
});
// 異步執行任務創建方法
dispatch_async(queue, ^{
// 這里放異步執行任務代碼
});
兩種因素相互組合有四種組合方式
同步 + 并發
異步 + 并發
同步 + 串行
異步 + 串行
但是加上說的兩種特殊隊列:全局并發隊列,主隊列。全局并發隊列可以當成普通的并發隊列來處理,方式主隊列有點特殊,需要額外處理,這就多了兩種方式。
同步 + 主隊列
異步 + 主隊列
組合效果如下表格
并發隊列 | 串行隊列 | 主隊列 | |
---|---|---|---|
同步 | 沒有開啟新線程,串行執行任務 | 沒有開啟新線程,串行執行任務 | 主線程調用:死鎖卡住 其他線程調用:沒有開發新線程,串行執行任務 |
異步 | 開啟新線程,并發執行任務 | 開啟新線程,串行執行任務 | 沒有開啟新線程,串行執行任務 |
GCD的使用
1.同步+并發
/**
同步并發執行
在當前線程執行任務,不會開啟新線程,執行完一個任務,在執行下一個任務
*/
- (void)syncWithConcurrent{
NSLog(@"currentThread -- %@", [NSThread currentThread]);
dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"111----%@", [NSThread currentThread]);
}
});
//追加任務
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"222----%@", [NSThread currentThread]);
}
});
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"333----%@", [NSThread currentThread]);
}
});
}
2018-05-16 14:38:55.788806+0800 MultiThread[5657:399983] currentThread -- <NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:55.789158+0800 MultiThread[5657:399983] begin
2018-05-16 14:38:56.790163+0800 MultiThread[5657:399983] 111----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:57.790860+0800 MultiThread[5657:399983] 111----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:58.791470+0800 MultiThread[5657:399983] 222----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:59.794281+0800 MultiThread[5657:399983] 222----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:39:00.795110+0800 MultiThread[5657:399983] 333----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:39:01.795762+0800 MultiThread[5657:399983] 333----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:39:01.796005+0800 MultiThread[5657:399983] end
從上述例子中可以看到
- 所有的任務都是在當前線程(主線程)中執行的。并沒有開啟新線程。
- 按順序執行任務。并發隊列雖然可以開啟多個線程,同時執行任務,但是不能創建新線程。所以并發并不存在,實際效果相當于串行。
2.異步 + 并發
/**
可以開啟多個線程,交替執行任務
*/
- (void)asyncConcurrent{
NSLog(@"currentThread -- %@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"111----%@", [NSThread currentThread]);
}
});
//追加任務
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"222----%@", [NSThread currentThread]);
}
});
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"333----%@", [NSThread currentThread]);
}
});
NSLog(@"end");
}
2018-05-16 14:45:25.548159+0800 MultiThread[5724:408572] currentThread -- <NSThread: 0x604000068c40>{number = 1, name = main}
2018-05-16 14:45:25.548681+0800 MultiThread[5724:408572] begin
2018-05-16 14:45:25.548907+0800 MultiThread[5724:408572] end
2018-05-16 14:45:26.549549+0800 MultiThread[5724:408638] 111----<NSThread: 0x60000007a8c0>{number = 5, name = (null)}
2018-05-16 14:45:26.549660+0800 MultiThread[5724:408804] 333----<NSThread: 0x600000263d80>{number = 7, name = (null)}
2018-05-16 14:45:26.549679+0800 MultiThread[5724:408803] 222----<NSThread: 0x600000263380>{number = 6, name = (null)}
2018-05-16 14:45:27.550524+0800 MultiThread[5724:408803] 222----<NSThread: 0x600000263380>{number = 6, name = (null)}
2018-05-16 14:45:27.550526+0800 MultiThread[5724:408804] 333----<NSThread: 0x600000263d80>{number = 7, name = (null)}
2018-05-16 14:45:27.550526+0800 MultiThread[5724:408638] 111----<NSThread: 0x60000007a8c0>{number = 5, name = (null)}
從上述例子可以看出:
- 除了當前線程(主線程),系統有開啟了三個線程,任務交替執行。
- begin和end先打印了,說明線程并沒有等待,開啟了新線程,在新線程中執行任務。
3.同步 + 串行
/**
不會創建新的線程,在當前線程執行,任務串行執行
*/
- (void)syncSerial{
NSLog(@"currentThread -- %@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"111----%@", [NSThread currentThread]);
}
});
//追加任務
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"222----%@", [NSThread currentThread]);
}
});
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"333----%@", [NSThread currentThread]);
}
});
NSLog(@"end");
}
2018-05-16 14:59:32.394332+0800 MultiThread[5889:426076] currentThread -- <NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:32.394566+0800 MultiThread[5889:426076] begin
2018-05-16 14:59:33.395773+0800 MultiThread[5889:426076] 111----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:34.397207+0800 MultiThread[5889:426076] 111----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:35.398690+0800 MultiThread[5889:426076] 222----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:36.399925+0800 MultiThread[5889:426076] 222----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:37.400594+0800 MultiThread[5889:426076] 333----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:38.401101+0800 MultiThread[5889:426076] 333----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:38.401345+0800 MultiThread[5889:426076] end
從上述例子可以看出:
- 所有任務都是在當前線程(主線程)中執行的,并沒有開啟新的線程(同步執行不具備開啟新線程的能力)
- 需要等待隊列任務的任務完成
- 任務按順序執行
4.異步 + 串行
/**
創建新線程,但是任務是串行的,會一個一個執行
*/
- (void)asyncSerial{
NSLog(@"currentThread -- %@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"111----%@", [NSThread currentThread]);
}
});
//追加任務
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"222----%@", [NSThread currentThread]);
}
});
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"333----%@", [NSThread currentThread]);
}
});
NSLog(@"end");
}
2018-05-16 15:08:48.563433+0800 MultiThread[6045:441312] currentThread -- <NSThread: 0x60400006b400>{number = 1, name = main}
2018-05-16 15:08:48.563794+0800 MultiThread[6045:441312] begin
2018-05-16 15:08:48.564524+0800 MultiThread[6045:441312] end
2018-05-16 15:08:49.569620+0800 MultiThread[6045:441364] 111----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:50.570245+0800 MultiThread[6045:441364] 111----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:51.574290+0800 MultiThread[6045:441364] 222----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:52.578832+0800 MultiThread[6045:441364] 222----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:53.580927+0800 MultiThread[6045:441364] 333----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:54.586438+0800 MultiThread[6045:441364] 333----<NSThread: 0x600000464080>{number = 5, name = (null)}
從上述例子可以看出:
- 開啟了一個新的線程
- 所有任務都是在begin和end打印后執行的
- 任務是按順序執行的
5.同步 + 主隊列
- 在主線程中調用
/**
卡死
*/
- (void)syncMain{
NSLog(@"currentThread -- %@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queueu = dispatch_get_main_queue();
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"111----%@", [NSThread currentThread]);
}
});
//追加任務
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"222----%@", [NSThread currentThread]);
}
});
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"333----%@", [NSThread currentThread]);
}
});
NSLog(@"end");
}
2018-05-16 15:15:40.866943+0800 MultiThread[6145:451264] currentThread -- <NSThread: 0x604000075c80>{number = 1, name = main}
2018-05-16 15:15:40.867132+0800 MultiThread[6145:451264] begin
(lldb)
我們將syncMain()
方法放到主線程的隊列中執行,而syncMain()
方法中,又在主線程中追加了打印任務。并發情況下,syncMain()
在等值打印任務完成,而打印任務又在等著syncMain()
的完成,相互等待,導致卡死。
- 在其他線程中調用
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
2018-05-16 15:21:21.114561+0800 MultiThread[6228:461243] currentThread -- <NSThread: 0x600000470600>{number = 5, name = (null)}
2018-05-16 15:21:21.114724+0800 MultiThread[6228:461243] begin
2018-05-16 15:21:22.153793+0800 MultiThread[6228:461100] 111----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:23.154493+0800 MultiThread[6228:461100] 111----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:24.156163+0800 MultiThread[6228:461100] 222----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:25.157599+0800 MultiThread[6228:461100] 222----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:26.159337+0800 MultiThread[6228:461100] 333----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:27.160866+0800 MultiThread[6228:461100] 333----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:27.161208+0800 MultiThread[6228:461243] end
上述例子可以看出:
- 所有任務都是在主線程中執行,沒有開啟新線程
- 同步執行任務都是需要等待隊列的任務執行結束
- 串行隊列,每次只執行一個任務
因為syncMain()
任務放在了新開啟的線程中,而打印任務都追加在主隊列中,此時主隊列并沒有其他執行的任務,所以可以直接執行打印任務。
6.異步 + 主隊列
/**
只在主線程中執行任務,執行完一個任務,再執行下一個任務
*/
- (void)asyncMain{
NSLog(@"currentThread -- %@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queueu = dispatch_get_main_queue();
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"111----%@", [NSThread currentThread]);
}
});
//追加任務
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"222----%@", [NSThread currentThread]);
}
});
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"333----%@", [NSThread currentThread]);
}
});
NSLog(@"end");
}
018-05-16 15:28:29.018407+0800 MultiThread[6308:470456] currentThread -- <NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:29.018601+0800 MultiThread[6308:470456] begin
2018-05-16 15:28:29.018752+0800 MultiThread[6308:470456] end
2018-05-16 15:28:30.056933+0800 MultiThread[6308:470456] 111----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:31.057944+0800 MultiThread[6308:470456] 111----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:32.059107+0800 MultiThread[6308:470456] 222----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:33.060579+0800 MultiThread[6308:470456] 222----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:34.062021+0800 MultiThread[6308:470456] 333----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:35.063583+0800 MultiThread[6308:470456] 333----<NSThread: 0x60400007fa40>{number = 1, name = main}
從上述例子中可以看出:
- 所有任務都是在主隊列中執行
- 異步執行不會做人接等待,可以繼續執行任務
- 任務按順序執行的,主隊列是串行隊列
GCD線程之間的通信
在其他線程中處理下載圖片,請求數據等操作,在主線程中刷新UI,數據,就需要用到線程之間的通訊。
- (void)communication{
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(global, ^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1--- %@", [NSThread currentThread]);
}
dispatch_async(main, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@", [NSThread currentThread]);
});
});
}
2018-05-16 15:47:23.444937+0800 MultiThread[6516:498644] 1--- <NSThread: 0x600000277f00>{number = 5, name = (null)}
2018-05-16 15:47:25.450123+0800 MultiThread[6516:498644] 1--- <NSThread: 0x600000277f00>{number = 5, name = (null)}
2018-05-16 15:47:27.455633+0800 MultiThread[6516:498644] 1--- <NSThread: 0x600000277f00>{number = 5, name = (null)}
2018-05-16 15:47:29.456492+0800 MultiThread[6516:498523] 2---<NSThread: 0x6040000647c0>{number = 1, name = main}
從上述例子中可以看出:
- 可以看到在其他線程中先執行任務,執行完了之后回到主線程執行主線程的相應操作。
GCD的其他方法
1.柵欄方法(dispatch_barrier_async)
如果我們需要實現異步請求兩個數據,同時B請求必須在A請求完成后才能繼續請求,這就需要一種手段分割開A和B請求,這就是柵欄方法dispatch_barrier_async
。dispatch_barrier_async
方法會等待前面添加到并發隊列中的任務完成后,截止執行dispatch_barrier_async
的block任務,最后回復正常的異步并發操作。
- (void)barrier{
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_barrier_async(queue, ^{
// 追加任務 barrier
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印當前線程
}
});
}
2018-05-16 16:08:35.729731+0800 MultiThread[6772:531874] 1---<NSThread: 0x604000465b00>{number = 5, name = (null)}
2018-05-16 16:08:35.729844+0800 MultiThread[6772:531870] 2---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:37.732856+0800 MultiThread[6772:531870] 2---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:37.732856+0800 MultiThread[6772:531874] 1---<NSThread: 0x604000465b00>{number = 5, name = (null)}
2018-05-16 16:08:39.735677+0800 MultiThread[6772:531870] barrier---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:41.736375+0800 MultiThread[6772:531870] barrier---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:43.740706+0800 MultiThread[6772:531870] 3---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:43.740710+0800 MultiThread[6772:531874] 4---<NSThread: 0x604000465b00>{number = 5, name = (null)}
2018-05-16 16:08:45.741610+0800 MultiThread[6772:531870] 3---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:45.741610+0800 MultiThread[6772:531874] 4---<NSThread: 0x604000465b00>{number = 5, name = (null)}
上述例子可以看出:
- 在執行完柵欄前面的操作之后,才執行柵欄操作,最后再執行柵欄后邊的操作
2.延遲執行方法(dispatch_after)
/**
* 延時執行方法 dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@", [NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"after---%@", [NSThread currentThread]);
});
}
2018-05-16 16:16:26.406706+0800 MultiThread[6866:543950] currentThread---<NSThread: 0x60000007f6c0>{number = 1, name = main}
2018-05-16 16:16:28.600856+0800 MultiThread[6866:543950] after---<NSThread: 0x60000007f6c0>{number = 1, name = main}
可以看出來,實際還是存在誤差的,所以要求不嚴格可以說還是用這個方法。
3.GCD一次性代碼(dispatch_once)
創建單例、或者需要整個程序運行期間只執行一次的代碼,我們就用到了dispatch_once
方法。
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 這里的代碼指揮執行一次
});
}
4.循環遍歷方法(dispatch_apply)
dispatch_apply
方法會開啟多個線程來異步遍歷數據,無論串行隊列,還是并發隊列,該方法都會等待全部任務完畢,有點像同步操作。
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
2018-05-16 16:34:57.901523+0800 MultiThread[7186:573598] apply---begin
2018-05-16 16:34:57.901834+0800 MultiThread[7186:573598] 0---<NSThread: 0x604000061f40>{number = 1, name = main}
2018-05-16 16:34:57.901899+0800 MultiThread[7186:573672] 1---<NSThread: 0x6000002683c0>{number = 5, name = (null)}
2018-05-16 16:34:57.901937+0800 MultiThread[7186:573662] 2---<NSThread: 0x600000267400>{number = 6, name = (null)}
2018-05-16 16:34:57.901974+0800 MultiThread[7186:573665] 3---<NSThread: 0x600000268280>{number = 7, name = (null)}
2018-05-16 16:34:57.902303+0800 MultiThread[7186:573598] 4---<NSThread: 0x604000061f40>{number = 1, name = main}
2018-05-16 16:34:57.902560+0800 MultiThread[7186:573598] apply---end
5.GCD隊列組(dispatch_group)
有時候會遇到這種情況,要求異步執行兩個耗時任務,然后當兩個任務完成后調回帶主線程,執行主線程的任務。dispatch_group
可以把相關的任務歸并到一個組內來執行,通過監聽組內所有任務的執行情況來做相應處理。
5.1 dispatch_group_create
用于創建任務組
dispatch_group_t dispatch_group_create(void);
5.2 dispatch_group_async
把異步任務提交到指定的任務組和指定的隊列執行
void dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
- group ——對應的任務組,之后可以通過dispatch_group_wait或者dispatch_group_notify監聽任務組內任務的執行情況
- queue ——block任務執行的線程隊列,任務組內不同任務的隊列可以不同
- block —— 執行任務的block
5.3 dispatch_group_enter
用于添加對應任務組中的未執行完畢的任務數,執行一次,未執行完畢的任務數加1,當未執行完畢任務數為0的時候,才會使dispatch_group_wait
解除阻塞和dispatch_group_notify
的block執行
void dispatch_group_enter(dispatch_group_t group);
dispatch_group_leave
用于減少任務組中的未執行完畢的任務數,執行一次,未執行完畢的任務數減1,dispatch_group_enter
和dispatch_group_leave
要匹配,不然系統會認為group任務沒有執行完畢
void dispatch_group_leave(dispatch_group_t group);
5.4 dispatch_group_wait
等待組任務完成,會阻塞當前線程,當任務組執行完畢時,才會解除阻塞當前線程
long dispatch_group_wait(dispatch_group_t group,
dispatch_time_t timeout);
- group ——需要等待的任務組
- timeout ——等待的超時時間(即等多久),單位為dispatch_time_t。如果設置為DISPATCH_TIME_FOREVER,則會一直等待(阻塞當前線程),直到任務組執行完畢
5.5 dispatch_group_notify
待任務組執行完畢時調用,不會阻塞當前線程
void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
- group ——需要監聽的任務組
- queue ——block任務執行的線程隊列,和之前group執行的線程隊列無關
- block ——任務組執行完畢時需要執行的任務block
示例
- (void)group{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"group one start");
NSLog(@"CurrentThread --- %@", [NSThread currentThread]);
dispatch_group_async(group, queue, ^{
dispatch_async(queue, ^{
NSLog(@"dispatch_async --- %@", [NSThread currentThread]);
sleep(3); //模擬異步請求
NSLog(@"group one finished");
});
});
dispatch_group_notify(group, queue, ^{
NSLog(@"dispatch_group_notify --- %@", [NSThread currentThread]);
NSLog(@"group finished");
});
}
2018-05-17 09:32:09.284063+0800 MultiThread[10977:949950] group one start
2018-05-17 09:32:09.284450+0800 MultiThread[10977:949950] CurrentThread --- <NSThread: 0x604000077880>{number = 1, name = main}
2018-05-17 09:32:09.284987+0800 MultiThread[10977:950236] dispatch_async --- <NSThread: 0x6000004794c0>{number = 5, name = (null)}
2018-05-17 09:32:09.285092+0800 MultiThread[10977:950242] dispatch_group_notify --- <NSThread: 0x600000479a00>{number = 6, name = (null)}
2018-05-17 09:32:09.286039+0800 MultiThread[10977:950242] group finished
2018-05-17 09:32:12.291271+0800 MultiThread[10977:950236] group one finished
可以看出來,在group中嵌套了一個異步任務是,開啟了一個新線程,新線程是直接返回,group就認為任務完成了。解決上述問題,就需要用了另外兩個方法。
- (void)group{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"group one start");
dispatch_group_enter(group);
NSLog(@"CurrentThread --- %@", [NSThread currentThread]);
dispatch_group_async(group, queue, ^{
dispatch_async(queue, ^{
NSLog(@"dispatch_async --- %@", [NSThread currentThread]);
sleep(3); //模擬異步請求
NSLog(@"group one finished");
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, queue, ^{
NSLog(@"dispatch_group_notify --- %@", [NSThread currentThread]);
NSLog(@"group finished");
});
}
2018-05-17 09:36:48.794270+0800 MultiThread[11054:959258] group one start
2018-05-17 09:36:48.794480+0800 MultiThread[11054:959258] CurrentThread --- <NSThread: 0x604000075080>{number = 1, name = main}
2018-05-17 09:36:48.794824+0800 MultiThread[11054:959669] dispatch_async --- <NSThread: 0x60000046d400>{number = 5, name = (null)}
2018-05-17 09:36:51.798630+0800 MultiThread[11054:959669] group one finished
2018-05-17 09:36:51.798986+0800 MultiThread[11054:959669] dispatch_group_notify --- <NSThread: 0x60000046d400>{number = 5, name = (null)}
2018-05-17 09:36:51.799302+0800 MultiThread[11054:959669] group finished
剛開始,未完成任務數量+1,等到異步任務完成后,數量-1,然后通知group,任務完成了,就會回調dispatch_group_notify
的block內容。
6. GCD信號量(dispatch_semaphore)
dispatch_semaphore
是GCD中的信號量,可以處理多線程中線程并發的問題,也可以用作同步處理。
創建信號量,里面的參數是表示信號的總量,值必須>=0
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
發送一個信號,信號量的總數會+1
dispatch_semaphore_signal(semaphore);
信號等待,當信號量的總數<=0的時候,會一直等待,直到信號量的總數>0的時候才會繼續下面的執行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
注意:dispatch_semaphore_wait
當信號量的總數<=0時候,該函數所在的線程就會等待,而信號量的總數>0的時候,該函數就會繼續往下執行,同時信號量的總數-1
這里有個等待時間的參數,如果在等待的時間內獲得了信號量,那么函數繼續往下執行,如果等待時間內信號量一直為0,那么函數也會繼續往下執行了
/**
線程同步
*/
- (void)semaphoreDemoOne{
NSLog(@"currentThread --- %@", [NSThread currentThread]);
NSLog(@"semaphore --- begin");
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block int number = 0;
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"samaphore --- end, number = %d", number);
}
2018-05-17 10:35:31.468369+0800 MultiThread[11654:1048990] currentThread --- <NSThread: 0x60000006f280>{number = 1, name = main}
2018-05-17 10:35:31.468523+0800 MultiThread[11654:1048990] semaphore --- begin
2018-05-17 10:35:33.472110+0800 MultiThread[11654:1049057] 1---<NSThread: 0x600000460940>{number = 5, name = (null)}
2018-05-17 10:35:33.472321+0800 MultiThread[11654:1048990] samaphore --- end, number = 100
如果注釋dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
這句代碼,number 就等于0了。block塊異步執行添加到了全局并發隊列里,所以程序在主線程會跳過block塊(同時開辟子線程異步執行block塊),執行塊外的代碼dispatch_semaphore_wait
,因為semaphore
信號量為0,且時間為DISPATCH_TIME_FOREVER
,所以會阻塞當前線程(主線程),進而只執行子線程的block塊,直到執行塊內部的dispatch_semaphore_signal
使得信號量+1。正在被阻塞的線程(主線程)會恢復繼續執行。這樣保證了線程之間的同步。
售票加鎖示例
/**
模擬兩個窗口售票
*/
- (void)initTickets{
self.semaphoreTicket = dispatch_semaphore_create(1);
self.ticketCount = 10;
dispatch_queue_t queueOne = dispatch_queue_create("com.easyFly.one", 0);
dispatch_queue_t queueTwo = dispatch_queue_create("com.easyFly.Two", 0);
__weak typeof(self) weakSelf = self;
dispatch_async(queueOne, ^{
[weakSelf saleTicket];
});
dispatch_async(queueTwo, ^{
[weakSelf saleTicket];
});
}
- (void)saleTicket{
while (true) {
// dispatch_semaphore_wait(_semaphoreTicket, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) {
self.ticketCount --;
NSLog(@"剩余票 --- %ld 窗口 --- %@", self.ticketCount, [NSThread currentThread]);
}else{
NSLog(@"所有票均已售完");
// 相當于解鎖
// dispatch_semaphore_signal(_semaphoreTicket);
break;
}
// dispatch_semaphore_signal(_semaphoreTicket);
}
}
2018-05-17 10:58:17.676200+0800 MultiThread[12006:1085650] 剩余票 --- 9 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676247+0800 MultiThread[12006:1085651] 剩余票 --- 9 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085651] 剩余票 --- 8 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085650] 剩余票 --- 8 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676578+0800 MultiThread[12006:1085651] 剩余票 --- 7 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.676641+0800 MultiThread[12006:1085650] 剩余票 --- 6 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676925+0800 MultiThread[12006:1085650] 剩余票 --- 4 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676751+0800 MultiThread[12006:1085651] 剩余票 --- 5 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.677039+0800 MultiThread[12006:1085650] 剩余票 --- 3 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.677602+0800 MultiThread[12006:1085651] 剩余票 --- 2 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.677828+0800 MultiThread[12006:1085650] 剩余票 --- 1 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.678013+0800 MultiThread[12006:1085651] 剩余票 --- 0 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.678449+0800 MultiThread[12006:1085650] 所有票均已售完
2018-05-17 10:58:17.678646+0800 MultiThread[12006:1085651] 所有票均已售完
如果注釋semaphore相關代碼,就會出現一張票賣了兩次的情況。打開注釋后
2018-05-17 11:00:22.375800+0800 MultiThread[12051:1089200] 剩余票 --- 9 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.376091+0800 MultiThread[12051:1089202] 剩余票 --- 8 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.376561+0800 MultiThread[12051:1089200] 剩余票 --- 7 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.376950+0800 MultiThread[12051:1089202] 剩余票 --- 6 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.377360+0800 MultiThread[12051:1089200] 剩余票 --- 5 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.377834+0800 MultiThread[12051:1089202] 剩余票 --- 4 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.378057+0800 MultiThread[12051:1089200] 剩余票 --- 3 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.378413+0800 MultiThread[12051:1089202] 剩余票 --- 2 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.378687+0800 MultiThread[12051:1089200] 剩余票 --- 1 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.378899+0800 MultiThread[12051:1089202] 剩余票 --- 0 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.378996+0800 MultiThread[12051:1089200] 所有票均已售完
2018-05-17 11:00:22.379244+0800 MultiThread[12051:1089202] 所有票均已售完
這就正常的售票了。
解析一下,兩個隊列相當于兩個售票窗口,初始化信號量為1,當一個隊列進入售票過程,dispatch_semaphore_wait
使得信號量-1,信號量為0,并執行下方售票代碼,dispatch_semaphore_signal
使得信號量+1,相當于解鎖。實現了線程安全。
參考資料
iOS多線程:『GCD』詳盡總結
iOS GCD之dispatch_semaphore(信號量)
關于iOS多線程,你看我就夠了