iOS多線程demohtml
iOS多線程之--NSOperationgithub
iOS多線程相關面試題swift
GCD
(Grand Central Dispatch)是蘋果公司爲多核的並行運算提出的解決方案,它是一套純C語言實現的API。使用GCD分派隊列(dispath queue)
能夠同步或者異步地執行任務,以及串行或者併發的執行任務。GCD會自動管理線程的生命週期(建立線程、調度任務、銷燬線程),因此開發人員只須要告訴GCD想要執行什麼任務,而不須要編寫任何管理線程的代碼。[GCD源碼]安全
同步和異步主要體如今能不能開啓新的線程,以及會不會阻塞當前線程。bash
同步
:在當前線程中執行任務,不具有開啓新線程的能力。同步任務會阻塞當前線程。異步
:在新的線程中執行任務,具有開啓新線程的能力。並不必定會開啓新線程,好比在主隊列中經過異步執行任務並不會開啓新線程。異步任務不會阻塞當前線程。串行和併發主要影響任務的執行方式。服務器
串行
:一個任務執行完畢後,再執行下一個任務。併發
:多個任務併發(同時)執行。注意:
若是當前隊列是串行隊列,經過同步函數向當前隊列中添加任務會形成死鎖。(串行隊列中添加異步任務和併發隊列中添加同步任務都不會死鎖。)網絡
任務就是要執行什麼操做,GCD中任務是放在block中(dispatch_block_t
)或者函數中(dispatch_function_t
)的(比較常見的是放在block中)。任務的執行方式有同步
和異步
2種。GCD中的同步函數和異步函數以下:
//同步執行
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
//異步執行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
複製代碼
這裏咱們先經過dispatch_async
和dispatch_async_f
這兩個異步函數來了解block任務和函數任務的使用方法,其它函數後面會單獨拿出來介紹。這裏要說明的是,block任務和函數任務功能是同樣的,因此後面內容都以block任務來作講解
。
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- (void)blockTask{
NSLog(@"%@",[NSThread currentThread]);
dispatch_async(dispatch_get_global_queue(0, 0), ^{ // blcok任務
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時任務
NSLog(@"block任務--%@",[NSThread currentThread]);
});
}
// ****************運行結果****************
2019-12-28 18:58:08.194661+0800 MultithreadingDemo[21777:4006228] <NSThread: 0x60000124a200>{number = 1, name = main}
2019-12-28 18:58:09.205977+0800 MultithreadingDemo[21777:4006340] block任務--<NSThread: 0x600001214bc0>{number = 3, name = (null)}
複製代碼
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
dispatch_function_t
是一個函數指針(其定義爲:typedef void (*dispatch_function_t)(void *_Nullable);
),指向耗時任務所在的的函數。- (void)functionTask{
NSLog(@"%@",[NSThread currentThread]);
dispatch_async_f(dispatch_get_global_queue(0, 0), @"abc124", testFunction);
}
// 任務所在的函數
void testFunction(void *para){
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時任務
NSLog(@"函數任務參數:%@----線程:%@",para,[NSThread currentThread]);
}
// ****************運行結果****************
2019-12-28 18:51:01.711140+0800 MultithreadingDemo[21777:4006228] <NSThread: 0x60000124a200>{number = 1, name = main}
2019-12-28 18:51:02.716100+0800 MultithreadingDemo[21777:4006340] 函數任務參數:abc124----線程:<NSThread: 0x600001214bc0>{number = 3, name = (null)}
複製代碼
GCD的派發隊列(dispatch_queue)
用來存聽任務(dispatch_block
)並控制任務的執行方式。(dispatch_queue)
採用的是FIFO
(先進先出)的原則,也就是說先提交的任務會先安排執行(先安排並不意味着先執行完)。
GCD的隊列能夠分爲2大類型:串行隊列
和併發隊列
。
串行隊列(Serial Dispatch Queue)
:讓任務一個接着一個地執行(一個任務執行完畢後,再執行下一個任務)。併發隊列(Concurrent Dispatch Queue)
:可讓多個任務併發(同時)執行(自動開啓多個線程同時執行任務)。要注意的是併發功能只有在異步(dispatch_async)函數下才有效。併發數是有上限的
,併發執行的任務數量取決於當前系統的狀態。主隊列由系統自動建立,並與應用程序的主線程相關聯。主隊列上的任務必定是在主線程上執行(不過主線程並非只執行主隊列的任務,爲了不線程切換對性能的消耗,主線程還有可能會執行其餘隊列的任務)。
主隊列是一個串行隊列,因此即使是在異步函數中也不會去開啓新的線程。它只有在主線程空閒的時候才能調度裏面的任務。
開發中通常是子線程執行完耗時操做後,咱們獲取到主隊列並將刷新UI的任務添加到主隊列中去執行。
// 主隊列同步函數
- (void)mainQueueTest1{
NSLog(@"主隊列同步函數");
// 獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
for (NSInteger i = 0; i < 5; i++) {
dispatch_sync(mainQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時操做
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
}
// ****************運行結果****************
------線程堵塞------
複製代碼
上面程序運行會堵塞線程,由於主隊列是串行隊列,若是經過同步函數向主隊列中添加任務,那麼並不會開啓子線程來執行這個任務,而是在主線程中執行。咱們將方法mainQueueTest1
稱做task1,將同步函數添加的任務稱做task2。task1和task2都在主隊列中,主隊列先安排task1到主線程執行,等task1執行了完了才能安排task2到主線程執行,而task1又必須等task2執行完了才能繼續往下執行,而task2又必須等task1執行完了才能被主隊列安排執行,這樣就形成了相互等待而卡死(死鎖)。
因此只要當前隊列是串行隊列,經過同步函數往當前隊列添加任務都會形成死鎖。
// 主隊列異步函數
- (void)mainQueueTest2{
NSLog(@"主隊列異步函數");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(mainQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時操做
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************運行結果****************
2019-12-28 15:42:04.622806+0800 MultithreadingDemo[20363:3934000] 主隊列異步函數
2019-12-28 15:42:04.623171+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 15:42:05.625086+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:06.625727+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:07.627400+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:08.628809+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:09.629977+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
複製代碼
在主隊列中經過異步函數添加的任務都是在主線程執行(由於主隊列中的任務都在主線程執行,因此並不會開啓新的線程),而且是串行執行。
獲取全局隊列的函數是dispatch_get_global_queue(long identifier, unsigned long flags)
。第一個參數表示隊列的優先級(優先級等級以下表所示);第二個參數是保留參數,傳0便可。全局隊列是一個併發隊列
。
優先級 | 描述 |
---|---|
DISPATCH_QUEUE_PRIORITY_HIGH | 最高優先級 |
DISPATCH_QUEUE_PRIORITY_DEFAULT | 默認優先級 |
DISPATCH_QUEUE_PRIORITY_LOW | 低優先級 |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | 後臺優先級(表示用戶不須要知道任務何時完成,選這項速度會很是慢) |
注意
這裏的優先級和NSOperation
中的優先級不一樣,這裏的優先級是指隊列的優先級,NSOperation
中的優先級是指任務的優先級。
對於全局隊列,若是兩個參數同樣,那麼獲取的是同一個隊列,以下所示queue1和queue2是同一個隊列
。
// 打印兩個隊列的地址發現是同一個隊列
dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0);
複製代碼
// 全局隊列同步函數
- (void)globalQueueTest1{
NSLog(@"全局隊列同步");
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_sync(globalQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時操做
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印結果****************
2019-12-28 15:42:41.301152+0800 MultithreadingDemo[20363:3934000] 全局隊列同步
2019-12-28 15:42:42.302694+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:43.304170+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:44.305796+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:45.307420+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:46.309029+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:46.309392+0800 MultithreadingDemo[20363:3934000] ----end----
複製代碼
可見經過同步函數向全局隊列添加的任務都是在當前線程執行(本例中當前線程是主線程),而且是串行執行。
// 全局隊列異步函數
- (void)globalQueueTest2{
NSLog(@"全局隊列異步");
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(globalQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時操做
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印結果****************
2019-12-28 15:43:44.399828+0800 MultithreadingDemo[20363:3934000] 全局隊列異步
2019-12-28 15:43:44.400040+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 15:43:45.405629+0800 MultithreadingDemo[20363:3936377] 0--<NSThread: 0x600003065740>{number = 5, name = (null)}
2019-12-28 15:43:45.405655+0800 MultithreadingDemo[20363:3936378] 2--<NSThread: 0x60000306b100>{number = 9, name = (null)}
2019-12-28 15:43:45.405722+0800 MultithreadingDemo[20363:3934072] 1--<NSThread: 0x60000306b080>{number = 6, name = (null)}
2019-12-28 15:43:45.405657+0800 MultithreadingDemo[20363:3936380] 4--<NSThread: 0x60000306cb40>{number = 7, name = (null)}
2019-12-28 15:43:45.405779+0800 MultithreadingDemo[20363:3936379] 3--<NSThread: 0x600003061780>{number = 8, name = (null)}
複製代碼
可見經過異步函數向全局隊列添加的任務不是在當前線程執行,而是多個任務是在不一樣的線程中執行,而且是併發執行。
本身建立隊列的方法以下,能夠建立串行隊列
和併發隊列
兩種隊列。
DISPATCH_QUEUE_SERIAL
:串行隊列,隊列中的任務按先進先出的順序連續執行(一個任務執行完了才能執行下一個任務)。DISPATCH_QUEUE_SERIAL_INACTIVE
:串行隊列,此時這個串行隊列的狀態是不活躍的,在這個串行隊列調用以前,必須使用dispatch_activate()函數激活隊列(僅支持ios 10.0及之後的系統)。DISPATCH_QUEUE_CONCURRENT
:併發隊列,隊列中的任務能夠併發(同時)執行。DISPATCH_QUEUE_CONCURRENT_INACTIVE
:併發隊列,此時這個併發隊列的狀態是不活躍的,在這個併發隊列調用以前,必須使用dispatch_activate()函數激活隊列(僅支持ios 10.0及之後的系統)。/**
參數1:隊列的名稱,方便調試
參數2:隊列的類型,
若是是串行隊列,參數2爲DISPATCH_QUEUE_SERIAL或者NULL
若是是併發隊列,參數2爲DISPATCH_QUEUE_CONCURRENT
*/
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
複製代碼
對於本身建立的隊列,若是兩個參數同樣,那麼建立的是兩個不一樣的隊列,以下所示queue1和queue2是不一樣的隊列
。
// 打印兩個隊列的地址發現是不一樣的隊列
dispatch_queue_t queue1 = dispatch_queue_create("abc", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("abc", DISPATCH_QUEUE_SERIAL);
複製代碼
// 自建並串行列同步函數
- (void)customQueueTest1{
NSLog(@"自建串行隊列同步函數");
dispatch_queue_t serialQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i < 5; i++) {
dispatch_sync(serialQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時操做
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印結果****************
2019-12-28 15:45:26.206409+0800 MultithreadingDemo[20363:3934000] 自建串行隊列同步函數
2019-12-28 15:45:27.207582+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:28.208068+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:29.208996+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:30.210593+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:31.211514+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:31.211855+0800 MultithreadingDemo[20363:3934000] ----end----
複製代碼
可見經過同步函數向本身建立的串行隊列中添加的任務都是在當前線程執行,不會開啓新的線程,並且是串行執行。
注意這個和經過同步函數向主隊列添加任務不一樣。咱們仍是將方法customQueueTest1
稱做task1,將同步函數添加的任務稱做task2。這裏兩個task屬於2個不一樣的隊列,task1由主隊列安排執行,task2由本身建立的隊列來安排執行。首先主隊列安排task1到主線程中執行,當執行到task2的地方時,由本身建立的隊列安排task2到主線程中執行(無需等待task1完成),等task2執行完後繼續執行task1,因此這裏不會形成線程堵塞。
// 自建串行隊列異步函數
- (void)customQueueTest2{
NSLog(@"自建串行隊列異步函數");
dispatch_queue_t serialQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(serialQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時操做
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印結果****************
2019-12-28 15:52:55.958177+0800 MultithreadingDemo[20363:3934000] 自建串行隊列異步函數
2019-12-28 15:52:55.958379+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 15:52:56.962162+0800 MultithreadingDemo[20363:3937135] 0--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:52:57.966949+0800 MultithreadingDemo[20363:3937135] 1--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:52:58.971743+0800 MultithreadingDemo[20363:3937135] 2--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:52:59.977245+0800 MultithreadingDemo[20363:3937135] 3--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:53:00.981966+0800 MultithreadingDemo[20363:3937135] 4--<NSThread: 0x60000306c780>{number = 10, name = (null)}
複製代碼
可見經過異步函數向本身建立的串行隊列中添加的任務是在新開啓的線程中執行,並且全部任務都是在同一個子線程中執行(也就是說多個任務只會開啓一個子線程),並且是串行執行。
// 自建併發隊列同步函數
- (void)customQueueTest3{
NSLog(@"自建併發隊列同步函數<##>");
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 5; i++) {
dispatch_sync(concurrentQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時操做
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印結果****************
2019-12-28 15:56:47.003113+0800 MultithreadingDemo[20363:3934000] 自建併發隊列同步函數
2019-12-28 15:56:48.003737+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:49.005293+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:50.006181+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:51.006946+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:52.007620+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:52.007953+0800 MultithreadingDemo[20363:3934000] ----end----
複製代碼
可見經過同步函數向本身建立的併發隊列中添加的任務是在當前線程中執行,並且是串行執行。
// 自建併發隊列異步函數
- (void)customQueueTest4{
NSLog(@"自建併發隊列異步函數<##>");
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模擬耗時操做
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印結果****************
2019-12-28 16:01:01.262912+0800 MultithreadingDemo[20363:3934000] 自建併發隊列異步函數<##>
2019-12-28 16:01:01.263306+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 16:01:02.265466+0800 MultithreadingDemo[20363:3942614] 1--<NSThread: 0x600003061700>{number = 15, name = (null)}
2019-12-28 16:01:02.265478+0800 MultithreadingDemo[20363:3944671] 0--<NSThread: 0x60000306cec0>{number = 17, name = (null)}
2019-12-28 16:01:02.265608+0800 MultithreadingDemo[20363:3944674] 4--<NSThread: 0x600003061a80>{number = 19, name = (null)}
2019-12-28 16:01:02.265610+0800 MultithreadingDemo[20363:3944673] 3--<NSThread: 0x600003065740>{number = 18, name = (null)}
2019-12-28 16:01:02.265622+0800 MultithreadingDemo[20363:3944672] 2--<NSThread: 0x600003061640>{number = 20, name = (null)}
複製代碼
可見經過異步函數向本身建立的併發隊列添加的任務不是在當前線程執行,而是多個任務是在不一樣的線程中執行,而且是併發執行。
同步 | 異步 | |
---|---|---|
主隊列 | 沒有開啓新線程;線程阻塞 | 沒有開啓新線程;串行執行任務 |
全局隊列 | 沒有開啓新線程;串行執行任務 | 有開啓新線程;併發執行任務 |
本身建立串行隊列 | 沒有開啓新線程;串行執行任務 | 有開啓新線程;串行執行任務 |
本身建立併發隊列 | 沒有開啓新線程;串行執行任務 | 有開啓新線程;併發執行任務 |
當一個派發隊列中有任務還沒被安排執行時,咱們能夠選擇將隊列掛起,而後在合適的時機恢復執行。掛起和恢復的行數以下:
//掛起指定的dispatch_queue
void dispatch_suspend(dispatch_object_t object);
//恢復指定的dispatch_queue
void dispatch_resume(dispatch_object_t object);
複製代碼
注意dispatch_suspend
只能掛起還沒執行的任務,已經執行和正在執行的任務是沒有影響的。並且對主隊列
執行掛起操做是無效的。
柵欄函數是GCD提供的用於阻塞分割任務的一組函數。其主要做用就是在隊列中設置柵欄,來人爲干預隊列中任務的執行順序。也能夠理解爲用來設置任務之間的依賴關係。柵欄函數分同步柵欄函數
和異步柵欄函數
:
//同步柵欄函數
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
//異步柵欄函數
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
複製代碼
同步柵欄函數和異步柵欄函數的異同點:
相同點
:它們都將多個任務分割成了3個部分,第一個部分是柵欄函數以前的任務,是最早執行的;第二個部分是柵欄函數添加的任務,這個任務要等柵欄函數以前的任務都執行完了纔會執行;第三個部分是柵欄函數以後的任務,這個部分要等柵欄函數裏面的任務執行完了纔會執行。不一樣點
:同步柵欄函數不會開啓新線程,其添加的任務在當前線程執行,會阻塞當前線程;異步柵欄函數會開啓新線程來執行其添加的任務,不會阻塞當前線程。假設我如今有這樣一個需求:
一個大文件被分紅part1和part2兩部分存在服務器上,如今要將part1和part2都下載下來後而後合併並寫入磁盤。這裏其實有4個任務,下載part1是task1,下載part2是task2,合併part1和part2是task3,將合併後的文件寫入磁盤是task4。這4個任務執行順序是task1和task2併發異步執行,這兩個任務都執行完了後再執行task3,task3執行完了再執行task4。
經過同步柵欄函數和異步柵欄函數均可以實現這個需求,下面咱們來看看用同步和異步柵欄函數來實現有什麼區別。
// 同步柵欄函數
- (void)syncBarrier{
NSLog(@"當前線程1");
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"開始下載part1---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0f]; // 模擬下載耗時2s
NSLog(@"完成下載part1---%@",[NSThread currentThread]);
});
NSLog(@"當前線程2");
dispatch_async(queue, ^{
NSLog(@"開始下載part2---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模擬下載耗時1s
NSLog(@"完成下載part2---%@",[NSThread currentThread]);
});
NSLog(@"當前線程3");
dispatch_barrier_sync(queue, ^{
NSLog(@"開始合併part1和part2---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模擬下載耗時1s
NSLog(@"完成合並part1和part2---%@",[NSThread currentThread]);
});
NSLog(@"當前線程4");
dispatch_async(queue, ^{
NSLog(@"開始寫入磁盤---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模擬下載耗時1s
NSLog(@"完成寫入磁盤---%@",[NSThread currentThread]);
});
NSLog(@"當前線程5");
}
// ****************打印結果****************
2019-12-29 11:48:52.804380+0800 MultithreadingDemo[28408:4279520] 當前線程1
2019-12-29 11:48:52.805902+0800 MultithreadingDemo[28408:4279520] 當前線程2
2019-12-29 11:48:52.806323+0800 MultithreadingDemo[28408:4279520] 當前線程3
2019-12-29 11:48:52.806282+0800 MultithreadingDemo[28408:4292323] 開始下載part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:48:52.806513+0800 MultithreadingDemo[28408:4280937] 開始下載part2---<NSThread: 0x6000030d0600>{number = 9, name = (null)}
2019-12-29 11:48:53.806988+0800 MultithreadingDemo[28408:4280937] 完成下載part2---<NSThread: 0x6000030d0600>{number = 9, name = (null)}
2019-12-29 11:48:54.809914+0800 MultithreadingDemo[28408:4292323] 完成下載part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:48:54.810426+0800 MultithreadingDemo[28408:4279520] 開始合併part1和part2---<NSThread: 0x6000030a5940>{number = 1, name = main}
2019-12-29 11:48:55.810924+0800 MultithreadingDemo[28408:4279520] 完成合並part1和part2---<NSThread: 0x6000030a5940>{number = 1, name = main}
2019-12-29 11:48:55.811204+0800 MultithreadingDemo[28408:4279520] 當前線程4
2019-12-29 11:48:55.811405+0800 MultithreadingDemo[28408:4279520] 當前線程5
2019-12-29 11:48:55.811455+0800 MultithreadingDemo[28408:4292323] 開始寫入磁盤---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:48:56.816247+0800 MultithreadingDemo[28408:4292323] 完成寫入磁盤---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
複製代碼
運行結果能夠看出,需求的功能是實現了,可是有個問題,同步柵欄函數在分割任務的同時也阻塞了當前線程,這裏當前線程是主線程,這就意味着在task一、task2和task3這3個任務都完成以前,UI界面是出於卡死狀態的,這種用戶體驗顯然是很是糟糕的。下面咱們來看看異步柵欄函數來實現這個功能的效果。
// 異步柵欄函數
- (void)asyncBarrier{
NSLog(@"當前線程1");
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"開始下載part1---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0f]; // 模擬下載耗時2s
NSLog(@"完成下載part1---%@",[NSThread currentThread]);
});
NSLog(@"當前線程2");
dispatch_async(queue, ^{
NSLog(@"開始下載part2---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模擬下載耗時1s
NSLog(@"完成下載part2---%@",[NSThread currentThread]);
});
NSLog(@"當前線程3");
dispatch_barrier_async(queue, ^{
NSLog(@"開始合併part1和part2---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模擬下載耗時1s
NSLog(@"完成合並part1和part2---%@",[NSThread currentThread]);
});
NSLog(@"當前線程4");
dispatch_async(queue, ^{
NSLog(@"開始寫入磁盤---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模擬下載耗時1s
NSLog(@"完成寫入磁盤---%@",[NSThread currentThread]);
});
NSLog(@"當前線程5");
}
// ****************打印結果****************
2019-12-29 11:50:08.465656+0800 MultithreadingDemo[28408:4279520] 當前線程1
2019-12-29 11:50:08.466085+0800 MultithreadingDemo[28408:4279520] 當前線程2
2019-12-29 11:50:08.466158+0800 MultithreadingDemo[28408:4292323] 開始下載part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:08.466392+0800 MultithreadingDemo[28408:4279520] 當前線程3
2019-12-29 11:50:08.466516+0800 MultithreadingDemo[28408:4293001] 開始下載part2---<NSThread: 0x6000030d0440>{number = 14, name = (null)}
2019-12-29 11:50:08.466586+0800 MultithreadingDemo[28408:4279520] 當前線程4
2019-12-29 11:50:08.466707+0800 MultithreadingDemo[28408:4279520] 當前線程5
2019-12-29 11:50:09.471031+0800 MultithreadingDemo[28408:4293001] 完成下載part2---<NSThread: 0x6000030d0440>{number = 14, name = (null)}
2019-12-29 11:50:10.469408+0800 MultithreadingDemo[28408:4292323] 完成下載part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:10.469996+0800 MultithreadingDemo[28408:4292323] 開始合併part1和part2---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:11.475249+0800 MultithreadingDemo[28408:4292323] 完成合並part1和part2---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:11.475594+0800 MultithreadingDemo[28408:4292323] 開始寫入磁盤---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:12.478816+0800 MultithreadingDemo[28408:4292323] 完成寫入磁盤---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
複製代碼
從上面運行結果能夠看出,異步柵欄函數不會阻塞當前線程,也就是說UI界面並不會被卡死。
前面柵欄函數實現的需求也能夠經過任務組來實現。GCD中關於任務組的API以下:
// 建立一個任務組(任務組本質上是一個值爲LONG_MAX的信號量dispatch_semaphore_t)
dispatch_group_t dispatch_group_create(void);
// 向任務組中添加任務的異步函數。
// 參數一:任務組; 參數二:派發隊列; 參數三:任務block
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
// 監聽group組中任務的完成狀態,當全部的任務都執行完成後,觸發block塊
// 參數一:任務組;
// 參數二:是第三個參數block所處的派發隊列,這個隊列和任務組中的任務的隊列能夠不是同一個隊列,好比任務組中的任務都完成後須要刷新UI,那這個隊列就是主隊列;
// 參數三:任務組中的任務都完成後要執行的block
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
// dispatch_group_enter是將block任務添加到queue隊列,並被group組管理,任務組的任務數+1
// dispatch_group_leave是相應的任務執行完成,任務組的任務數-1
// 這兩個API是成對出現的
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
// 會阻塞當前線程,等其前面的任務都執行完了後纔會執行其後面的代碼
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
複製代碼
現需求以下:
某個界面須要請求banner信息和產品列表信息,等這兩個接口的數據都返回後再回到主線程刷新UI。
這個需求經過柵欄函數和任務組均可以實現,任務組能夠經過dispatch_async
、dispatch_group_enter
和dispatch_group_leave
這3個API配合使用來實現,也能夠經過dispatch_group_async
這個API來實現。
dispatch_async
、dispatch_group_enter
和dispatch_group_leave
// dispatch_group_enter()、dispatch_group_leave()和dispatch_async()配合使用
- (void)GCDGroup1{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"當前線程1");
dispatch_group_enter(group); // 開始任務前將任務交給任務組管理,任務組中任務數+1
dispatch_async(queue, ^{
NSLog(@"開始請求banner數據---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模擬下載耗時1s
NSLog(@"收到banner數據---%@",[NSThread currentThread]);
dispatch_group_leave(group); // 任務結束後將任務從任務組中移除,任務組中任務數-1
});
NSLog(@"當前線程2");
dispatch_group_enter(group); // 任務組中任務數+1
dispatch_async(queue, ^{
NSLog(@"開始請求產品列表數據---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.0f]; // 模擬下載耗時1s
NSLog(@"收到產品列表數據---%@",[NSThread currentThread]);
dispatch_group_leave(group); // 任務組中任務數-1
});
NSLog(@"當前線程3");
// 監放任務組中的任務的完成狀況,當任務組中全部任務都完成時指定隊列安排執行block中的代碼
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新UI---%@",[NSThread currentThread]);
});
NSLog(@"當前線程4");
}
// ****************打印結果****************
2019-12-30 08:56:19.991427+0800 MultithreadingDemo[35831:4040872] 當前線程1
2019-12-30 08:56:19.991561+0800 MultithreadingDemo[35831:4040872] 當前線程2
2019-12-30 08:56:19.991607+0800 MultithreadingDemo[35831:4040962] 開始請求banner數據---<NSThread: 0x600003f91f80>{number = 7, name = (null)}
2019-12-30 08:56:19.991664+0800 MultithreadingDemo[35831:4040872] 當前線程3
2019-12-30 08:56:19.991708+0800 MultithreadingDemo[35831:4044340] 開始請求產品列表數據---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:56:19.991738+0800 MultithreadingDemo[35831:4040872] 當前線程4
2019-12-30 08:56:20.992641+0800 MultithreadingDemo[35831:4040962] 收到banner數據---<NSThread: 0x600003f91f80>{number = 7, name = (null)}
2019-12-30 08:56:22.993730+0800 MultithreadingDemo[35831:4044340] 收到產品列表數據---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:56:22.993924+0800 MultithreadingDemo[35831:4040872] 回到主線程刷新UI---<NSThread: 0x600003fe6d00>{number = 1, name = main}
複製代碼
- (void)GCDGroup2{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"當前線程1");
dispatch_group_async(group, queue, ^{
NSLog(@"開始請求banner數據---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模擬下載耗時1s
NSLog(@"收到banner數據---%@",[NSThread currentThread]);
});
NSLog(@"當前線程2");
dispatch_group_async(group, queue, ^{
NSLog(@"開始請求產品列表數據---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.0f]; // 模擬下載耗時1s
NSLog(@"收到產品列表數據---%@",[NSThread currentThread]);
});
NSLog(@"當前線程3");
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新UI---%@",[NSThread currentThread]);
});
NSLog(@"當前線程4");
}
// ****************打印結果****************
2019-12-30 08:59:33.989079+0800 MultithreadingDemo[35831:4040872] 當前線程1
2019-12-30 08:59:33.989212+0800 MultithreadingDemo[35831:4040872] 當前線程2
2019-12-30 08:59:33.989248+0800 MultithreadingDemo[35831:4045455] 開始請求banner數據---<NSThread: 0x600003f9a3c0>{number = 9, name = (null)}
2019-12-30 08:59:33.989299+0800 MultithreadingDemo[35831:4040872] 當前線程3
2019-12-30 08:59:33.989313+0800 MultithreadingDemo[35831:4044340] 開始請求產品列表數據---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:59:33.989358+0800 MultithreadingDemo[35831:4040872] 當前線程4
2019-12-30 08:59:34.992763+0800 MultithreadingDemo[35831:4045455] 收到banner數據---<NSThread: 0x600003f9a3c0>{number = 9, name = (null)}
2019-12-30 08:59:36.992068+0800 MultithreadingDemo[35831:4044340] 收到產品列表數據---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:59:36.992348+0800 MultithreadingDemo[35831:4040872] 回到主線程刷新UI---<NSThread: 0x600003fe6d00>{number = 1, name = main}
複製代碼
從上面的打印結果能夠看出,這兩種實現方式的效果是同樣的,dispatch_group_notify
監放任務組並不會阻塞當前線程。
咱們把上面代碼中的dispatch_group_notify
換成dispatch_group_wait
再看看運行結果。
// dispatch_group_wait的使用
- (void)GCDGroup3{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"當前線程1");
dispatch_group_async(group, queue, ^{
NSLog(@"開始請求banner數據---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模擬下載耗時1s
NSLog(@"收到banner數據---%@",[NSThread currentThread]);
});
NSLog(@"當前線程2");
dispatch_group_async(group, queue, ^{
NSLog(@"開始請求產品列表數據---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.0f]; // 模擬下載耗時1s
NSLog(@"收到產品列表數據---%@",[NSThread currentThread]);
});
NSLog(@"當前線程3");
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"回到主線程刷新UI---%@",[NSThread currentThread]);
// });
// 將等待時間設置爲DISPATCH_TIME_FOREVER,表示永不超時,等任務組中任務所有都完成後纔會執行其後面的代碼
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"任務組中的任務所有完成,刷新UI");
NSLog(@"當前線程4");
}
// ****************打印結果****************
2019-12-30 09:10:12.841430+0800 MultithreadingDemo[36021:4052387] 當前線程1
2019-12-30 09:10:12.841566+0800 MultithreadingDemo[36021:4052387] 當前線程2
2019-12-30 09:10:12.841604+0800 MultithreadingDemo[36021:4052474] 開始請求banner數據---<NSThread: 0x60000111dcc0>{number = 2, name = (null)}
2019-12-30 09:10:12.841660+0800 MultithreadingDemo[36021:4052387] 當前線程3
2019-12-30 09:10:12.841704+0800 MultithreadingDemo[36021:4052591] 開始請求產品列表數據---<NSThread: 0x600001151680>{number = 6, name = (null)}
2019-12-30 09:10:13.842615+0800 MultithreadingDemo[36021:4052474] 收到banner數據---<NSThread: 0x60000111dcc0>{number = 2, name = (null)}
2019-12-30 09:10:15.842548+0800 MultithreadingDemo[36021:4052591] 收到產品列表數據---<NSThread: 0x600001151680>{number = 6, name = (null)}
2019-12-30 09:10:15.842738+0800 MultithreadingDemo[36021:4052387] 任務組中的任務所有完成,刷新UI
2019-12-30 09:10:15.842826+0800 MultithreadingDemo[36021:4052387] 當前線程4
複製代碼
結果發現dispatch_group_wait
雖然實現了需求,可是有個問題,它阻塞了當前線程
,效果和同步柵欄函數
同樣。
對於dispatch_group_wait
函數的第二個參數需特別說明一下,設置爲DISPATCH_TIME_FOREVER
表示永不超時,等任務組中任務所有都完成後纔會執行其後面的代碼。但若是將其設置爲一個具體的時間,好比下面設置爲2秒,那麼若是任務組中的全部任務須要1秒執行完,那麼就只須要等待1秒就能夠執行後面的代碼;若是任務組中所有任務執行完要3秒,那麼等待2秒後就會執行後面的代碼。
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)));
複製代碼
dispatch_after
是GCD中用於延遲將某個任務添加到隊列中,其定義以下:
void dispatch_after(dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);
複製代碼
when
:數據類型是dispatch_time_t
(後面會詳細介紹),表示延遲多長時間開始執行。queue
:管理要延遲執行的任務的派發隊列。block
:要延遲執行的任務塊。好比我要從如今開始,延遲3秒後在主線程刷新UI,其代碼以下。
// dispatch_after
// 需求:從如今開始,延遲3秒後在主線程刷新UI。
- (void)dispatchAfter{
NSLog(@"如今時間--%@",[NSDate date]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"到主線程刷新UI--%@",[NSDate date]);
});
}
// ****************打印結果****************
2019-12-30 10:19:00.397355+0800 MultithreadingDemo[36250:4082843] 如今時間--2019-12-30 02:19:00 +0000
2019-12-30 10:19:03.397714+0800 MultithreadingDemo[36250:4082843] 到主線程刷新UI--2019-12-30 02:19:03 +0000
複製代碼
建立dispatch_time_t
類型數據的函數有2個:dispatch_time
和dispatch_walltime
。
dispatch_time
定義以下:
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
複製代碼
when
:也是一個dispatch_time_t
類型的數據,表示從什麼時間開始,通常直接傳 DISPATCH_TIME_NOW
表示從如今開始。delta
:表示具體的時間長度,delta
的單位是納秒,因此不能直接傳 int 或 float, 須要寫成這種形式(int64_t)3 * NSEC_PER_SEC
來表示3秒。 前面dispatch_after
的示例代碼中就是用dispatch_time
來建立的dispatch_time_t
,下面咱們主要介紹一下第二個參數中用到的一些關於時間的宏:#define NSEC_PER_SEC 1000000000ull 每秒有1000000000納秒
#define NSEC_PER_MSEC 1000000ull 每毫秒有1000000納秒
#define USEC_PER_SEC 1000000ull 每秒有1000000微秒
#define NSEC_PER_USEC 1000ull 每微秒有1000納秒
複製代碼
再次強調一下delta
單位是納秒
,因此咱們表示1秒能夠有以下幾種寫法:
1 * NSEC_PER_SEC
1000 * NSEC_PER_MSEC
(表示1000毫秒)USEC_PER_SEC * NSEC_PER_USEC
dispatch_walltime
定義以下:
dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
複製代碼
when
:表示從什麼時間開始,是一個結構體,能夠建立一個絕對的時間點(好比2019-12-30 10:50:55)。也能夠傳NULL
表示從當前時間開始。delta
:和dispatch_time函數的第二個參數同樣。// dispatch_walltime
// 需求:從一個具體時間點開始,再晚10秒執行任務
- (void)dispatchWallTime{
NSString *dateStr = @"2019-12-30 11:09:00";
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT+0800"];
NSDate *date = [formatter dateFromString:dateStr];
NSTimeInterval timeInterval = [date timeIntervalSince1970];
// dispatch_walltime第一個參數的結構體
struct timespec timeStruct;
timeStruct.tv_sec = (NSInteger)timeInterval;
NSLog(@"設置的時間點--%@",[formatter stringFromDate:date]);
// 比時間點再晚10秒
dispatch_time_t time = dispatch_walltime(&timeStruct, (int64_t)(10 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"到主線程刷新UI--%@",[formatter stringFromDate:[NSDate date]]);
});
}
// ****************打印結果****************
2019-12-30 11:08:40.525710+0800 MultithreadingDemo[36430:4106292] 設置的時間點--2019-12-30 11:09:00
2019-12-30 11:09:10.000208+0800 MultithreadingDemo[36430:4106292] 到主線程刷新UI--2019-12-30 11:09:10
複製代碼
二者之間的區別:
dispatch_time
建立的是一個相對時間,它參考的是當前系統的時鐘,當設備進入休眠後,系統時鐘也會進入休眠狀態,此時dispatch_time
也會被掛起。好比10:00分開始執行dispatch_time
,而且60分鐘後執行某個任務。10:10分設備休眠了,10:40分設備從休眠中喚醒(共休眠了30分鐘),那麼從喚醒時刻開始,再等待50分鐘(也就是11:30分)纔會執行任務。dispatch_walltime
建立的是一個絕對的時間點,好比上面的例子,一旦建立就表示從10:00開始,60分鐘以後(也就是11:00)執行任務,它不會受到休眠的影響。dispatch_apply
相似for循環,會在指定的隊列中屢次執行任務。其定義以下:
void dispatch_apply(size_t iterations,
dispatch_queue_t queue,
DISPATCH_NOESCAPE void (^block)(size_t));
複製代碼
iterations
:執行的次數queue
:提交任務的隊列block
:執行任務的代碼塊- (void)dispatchApply{
NSLog(@"開始");
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"第%ld次開始執行--%@",index,[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.0f];
NSLog(@"第%ld次結束執行--%@",index,[NSThread currentThread]);
});
NSLog(@"結束");
}
// ****************打印結果****************
2019-12-30 11:59:35.025320+0800 MultithreadingDemo[36604:4129509] 開始
2019-12-30 11:59:35.025457+0800 MultithreadingDemo[36604:4129509] 第0次開始執行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:35.025616+0800 MultithreadingDemo[36604:4129600] 第1次開始執行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:35.025719+0800 MultithreadingDemo[36604:4129599] 第2次開始執行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:35.025735+0800 MultithreadingDemo[36604:4129598] 第3次開始執行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:38.025791+0800 MultithreadingDemo[36604:4129509] 第0次結束執行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:38.025944+0800 MultithreadingDemo[36604:4129599] 第2次結束執行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:38.025945+0800 MultithreadingDemo[36604:4129600] 第1次結束執行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:38.025951+0800 MultithreadingDemo[36604:4129598] 第3次結束執行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:38.026063+0800 MultithreadingDemo[36604:4129509] 第4次開始執行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:38.026082+0800 MultithreadingDemo[36604:4129600] 第6次開始執行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:38.026086+0800 MultithreadingDemo[36604:4129599] 第5次開始執行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:38.026145+0800 MultithreadingDemo[36604:4129598] 第7次開始執行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:41.027346+0800 MultithreadingDemo[36604:4129600] 第6次結束執行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:41.027346+0800 MultithreadingDemo[36604:4129509] 第4次結束執行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:41.027384+0800 MultithreadingDemo[36604:4129598] 第7次結束執行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:41.027402+0800 MultithreadingDemo[36604:4129599] 第5次結束執行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:41.027504+0800 MultithreadingDemo[36604:4129600] 第8次開始執行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:41.027506+0800 MultithreadingDemo[36604:4129509] 第9次開始執行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:44.027923+0800 MultithreadingDemo[36604:4129600] 第8次結束執行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:44.027923+0800 MultithreadingDemo[36604:4129509] 第9次結束執行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:44.028156+0800 MultithreadingDemo[36604:4129509] 結束
複製代碼
由打印結果能夠看出對於併發隊列dispatch_apply
會建立多個線程去併發執行,並且會阻塞當前線程,等全部任務都完成後纔會繼續執行後面的代碼。
對於串行隊列不會開啓新的線程,而是會在當前線程中串行執行。
GCD提供了dispatch_once()
函數保證在應用程序生命週期中只執行一次指定處理。好比來生成單例。
- (void)dispatchOnce{
static ViewController *vc = nil;
static dispatch_once_t onceToken;
dispatch_apply(3, dispatch_get_global_queue(0, 0), ^(size_t idx) {
NSLog(@"第%ld次開始執行--%@",idx,[NSThread currentThread]);
dispatch_once(&onceToken, ^{
vc = [[ViewController alloc] init];
NSLog(@"是否只執行了一次--%@",[NSThread currentThread]);
});
NSLog(@"第%ld次結束執行--%@",idx,[NSThread currentThread]);
});
}
// ****************打印結果****************
2019-12-30 12:14:43.911414+0800 MultithreadingDemo[36690:4137229] 第0次開始執行--<NSThread: 0x600002b7e0c0>{number = 1, name = main}
2019-12-30 12:14:43.911421+0800 MultithreadingDemo[36690:4137379] 第1次開始執行--<NSThread: 0x600002b1df40>{number = 6, name = (null)}
2019-12-30 12:14:43.911461+0800 MultithreadingDemo[36690:4137378] 第2次開始執行--<NSThread: 0x600002b0c280>{number = 7, name = (null)}
2019-12-30 12:14:43.911613+0800 MultithreadingDemo[36690:4137229] 是否只執行了一次--<NSThread: 0x600002b7e0c0>{number = 1, name = main}
2019-12-30 12:14:43.911748+0800 MultithreadingDemo[36690:4137229] 第0次結束執行--<NSThread: 0x600002b7e0c0>{number = 1, name = main}
2019-12-30 12:14:43.911750+0800 MultithreadingDemo[36690:4137378] 第2次結束執行--<NSThread: 0x600002b0c280>{number = 7, name = (null)}
2019-12-30 12:14:43.911765+0800 MultithreadingDemo[36690:4137379] 第1次結束執行--<NSThread: 0x600002b1df40>{number = 6, name = (null)}
複製代碼
dispatch_semaphore
用於控制最大併發數。其主要涉及到3個函數:
// 建立信號量API
dispatch_semaphore_t dispatch_semaphore_create(long value);
複製代碼
dispatch_semaphore_create()
建立並返回一個dispatch_semaphore_t類型的信號量,傳入的參數必須大於等於0,不然會返回NULL。傳入的參數value就是信號量的初始值,也能夠理解爲最大併發數。當這個值設置爲1時,最大併發數爲1,能夠當成鎖來使用。
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
複製代碼
dispatch_semaphore_wait()
函數的第一個參數是信號量,第二個參數是等待超時時間。這個函數首先會判斷信號量的值是否大於0,若是大於0,那麼信號值減1並繼續執行後續代碼;若是信號值等於0,那麼就阻塞當前線程進行等待,直到信號值大於0或等待超時時纔會繼續執行後續代碼。
// 發送信號
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
複製代碼
dispatch_semaphore_signal()
函數用來發送信號,發送信號後信號量的值會+1,它可使處於等待狀態的線程被喚醒。
// 信號量
- (void)dispatchSemaphore{
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
// 建立信號量並設置信號值(最大併發數)爲2
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
for (NSInteger i = 0; i < 5; i++) {
// 若是信號值大於0,信號值減1並執行後續代碼
// 若是信號值等於0,當前線程將被阻塞處於等待狀態,直到信號值大於0或者等待超時爲止
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"第%ld次開始執行--%@",i,[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f];
NSLog(@"第%ld次結束執行--%@",i,[NSThread currentThread]);
// 任務執行完後發送信號使信號值+1
dispatch_semaphore_signal(semaphore);
});
}
NSLog(@"******當前線程******");
}
// ****************打印結果****************
2019-12-30 14:33:16.757992+0800 MultithreadingDemo[36982:4182131] 第0次開始執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:17.762332+0800 MultithreadingDemo[36982:4182131] 第0次結束執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:17.762541+0800 MultithreadingDemo[36982:4182131] 第1次開始執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:18.764500+0800 MultithreadingDemo[36982:4182131] 第1次結束執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:18.764710+0800 MultithreadingDemo[36982:4182131] 第2次開始執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:19.766611+0800 MultithreadingDemo[36982:4182131] 第2次結束執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:19.766765+0800 MultithreadingDemo[36982:4182131] 第3次開始執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:20.770737+0800 MultithreadingDemo[36982:4182131] 第3次結束執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:20.770932+0800 MultithreadingDemo[36982:4182040] ******當前線程******
2019-12-30 14:33:20.770944+0800 MultithreadingDemo[36982:4182131] 第4次開始執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:21.771514+0800 MultithreadingDemo[36982:4182131] 第4次結束執行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
複製代碼
dispatch_source
是GCD中的一個基本類型,從字面意思可稱爲調度源,它的做用是當有一些特定的較底層的系統事件發生時,調度源會捕捉到這些事件,而後能夠作其餘的邏輯處理,調度源有多種類型,分別監聽對應類型的系統事件。關於dispatch_source
的詳細介紹能夠參考文章(iOS dispatch_source_t的理解)。
下面咱們經過dispatch_source
來實現一個倒計時的功能。
- (void)dispatchSource{
__block NSInteger timeout = 10; // 倒計時時間
dispatch_queue_t queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
/*
建立一個dispatch_source_t對象(其本質是一個OC對象)
第一個參數是要監聽的事件的類型
第4個參數是回調函數所在的隊列
第2和第3個參數是和監聽事件類型(第一個參數)有關的,監聽事件類型是DISPATCH_SOURCE_TYPE_TIMER時這兩個參數都設置爲0就能夠了。
具體的能夠參考博客 https://www.cnblogs.com/wjw-blog/p/5903441.html
*/
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
/*
設置計時器的一些參數
第一個參數是前面建立的dispatch_source_t對象
第二個參數是計時器開始的時間
第三個參數是計時器間隔時間
第四個參數是是一個微小的時間量,單位是納秒,系統爲了改進性能,會根據這個時間來推遲timer的執行以與其它系統活動同步。也就是設置timer容許的偏差。
*/
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0);
// 設置回調事件
dispatch_source_set_event_handler(self.timer, ^{
timeout--;
NSLog(@"%ld",timeout);
if (timeout <= 0) {
// 結束倒計時
dispatch_source_cancel(self.timer);
}
});
dispatch_resume(self.timer);
}
複製代碼
上面只是簡單經過GCD實現了計時器功能,咱們徹底能夠基於GCD本身封裝一個和NSTimer
功能相似的計時器。由於NSTimer
是基於Runloop
的,使用過程當中常常會遇到一些坑,並且NSTimer
計時器沒有GCD計時器精準。
本身封裝GCD計時器時必定要注意,dispatch_source_t
類型的timer
不要定義爲局部變量,不然定時器不起做用。