本文內容
任務、隊列的概念、創建方式
任務 + 隊列的6種組合的執行方式
線程間如何通信
dispatch_once、dispatch_after、dispatch_apply(快速迭代)、dispatch_barrier(柵欄函數)、dispatch_group(隊列組)、dispatch_semaphore(信號量)如何實現線程安全與線程同步
iOS多線程demo地址
上文說到iOS 多線程- pThread和NSThread
這篇文章來講講GCD
GCD ??????????的優點
- 可用于多核的并行運算
- 會自動利用更多的CPU內核
- 自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
- 只用關注執行什么任務,不用編寫任何線程管理代碼
1.任務
任務:執行的操作,放在block中的代碼
執行任務的方式有兩種,主要區別是:是否等待隊列中的任務執行結束,是否具備開啟新線程的能力
。
同步執行(sync):同步添加當前任務到指定的隊列中,在隊列中的任務全部結束之前,
會一直等待
,直到隊列中的任務全部完成后,才開始下一個任務,只能在當前線程中執行任務,不具備
開啟線程的能力。異步執行 (async):異步添加當前任務到指定隊列中,
不會等待
隊列的任務執行結束,直接開始執行下一個任務,可以在新的線程中執行任務,具備
開啟線程的能力。
*注意:異步執行 (async)雖然具有開啟線程的能力,但是不一定會開啟新的線程,這跟任務所指定的隊列有關
2.隊列
隊列:存放任務的隊列,隊列是一種特殊的線性表,采用FIFO(先進先出)的原則,即新任務總是被插入到隊列末尾,而讀取任務總是從隊列的頭部開始讀取,每讀取一個任務,隊列中則釋放一個任務。
隊列有兩種方式,都滿足FIFO原則,主要區別是:執行順序不同,開啟線程數不同
- 串行隊列(Serial Dispatch Queue ):只開啟
一個
線程,一個
任務執行完畢后,再執行下一個任務 - 并行隊列(Concurrent Dispatch Queue)?????????????? ??????????????????????????????????????????????????????????????:可以開啟
多個
線程,并且同時
執行多個任務
3.使用步驟
- 創建一個隊列
- 將任務添加到隊列中,系統根據任務執行方式(同步、異步)進行執行
3.1 隊列的創建、獲取
使用dispatch_queue_create
創建隊列
第一個參數:隊列的唯一標識符,用于DEBUG,可以為空,推薦使用應用程序ID這種逆序全局域名。
第二個參數:隊列類型,串行隊列DISPATCH_QUEUE_SERIAL
,并行隊列DISPATCH_QUEUE_CONCURRENT
//創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.queque", DISPATCH_QUEUE_SERIAL);
//創建并行隊列
dispatch_queue_t queue2 = dispatch_queue_create("com.xiuxiu.queque", DISPATCH_QUEUE_CONCURRENT);
主隊列Main Dispatch Queue
: GCD提供一種特殊串行隊列:
- 所有放在主隊列中的任務,都會放到主線程中執行
- 可使用
dispatch_get_main_queue()
獲取主隊列
主隊列獲取方法
dispatch_queue_t queue = dispatch_get_main_queue()
全局并發隊列Global Dispatch Queue
: GCD默認提供的全局并發隊列
并發隊列獲取方法:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
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
第二個參數:沒有使用,用0 即可
3.2 任務的執行方式
dispatch_async(queue, ^{
//異步執行任務代碼
});
dispatch_sync(queue, ^{
//同步執行任務代碼
});
第一個參數是隊列,那么隊列 + 任務 執行方式就有6種組合(加上主隊列)
同步執行 + 串行隊列
異步執行 + 串行隊列
同步執行 + 并行隊列
異步執行 + 并行隊列
同步執行 + 主隊列
異步執行 + 主隊列
3.2.1.同步執行 + 串行隊列
/*
同步執行 + 串行隊列
不會開啟新線程,在當前線程中執行任務,一個任務執行完畢后,再執行下一個任務
*/
- (void)syncSerial{
NSLog(@" syncSerial start");
dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.syncSerial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
NSLog(@" syncSerial end");
}
:
前面的編號代表進程
的編號,一個APP的就是一個進程,進程編號總是一致的;
:
后面的編號代表線程
的編號, 21898代表線程的編號
輸出結果:
- 在當前線程中執行任務,沒有開啟新線程(同步執行不具備開啟線程能力),根據線程編號看出
- 所有任務都在
syncSerial start
和syncSerial end
之間執行(同步執行需要等待隊列中的任務執行結束) - 任務按順序執行(串行隊列每次只有一個任務被執行,一個任務執行完畢后,再執行下一個任務)
2018-12-27 10:19:08.371664+0800 Thread[908:21898] syncSerial start
2018-12-27 10:19:08.371980+0800 Thread[908:21898] 任務一,i = 0
2018-12-27 10:19:09.372354+0800 Thread[908:21898] 任務一,i = 1
2018-12-27 10:19:10.372865+0800 Thread[908:21898] 任務一,i = 2
2018-12-27 10:19:11.373588+0800 Thread[908:21898] 任務二,i = 0
2018-12-27 10:19:12.374936+0800 Thread[908:21898] 任務二,i = 1
2018-12-27 10:19:13.375258+0800 Thread[908:21898] 任務二,i = 2
2018-12-27 10:19:14.376006+0800 Thread[908:21898] syncSerial end
3.2.2.異步執行 + 串行隊列
/*
異步執行 + 串行隊列
會開啟新線程,但是因為隊列是串行的,一個任務執行完畢后,再執行下一個任務
*/
- (void)asyncSerial{
NSLog(@" asyncSerial start");
dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.asyncSerial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
NSLog(@" asyncSerial end");
}
輸出結果:
- 開啟一條線程(異步執行具備開啟線程的能力,因為是串行隊列,只開啟了一條線程)
- 所有任務都在
asyncSerial start
和asyncSerial end
之后執行(異步執行不會等待,繼續執行任務) - 任務按順序執行(串行隊列每次只有一個任務被執行,任務一個接著一個執行)
2018-12-27 10:43:17.947620+0800 Thread[1105:32316] asyncSerial start
2018-12-27 10:43:17.947916+0800 Thread[1105:32316] asyncSerial end
2018-12-27 10:43:17.948034+0800 Thread[1105:32360] 任務一,i = 0
2018-12-27 10:43:18.953286+0800 Thread[1105:32360] 任務一,i = 1
2018-12-27 10:43:19.956284+0800 Thread[1105:32360] 任務一,i = 2
2018-12-27 10:43:20.961804+0800 Thread[1105:32360] 任務二,i = 0
2018-12-27 10:43:21.965620+0800 Thread[1105:32360] 任務二,i = 1
2018-12-27 10:43:22.967181+0800 Thread[1105:32360] 任務二,i = 2
3.2.3. 同步執行 + 并行隊列
/*
同步執行 + 并行隊列
不會開啟新線程,在當前線程中執行任務,一個任務執行完畢后,再執行下一個任務
*/
- (void)syncConcurrent{
NSLog(@" syncConcurrent start");
dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.syncConcurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
NSLog(@" syncConcurrent end");
}
輸出結果:
- 在當前線程中執行任務,沒有開啟新線程(同步執行不具備開啟線程能力),根據線程編號看出,上面講過
- 所有任務都在
syncConcurrent start
和syncConcurrent end
之間執行(同步執行需要等待隊列中的任務執行結束) - 任務按順序執行,雖然并行隊列可以開啟多個線程,并且同時執行多個任務,但是因為同步執行不具備開啟線程的能力,只有當前這一個線程,而且同步執行需要等待隊列中的任務執行結束,再執行下一個任務,所以任務只能一個接一個按照順序執行。
2018-12-27 10:54:22.992819+0800 Thread[1210:37289] syncConcurrent start
2018-12-27 10:54:22.993023+0800 Thread[1210:37289] 任務一,i = 0
2018-12-27 10:54:23.994423+0800 Thread[1210:37289] 任務一,i = 1
2018-12-27 10:54:24.995012+0800 Thread[1210:37289] 任務一,i = 2
2018-12-27 10:54:25.996057+0800 Thread[1210:37289] 任務二,i = 0
2018-12-27 10:54:26.997429+0800 Thread[1210:37289] 任務二,i = 1
2018-12-27 10:54:27.998885+0800 Thread[1210:37289] 任務二,i = 2
2018-12-27 10:54:29.000327+0800 Thread[1210:37289] syncConcurrent end
3.2.4.異步執行 + 并發隊列
/*
異步執行 + 并行隊列
開啟多個線程,任務交替執行
*/
- (void)asyncConcurrent{
NSLog(@" asyncConcurrent start");
dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
NSLog(@" asyncConcurrent end");
}
輸出結果:
- 開啟了2個新線程(異步執行具備開啟線程的能力)
- 所有任務都在
asyncConcurrent start
和asyncConcurrent end
之后執行(異步執行不會等待,繼續執行任務) - 任務交替執行(異步執行具備開啟線程的能力,并且并行隊列可以開啟多個線程,同時執行多個任務)
2018-12-27 11:18:23.174090+0800 Thread[1210:37289] asyncConcurrent start
2018-12-27 11:18:23.174253+0800 Thread[1210:37289] asyncConcurrent end
2018-12-27 11:18:23.174351+0800 Thread[1210:47038] 任務二,i = 0
2018-12-27 11:18:23.174401+0800 Thread[1210:37362] 任務一,i = 0
2018-12-27 11:18:24.177759+0800 Thread[1210:37362] 任務一,i = 1
2018-12-27 11:18:24.177759+0800 Thread[1210:47038] 任務二,i = 1
2018-12-27 11:18:25.178650+0800 Thread[1210:47038] 任務二,i = 2
2018-12-27 11:18:25.178650+0800 Thread[1210:37362] 任務一,i = 2
3.2.5.同步執行 + 主隊列
同步執行 + 主隊列 在不同線程中調用,結果不一樣。
在主線程中調用,出現死鎖
在其他線程中調用,不會開啟線程,一個任務執行完畢后,再執行下一個任務
3.2.5.1在主線程調用
/*
同步執行 + 主隊列
在主線程中調用,出現死鎖
*/
- (void)syncMain{
NSLog(@" syncMain start");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
NSLog(@" syncMain end");
}
輸出結果:
在Xcode10 上運行崩潰,只輸出了syncMain start
為什么?
因為我們在主線程中執行 syncMain
方法,相當于把 syncMain
任務放到主線程的隊列中,而同步執行會等待當前隊列中的任務執行完畢后,才會接著執行。我們把任務一追加到主隊列中,任務一會等待主線程處理完syncMain
方法,而syncMain
方法又需要等待任務一執行完畢,才能繼續執行,雙方都在等待,所以線程死鎖,任務無法執行。
2018-12-27 11:26:17.011738+0800 Thread[1443:50531] syncMain start
3.2.5.2 在其他線程調用
/*
同步執行 + 主隊列
在其他線程中調用,不會開啟線程,一個任務執行完畢后,再執行下一個任務
*/
- (void)syncMain{
NSLog(@"主線程");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@" syncMain start");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_sync(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
NSLog(@" syncMain end");
});
}
輸出結果:
- 沒有開啟線程,放在主隊列的任務都在主線程中執行
- 所有任務都在
syncMain start
和syncMain end
之間執行(同步執行需要等待隊列中的任務執行結束) - 任務按順序執行(串行隊列每次只有一個任務被執行,任務一個接著一個執行)
2018-12-27 14:22:03.964047+0800 Thread[2134:84333] 主線程
2018-12-27 14:22:03.964238+0800 Thread[2134:84385] syncMain start
2018-12-27 14:22:03.965009+0800 Thread[2134:84333] 任務一,i = 0
2018-12-27 14:22:04.966453+0800 Thread[2134:84333] 任務一,i = 1
2018-12-27 14:22:05.967903+0800 Thread[2134:84333] 任務一,i = 2
2018-12-27 14:22:06.968817+0800 Thread[2134:84333] 任務二,i = 0
2018-12-27 14:22:07.969877+0800 Thread[2134:84333] 任務二,i = 1
2018-12-27 14:22:08.970376+0800 Thread[2134:84333] 任務二,i = 2
2018-12-27 14:22:09.971810+0800 Thread[2134:84385] syncMain end
3.2.6.異步執行 + 主隊列
/*
異步執行 + 主隊列
不會開啟新線程,在主線程中執行,一個任務執行完畢后,再執行下一個任務
*/
- (void)asyncMain{
NSLog(@" asyncMain start");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
NSLog(@" asyncMain end");
}
輸出結果:
- 沒有開啟線程,放在主隊列的任務都在主線程中執行
- 所有任務都在
asyncMain start
和asyncMain end
之后執行(異步執行不會等待,繼續執行任務) - 任務按順序執行(因為主隊列是串行隊列,每次只有一個任務被執行,任務一個接著一個執行)
2018-12-27 14:33:15.293261+0800 Thread[2134:84333] asyncMain start
2018-12-27 14:33:15.293406+0800 Thread[2134:84333] asyncMain end
2018-12-27 14:33:15.293646+0800 Thread[2134:84333] 任務一,i = 0
2018-12-27 14:33:16.293875+0800 Thread[2134:84333] 任務一,i = 1
2018-12-27 14:33:17.295251+0800 Thread[2134:84333] 任務一,i = 2
2018-12-27 14:33:18.296751+0800 Thread[2134:84333] 任務二,i = 0
2018-12-27 14:33:19.297602+0800 Thread[2134:84333] 任務二,i = 1
2018-12-27 14:33:20.298579+0800 Thread[2134:84333] 任務二,i = 2
4.線程間通信
在iOS開發工程中,我們一般在主線程中進行UI刷新,如:點擊、拖拽、滾動事件,耗時操作放在其他線程中,而當耗時操作結束后,回到主線程,就需要用到線程間的通信。
在全局隊列中執行任務,任務完成后,切回主線程
- (void)gcdCommunication{
NSLog(@"我在主線程");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程");
});
});
}
輸出結果:
2018-12-27 15:05:55.188427+0800 Thread[2519:102865] 我在主線程
2018-12-27 15:05:55.188635+0800 Thread[2519:102899] 任務一,i = 0
2018-12-27 15:05:56.189689+0800 Thread[2519:102899] 任務一,i = 1
2018-12-27 15:05:57.191253+0800 Thread[2519:102899] 任務一,i = 2
2018-12-27 15:05:58.195350+0800 Thread[2519:102865] 回到主線程
5.dispatch_once
創建單例或者整個程序只運行一次的代碼,可以使用dispatch_once
,dispatch_once
函數保證這個程序運行過程中只被執行一次,即時在多線程情況下也是安全的。
/*
驗證dispatch_once
*/
- (void)testGcdOnce{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (NSInteger i = 0; i < 3; i ++) {
[self gcdOnce];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (NSInteger i = 0; i < 3; i ++) {
[self gcdOnce];
}
});
}
- (void)gcdOnce{
NSLog(@"%s",__func__);
static TicketManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[TicketManager alloc]init];
NSLog(@"創建對象");
});
}
輸出結果:dispatch_once
里面只被執行了一次
2018-12-27 15:19:31.228794+0800 Thread[2702:110251] -[ViewController gcdOnce]
2018-12-27 15:19:31.228794+0800 Thread[2702:110250] -[ViewController gcdOnce]
2018-12-27 15:19:31.229290+0800 Thread[2702:110251] 創建對象
2018-12-27 15:19:31.229516+0800 Thread[2702:110250] -[ViewController gcdOnce]
2018-12-27 15:19:31.229514+0800 Thread[2702:110251] -[ViewController gcdOnce]
2018-12-27 15:19:31.229630+0800 Thread[2702:110250] -[ViewController gcdOnce]
2018-12-27 15:19:31.229678+0800 Thread[2702:110251] -[ViewController gcdOnce]
6.dispatch_after
dispatch_after
并不是在指定時間之后才執行處理,而是在指定時間之后將任務追加到隊列中,這個指定時間并不是絕對準確的,想要大致完成延時任務可以使用dispatch_after
函數實現
- (void)gcdAfter{
NSLog(@"%s",__func__);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%s",__func__);
});;
}
輸出結果:大致為1秒
2018-12-27 15:28:23.416958+0800 Thread[2781:114534] -[ViewController gcdAfter]
2018-12-27 15:28:24.513064+0800 Thread[2781:114534] -[ViewController gcdAfter]_block_invoke
7. dispatch_apply(快速迭代)
dispatch_apply
快速迭代方法,按照指定次數將指定的任務添加到隊列中,并等待隊列中的任務全部執行結束。
如果在串行隊列中使用dispatch_apply
函數,就和for
循環遍歷一樣,按照順序執行,體現不出快速迭代的意義。
如果在并行隊列中使用dispatch_apply
函數,dispatch_apply
可以在多個線程中同時遍歷多個數字。
7.1 在串行隊列使用dispatch_apply
將在主隊列dispatch_get_main_queue
的遍歷任務放在并行隊列dispatch_get_global_queue
中,為了避免上面講的死鎖問題,關注apply begin
和apply end
之間的代碼即可
/*
dispatch_apply:快速迭代
*/
- (void)gcdApply{
NSLog(@"主線程");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"apply begin");
dispatch_apply(6, dispatch_get_main_queue(), ^(size_t index) {
NSLog(@"index = %zu",index);
});
NSLog(@"apply end");
});
}
輸出結果:
- 沒有開啟線程(主隊列的任務只在主線程中執行)
-
apply end
在最后輸出(dispatch_apply
函數會等待隊列全部任務執行結束)
2018-12-27 15:58:28.788666+0800 Thread[3090:128936] 主線程
2018-12-27 15:58:28.788880+0800 Thread[3090:128988] apply begin
2018-12-27 15:58:28.819630+0800 Thread[3090:128936] index = 0
2018-12-27 15:58:28.819791+0800 Thread[3090:128936] index = 1
2018-12-27 15:58:28.819897+0800 Thread[3090:128936] index = 2
2018-12-27 15:58:28.820107+0800 Thread[3090:128936] index = 3
2018-12-27 15:58:28.820396+0800 Thread[3090:128936] index = 4
2018-12-27 15:58:28.820503+0800 Thread[3090:128936] index = 5
2018-12-27 15:58:28.820752+0800 Thread[3090:128988] apply end
7.2在并行隊列使用dispatch_apply
/*
dispatch_apply:快速迭代
*/
- (void)gcdApply{
NSLog(@"apply begin");
dispatch_apply(6, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"index = %zu",index);
});
NSLog(@"apply end");
}
輸出結果:
- 開啟線程(看線程編號得出)
-
apply end
在最后輸出(dispatch_apply
函數會等待隊列全部任務執行結束)
2018-12-27 15:39:06.219402+0800 Thread[2918:120358] apply begin
2018-12-27 15:39:06.219636+0800 Thread[2918:120391] index = 1
2018-12-27 15:39:06.219619+0800 Thread[2918:120358] index = 0
2018-12-27 15:39:06.219746+0800 Thread[2918:120391] index = 3
2018-12-27 15:39:06.219747+0800 Thread[2918:120392] index = 4
2018-12-27 15:39:06.219737+0800 Thread[2918:120358] index = 2
2018-12-27 15:39:06.219832+0800 Thread[2918:120391] index = 5
2018-12-27 15:39:06.219933+0800 Thread[2918:120358] apply end
需求1:我們需要異步執行兩個操作(一個操作可以是一個任務,也可以是多個任務,這里是兩個任務),而且第一組操作結束后,才能開始第二組操作,如何實現呢?
這里的意思其實保持線程同步,將異步任務轉化為同步任務的意思。
方法1:使用柵欄函數
方法2:使用dispatch_group
方式3 :使用dispatch_semaphore
這三個方法都會在下面一一講解的。
總結:就當前這個需求,使用柵欄函數會比較簡單,所有方法講完具體實現就可以看出來了不用創建group
或者semaphore
,直接放個柵欄在中間,分隔兩個操作。
8.dispatch_barrier(柵欄函數)
柵欄函數存在的意義:先執行柵欄函數之前的任務,再執行柵欄函數中的任務,最后執行柵欄函數之后的任務,增加柵欄函數不影響原有隊列的任務執行方式,也就是柵欄函數之前隊列的任務是什么執行方式,柵欄函數之后隊列的任務還是什么執行方式。
柵欄函數分為:dispatch_barrier_async
和dispatch_barrier_sync
區別就是 :添加柵欄函數后面任務到隊列的時間不一樣。
dispatch_barrier_sync
:需要
等待柵欄函數中的任務執行結束后,才會添加柵欄函數后的任務到隊列中。
dispatch_barrier_async
: 不需要
等待柵欄函數中的任務執行結束,就已經將柵欄函數后的任務到隊列中。
8.1 dispatch_barrier_sync
實現代碼
- (void)gcdBarrier{
NSLog(@"主線程");
dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.gcd", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
//1.dispatch_barrier_sync
dispatch_barrier_sync(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" barrier1,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" barrier2,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
NSLog(@"---------barrier代碼后面----------------");
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務三,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務四,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
}
輸出結果:
-
先
執行任務一、二,再
執行柵欄函數中的barrier1
、barrier2
,最后
執行任務三、四; - 任務一、二交替執行(異步執行),任務三、四交替執行(異步執行),印證柵欄函數不影響隊列任務的執行方式;
- 橫線在柵欄函數之后輸出(
dispatch_barrier_sync
:需要等待柵欄函數中的任務執行結束后,才會添加柵欄函數后的任務到隊列中)。
2018-12-28 17:07:54.227141+0800 Thread[11766:169507] 主線程
2018-12-28 17:07:54.227455+0800 Thread[11766:169563] 任務二,i = 0
2018-12-28 17:07:54.227482+0800 Thread[11766:169565] 任務一,i = 0
2018-12-28 17:07:55.231426+0800 Thread[11766:169563] 任務二,i = 1
2018-12-28 17:07:55.231426+0800 Thread[11766:169565] 任務一,i = 1
2018-12-28 17:07:56.232952+0800 Thread[11766:169563] 任務二,i = 2
2018-12-28 17:07:56.232985+0800 Thread[11766:169565] 任務一,i = 2
2018-12-28 17:07:57.236980+0800 Thread[11766:169507] barrier1,i = 0
2018-12-28 17:07:58.238423+0800 Thread[11766:169507] barrier1,i = 1
2018-12-28 17:07:59.239776+0800 Thread[11766:169507] barrier1,i = 2
2018-12-28 17:08:00.240315+0800 Thread[11766:169507] barrier2,i = 0
2018-12-28 17:08:01.240863+0800 Thread[11766:169507] barrier2,i = 1
2018-12-28 17:08:02.242310+0800 Thread[11766:169507] barrier2,i = 2
2018-12-28 17:08:03.242826+0800 Thread[11766:169507] ---------barrier代碼后面----------------
2018-12-28 17:08:03.243200+0800 Thread[11766:169564] 任務三,i = 0
2018-12-28 17:08:03.243315+0800 Thread[11766:169663] 任務四,i = 0
2018-12-28 17:08:04.247765+0800 Thread[11766:169663] 任務四,i = 1
2018-12-28 17:08:04.247765+0800 Thread[11766:169564] 任務三,i = 1
2018-12-28 17:08:05.252405+0800 Thread[11766:169663] 任務四,i = 2
2018-12-28 17:08:05.252405+0800 Thread[11766:169564] 任務三,i = 2
8.2dispatch_barrier_async
實現代碼
- (void)gcdBarrier{
NSLog(@"主線程");
dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.gcd", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
//2.dispatch_barrier_async
dispatch_barrier_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" barrier1,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" barrier2,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
NSLog(@"---------barrier代碼后面----------------");
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務三,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務四,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
}
輸出結果:
-
先
執行任務一、二,再
執行柵欄函數中的barrier1
、barrier2
,最后
執行任務三、四; - 任務一、二交替執行(異步執行),任務三、四交替執行(異步執行),印證柵欄函數不影響隊列任務的執行方式;
- 橫線在柵欄函數之前輸出(
dispatch_barrier_async
:不需要
等待柵欄函數中的任務執行結束,就已經將柵欄函數后的任務到隊列中)。
2018-12-28 17:10:58.908322+0800 Thread[11798:171154] 主線程
2018-12-28 17:10:58.908586+0800 Thread[11798:171209] 任務一,i = 0
2018-12-28 17:10:58.908586+0800 Thread[11798:171154] ---------barrier代碼后面----------------
2018-12-28 17:10:58.908616+0800 Thread[11798:171207] 任務二,i = 0
2018-12-28 17:10:59.912811+0800 Thread[11798:171207] 任務二,i = 1
2018-12-28 17:10:59.912811+0800 Thread[11798:171209] 任務一,i = 1
2018-12-28 17:11:00.917006+0800 Thread[11798:171207] 任務二,i = 2
2018-12-28 17:11:00.917047+0800 Thread[11798:171209] 任務一,i = 2
2018-12-28 17:11:01.919238+0800 Thread[11798:171209] barrier1,i = 0
2018-12-28 17:11:02.921574+0800 Thread[11798:171209] barrier1,i = 1
2018-12-28 17:11:03.924472+0800 Thread[11798:171209] barrier1,i = 2
2018-12-28 17:11:04.929280+0800 Thread[11798:171209] barrier2,i = 0
2018-12-28 17:11:05.930060+0800 Thread[11798:171209] barrier2,i = 1
2018-12-28 17:11:06.931831+0800 Thread[11798:171209] barrier2,i = 2
2018-12-28 17:11:07.934022+0800 Thread[11798:171209] 任務三,i = 0
2018-12-28 17:11:07.934022+0800 Thread[11798:171207] 任務四,i = 0
2018-12-28 17:11:08.939572+0800 Thread[11798:171207] 任務四,i = 1
2018-12-28 17:11:08.939573+0800 Thread[11798:171209] 任務三,i = 1
2018-12-28 17:11:09.942792+0800 Thread[11798:171209] 任務三,i = 2
2018-12-28 17:11:09.942829+0800 Thread[11798:171207] 任務四,i = 2
9.dispatch_group(隊列組)
任務的執行是先創建一個任務,放入隊列進行執行。而隊列組就是用來存放隊列的一個組。
將隊列放入隊列組中可以使用 dispatch_group_async
或者dispatch_group_enter
和dispatch_group_leave
的組合實現。
隊列組的任務結束完成后,調用dispatch_group_notify
可以回到指定
線程執行任務,調用dispatch_group_wait
可以回到當前
線程執行任務(阻塞
當前線程)。
就上面的需求1,用dispatch_group
方式實現
1. 我們用dispatch_group_async
+ dispatch_group_notify
來進行實現
- (void)requirementGroupAsync{
NSLog(@"主線程");
dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_group_notify(group, queue, ^{
NSLog(@"------dispatch_group_notify------");
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務三,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務四,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
});
}
輸出結果:先執行任務一、二,再執行任務三、四;
2018-12-28 17:59:23.695495+0800 Thread[12227:189820] 主線程
2018-12-28 17:59:23.695775+0800 Thread[12227:189867] 任務一,i = 0
2018-12-28 17:59:23.695778+0800 Thread[12227:189868] 任務二,i = 0
2018-12-28 17:59:24.697513+0800 Thread[12227:189867] 任務一,i = 1
2018-12-28 17:59:24.697514+0800 Thread[12227:189868] 任務二,i = 1
2018-12-28 17:59:25.701243+0800 Thread[12227:189868] 任務二,i = 2
2018-12-28 17:59:25.701243+0800 Thread[12227:189867] 任務一,i = 2
2018-12-28 17:59:26.705407+0800 Thread[12227:189868] ------dispatch_group_notify------
2018-12-28 17:59:26.705799+0800 Thread[12227:189868] 任務三,i = 0
2018-12-28 17:59:26.705803+0800 Thread[12227:189866] 任務四,i = 0
2018-12-28 17:59:27.709876+0800 Thread[12227:189868] 任務三,i = 1
2018-12-28 17:59:27.709873+0800 Thread[12227:189866] 任務四,i = 1
2018-12-28 17:59:28.713711+0800 Thread[12227:189866] 任務四,i = 2
2018-12-28 17:59:28.713711+0800 Thread[12227:189868] 任務三,i = 2
2. 我們用dispatch_group_async
+ dispatch_group_wait
來進行實現
- (void)requirementGroupAsync{
NSLog(@"主線程");
dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
//2.dispatch_group_wait
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"-----dispatch_group_wait----");
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務三,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務四,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
}
輸出結果:
- 先執行任務一、二,再執行任務三、四;
-
dispatch_group_wait
在主線程輸出,就如之前所說dispatch_group_wait
會阻塞當前線程。
2018-12-28 18:07:54.379565+0800 Thread[12300:193286] 主線程
2018-12-28 18:07:54.379782+0800 Thread[12300:193334] 任務二,i = 0
2018-12-28 18:07:54.379783+0800 Thread[12300:193332] 任務一,i = 0
2018-12-28 18:07:55.384688+0800 Thread[12300:193334] 任務二,i = 1
2018-12-28 18:07:55.384720+0800 Thread[12300:193332] 任務一,i = 1
2018-12-28 18:07:56.385807+0800 Thread[12300:193332] 任務一,i = 2
2018-12-28 18:07:56.385808+0800 Thread[12300:193334] 任務二,i = 2
2018-12-28 18:07:57.386847+0800 Thread[12300:193286] -----dispatch_group_wait----
2018-12-28 18:07:57.387161+0800 Thread[12300:193334] 任務三,i = 0
2018-12-28 18:07:57.387174+0800 Thread[12300:193332] 任務四,i = 0
2018-12-28 18:07:58.387806+0800 Thread[12300:193332] 任務四,i = 1
2018-12-28 18:07:58.387808+0800 Thread[12300:193334] 任務三,i = 1
2018-12-28 18:07:59.390173+0800 Thread[12300:193332] 任務四,i = 2
2018-12-28 18:07:59.390209+0800 Thread[12300:193334] 任務三,i = 2
3. 我們用dispatch_group_enter
+ dispatch_group_leave
+ dispatch_group_notify
來進行實現
- (void)requirementGroupEnter{
NSLog(@"主線程");
dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
dispatch_group_leave(group);
});
//1.dispatch_group_notify
dispatch_group_notify(group, queue, ^{
NSLog(@"------dispatch_group_notify------");
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務三,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務四,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
});
}
輸出結果:先執行任務一、二,再執行任務三、四;
2018-12-28 18:19:52.294127+0800 Thread[12422:198467] 主線程
2018-12-28 18:19:52.294429+0800 Thread[12422:198501] 任務一,i = 0
2018-12-28 18:19:52.294438+0800 Thread[12422:198504] 任務二,i = 0
2018-12-28 18:19:53.297632+0800 Thread[12422:198504] 任務二,i = 1
2018-12-28 18:19:53.297642+0800 Thread[12422:198501] 任務一,i = 1
2018-12-28 18:19:54.302891+0800 Thread[12422:198501] 任務一,i = 2
2018-12-28 18:19:54.302891+0800 Thread[12422:198504] 任務二,i = 2
2018-12-28 18:19:55.307933+0800 Thread[12422:198501] ------dispatch_group_notify------
2018-12-28 18:19:55.308242+0800 Thread[12422:198501] 任務三,i = 0
2018-12-28 18:19:55.308258+0800 Thread[12422:198502] 任務四,i = 0
2018-12-28 18:19:56.312331+0800 Thread[12422:198501] 任務三,i = 1
2018-12-28 18:19:56.312331+0800 Thread[12422:198502] 任務四,i = 1
2018-12-28 18:19:57.313584+0800 Thread[12422:198502] 任務四,i = 2
2018-12-28 18:19:57.313584+0800 Thread[12422:198501] 任務三,i = 2
4. 我們用dispatch_group_enter
+ dispatch_group_leave
+ dispatch_group_wait
來進行實現
- (void)requirementGroupEnter{
NSLog(@"主線程");
dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務二,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
dispatch_group_leave(group);
});
// 2.dispatch_group_wait
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"-----dispatch_group_wait----");
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務三,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務四,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
}
- 先執行任務一、二,再執行任務三、四;
-
dispatch_group_wait
在主線程輸出,就如之前所說dispatch_group_wait
會阻塞當前線程
2018-12-28 18:21:29.197243+0800 Thread[12446:199547] 主線程
2018-12-28 18:21:29.197534+0800 Thread[12446:199590] 任務一,i = 0
2018-12-28 18:21:29.197598+0800 Thread[12446:199591] 任務二,i = 0
2018-12-28 18:21:30.200867+0800 Thread[12446:199591] 任務二,i = 1
2018-12-28 18:21:30.200870+0800 Thread[12446:199590] 任務一,i = 1
2018-12-28 18:21:31.204217+0800 Thread[12446:199590] 任務一,i = 2
2018-12-28 18:21:31.204215+0800 Thread[12446:199591] 任務二,i = 2
2018-12-28 18:21:32.205551+0800 Thread[12446:199547] -----dispatch_group_wait----
2018-12-28 18:21:32.205926+0800 Thread[12446:199590] 任務三,i = 0
2018-12-28 18:21:32.205930+0800 Thread[12446:199591] 任務四,i = 0
2018-12-28 18:21:33.210243+0800 Thread[12446:199590] 任務三,i = 1
2018-12-28 18:21:33.210413+0800 Thread[12446:199591] 任務四,i = 1
2018-12-28 18:21:34.215728+0800 Thread[12446:199590] 任務三,i = 2
2018-12-28 18:21:34.215728+0800 Thread[12446:199591] 任務四,i = 2
那dispatch_group_async
和dispatch_group_enter
+ dispatch_group_leave
有什么區別呢?
需求2:異步執行兩個網絡請求,兩個網絡請求執行結束后,進行一定的操作。
這里sendRequest
用來代表網絡請求
- (void)sendRequest:(void (^)(void))block{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 1");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
- (void)sendRequest2:(void (^)(void))block{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 2");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
1. 用dispatch_group_async
實現
- (void)gcdGroupAsync{
NSLog(@"主線程");
// dispatch_group_async 里面,應該放同步代碼,而不是異步代碼
dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[self sendRequest:^{
NSLog(@"sendRequest done");
}];
});
dispatch_group_async(group, queue, ^{
[self sendRequest2:^{
NSLog(@"sendRequest2 done");
}];
});
dispatch_group_notify(group, queue, ^{
NSLog(@"all task over");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新UI");
});
});
}
輸出結果:顯示不符合需求,task1和task2還沒有結束,就輸出all task over 咯,為啥子呢?
原因:因為dispatch_group_async
里面放入的是異步的任務,dispatch_group_async
執行了sendRequest
這行代碼后,就認為sendRequest
已經執行完畢了(其實還沒有回調回來),group
不再持有這個任務,就會執行下面的dispatch_group_async
,而sendRequest2
同理,group
沒有任務時,就會執行dispatch_group_notify
里面的任務,所以造成這樣子的輸出結果。
由此可見:dispatch_group_async
里面適合放入同步代碼,而不是異步代碼。
2018-12-29 17:13:42.710421+0800 Thread[17395:417245] 主線程
2018-12-29 17:13:42.710700+0800 Thread[17395:417334] start task 2
2018-12-29 17:13:42.710726+0800 Thread[17395:417333] all task over
2018-12-29 17:13:42.710708+0800 Thread[17395:417331] start task 1
2018-12-29 17:13:42.733662+0800 Thread[17395:417245] 回到主線程刷新UI
2018-12-29 17:13:45.714431+0800 Thread[17395:417331] end task 1
2018-12-29 17:13:45.714511+0800 Thread[17395:417334] end task 2
2018-12-29 17:13:45.714752+0800 Thread[17395:417245] sendRequest done
2018-12-29 17:13:45.714854+0800 Thread[17395:417245] sendRequest2 done
2.用dispatch_group_enter
+ dispatch_group_leave
實現
- (void)gcdGroupEnter{
NSLog(@"主線程");
dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self sendRequest:^{
NSLog(@"sendRequest done");
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self sendRequest2:^{
NSLog(@"sendRequest2 done");
dispatch_group_leave(group);
}];
dispatch_group_notify(group, queue, ^{
NSLog(@"all task over");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新UI");
});
});
}
輸出結果:當task1 和task2執行結束后,才輸出all task over,符合我們的需求。
當我們調用sendRequest
時,先調用dispatch_group_enter
時,任務回調后再調用dispatch_group_leave
,整個異步操作中,任務是被group
持有的,只有回調結束后才離開group
,所以不會出現上面的問題。
注意:dispatch_group_enter
和dispatch_group_leave
成對存在
2018-12-29 17:32:12.739361+0800 Thread[17557:425760] 主線程
2018-12-29 17:32:12.739608+0800 Thread[17557:425809] start task 1
2018-12-29 17:32:12.739608+0800 Thread[17557:425810] start task 2
2018-12-29 17:32:15.742987+0800 Thread[17557:425809] end task 1
2018-12-29 17:32:15.742990+0800 Thread[17557:425810] end task 2
2018-12-29 17:32:15.743325+0800 Thread[17557:425760] sendRequest2 done
2018-12-29 17:32:15.743528+0800 Thread[17557:425760] sendRequest done
2018-12-29 17:32:15.743742+0800 Thread[17557:425810] all task over
2018-12-29 17:32:15.744000+0800 Thread[17557:425760] 回到主線程刷新UI
10.dispatch_semaphore(信號量)
dispatch_semaphore
使用計數來完成這個問題,計數為0 ,不可以通過,計數大于等于1,可以通過
其中有三個函數分別為:
-
dispatch_semaphore_create
:創建semaphore
并初始化信號量,初始化的值大于等于0; -
dispatch_semaphore_signal
:信號量+1; -
dispatch_semaphore_wait
: 判斷當前信號量的值,如果當前信號量大于0,信號量-1,往下執行,如果當前信號量等于0,就會阻塞在當前線程,一直等待。
用處:
1、保持線程同步,將異步任務轉化為同步任務
2、保證線程安全,為線程加鎖
線程安全
:在多個線程中同時訪問并操作同一對象時,運行結果與預期的值相同就是線程安全。
線程安全問題都是由全局變量及靜態變量引起的,若每個線程中對全局變量
、靜態變量
只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
線程同步
:可理解為線程A和B一塊配合,A執行到一定程度時要依靠B的某個結果,于是停下來,示意B運行;B依言執行,再將結果給A;A再繼續操作。
10.1.保持線程同步,將異步任務轉化為同步任務
這個也可以用來實現上面的需求1
- (void)gcdSemaphore{
NSLog(@"主線程");
dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.gcd", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務一,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
long x = dispatch_semaphore_signal(semaphore);
NSLog(@"signal后的信號量 = %ld",x);
});
long x = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"wait后的信號量 = %ld",x);
NSLog(@"---dispatch_semaphore_wait-----");
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務三,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
NSLog(@" 任務四,i = %ld",(long)i);
[NSThread sleepForTimeInterval:1.0];
}
});
}
輸出結果:
- 先輸出任務一,再輸出任務三、四
-
dispatch_semaphore_wait
在主線程輸出(信號量為0 ,阻塞在當前線程)
我們在主線程創建了一個信號量賦值為0,并開辟了一個并行隊列異步執行任務一,因為是一個異步操作,此時不會等待任務一執行結束, 直接執行到dispatch_semaphore_wait
,此時判斷出信號量的值0,不可通行,阻塞當前線程,當運行到dispatch_semaphore_signal
時,信號量加1后等于1大于0,可通行,執行dispatch_semaphore_wait
后面的任務三、四,通行后信號量減1等于0。
2018-12-29 15:22:24.960944+0800 Thread[16315:368237] 主線程
2018-12-29 15:22:24.961180+0800 Thread[16315:368286] 任務一,i = 0
2018-12-29 15:22:25.965568+0800 Thread[16315:368286] 任務一,i = 1
2018-12-29 15:22:26.970160+0800 Thread[16315:368286] 任務一,i = 2
2018-12-29 15:22:27.975627+0800 Thread[16315:368237] wait后的信號量 = 0
2018-12-29 15:22:27.975627+0800 Thread[16315:368286] signal后的信號量 = 1
2018-12-29 15:22:27.975956+0800 Thread[16315:368237] ---dispatch_semaphore_wait-----
2018-12-29 15:22:27.976232+0800 Thread[16315:368287] 任務四,i = 0
2018-12-29 15:22:27.976285+0800 Thread[16315:368286] 任務三,i = 0
2018-12-29 15:22:28.978431+0800 Thread[16315:368286] 任務三,i = 1
2018-12-29 15:22:28.978445+0800 Thread[16315:368287] 任務四,i = 1
2018-12-29 15:22:29.980405+0800 Thread[16315:368286] 任務三,i = 2
2018-12-29 15:22:29.980405+0800 Thread[16315:368287] 任務四,i = 2
10.2.保證線程安全,為線程加鎖
在上面講NSThread的時候,講過synchronized
和NSCondition
加鎖方式,這里使用dispatch_semaphore
進行加密,運行結果和上面一致。
- (void)sale{
while (1) {
//1、synchronized
// @synchronized (self) {
// if (self.tickets > 0 ) {
// [NSThread sleepForTimeInterval:0.1];
// self.tickets --;
// self.saleCount = Total - self.tickets;
// NSLog(@"%@ , 賣出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
// }else{
// break;//一定要break,不然就會死循環
// }
// }
// 2、NSCondition
// [self.condition lock];
// if (self.tickets > 0 ) {
// [NSThread sleepForTimeInterval:0.1];
// self.tickets --;
// self.saleCount = Total - self.tickets;
// NSLog(@"%@ , 賣出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
// }else{
// break;
// }
// [self.condition unlock];
//
//3、dispatch_semaphore方式
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
if (self.tickets > 0 ) {
[NSThread sleepForTimeInterval:0.1];
self.tickets --;
self.saleCount = Total - self.tickets;
NSLog(@"%@ , 賣出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
}else{
dispatch_semaphore_signal(self.semaphore);
break;
}
dispatch_semaphore_signal(self.semaphore);
}
}
信號量還需要多看點資料,這里就先這樣子吧~~
上面就保持線程同步,將異步任務轉化為同步任務,保證線程安全,給線程加鎖就講了好多種方式,選擇的時候,針對需求而言來選擇一個較好的方式就OK啦~
參考博客:
文章鏈接:
iOS 多線程- pThread和NSThread
iOS 多線程-NSOperation + NSOperationQueue
喜歡就點個贊吧????
有錯之處,還請指出,感謝????