一、GCD 簡介
① 什么是 GCD ?
GCD 是 Apple 開發(fā)的一個多核編程的較新的解決方法; GCD 全稱:Grand Central Dispatch,是純 C 語言,提供非常多強大的函數(shù); 它主要用于優(yōu)化應用程序以支持多核處理器以及其他對稱多處理系統(tǒng); 它是一個在線程池模式的基礎(chǔ)上執(zhí)行的并發(fā)任務(wù); 在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。 簡而言之,GCD 就是將任務(wù)添加到隊列,并指定任務(wù)執(zhí)行的函數(shù) ;
② GCD 優(yōu)勢
GCD 是蘋果公司為多核的并行運算提出的解決方案; GCD 會自動利用更多的CPU內(nèi)核(比如雙核、四核); GCD 會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程); 程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼。
二、任務(wù)與隊列
① 任務(wù)
任務(wù)就是執(zhí)行操作的意思,換句話說就是在線程中執(zhí)行的那段代碼,在 GCD 中是放在 block 中的。 執(zhí)行任務(wù)有兩種方式:『同步執(zhí)行』 和 『異步執(zhí)行』。兩者的主要區(qū)別是:是否等待隊列的任務(wù)執(zhí)行結(jié)束,以及是否具備開啟新線程的能力。 同步執(zhí)行(sync): 同步添加任務(wù)到指定的隊列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會一直等待,直到隊列里面的任務(wù)完成之后再繼續(xù)執(zhí)行; 只能在當前線程中執(zhí)行任務(wù),不具備開啟新線程的能力。 異步執(zhí)行(async): 異步添加任務(wù)到指定的隊列中,它不會做任何等待,可以繼續(xù)執(zhí)行任務(wù); 可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力。 任務(wù)的創(chuàng)建:GCD 提供了同步執(zhí)行任務(wù)的創(chuàng)建方法 dispatch_sync 和異步執(zhí)行任務(wù)創(chuàng)建方法 dispatch_async。
dispatch_sync ( queue
, ^ { } ) ; dispatch_async ( queue
, ^ { } ) ;
② 隊列
隊列指執(zhí)行任務(wù)的等待隊列,即用來存放任務(wù)的隊列。 隊列是一種特殊的線性表,采用 FIFO(先進先出)的原則,即新任務(wù)總是被插入到隊列的末尾,而讀取任務(wù)的時候總是從隊列的頭部開始讀取。每讀取一個任務(wù),則從隊列中釋放一個任務(wù)。 隊列的結(jié)構(gòu)可參考下圖:
在 GCD 中有兩種隊列:『串行隊列』 和 『并發(fā)隊列』,兩者都符合 FIFO(先進先出)的原則,主要區(qū)別是:執(zhí)行順序不同,以及開啟線程數(shù)不同。 串行隊列(Serial Dispatch Queue) :每次只有一個任務(wù)被執(zhí)行,讓任務(wù)一個接著一個地執(zhí)行(只開啟一個線程,一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)),如下:
使用dispatch_queue_create(“xx”, DISPATCH_QUEUE_SERIAL)創(chuàng)建串行隊列,其中的 DISPATCH_QUEUE_SERIAL 也可以使用 NULL 表示,這兩種均表示默認的串行隊列;
dispatch_queue_t serialQueue1
= dispatch_queue_create ( "com.YDW.Queue" , NULL ) ; dispatch_queue_t serialQueue2
= dispatch_queue_create ( "com.YDW.Queue" , DISPATCH_QUEUE_SERIAL
) ;
并發(fā)隊列(Concurrent Dispatch Queue) :可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(可以開啟多個線程,并且同時執(zhí)行任務(wù)),如下:
使用 dispatch_queue_create(“xxx”, DISPATCH_QUEUE_CONCURRENT) 創(chuàng)建并發(fā)隊列:
dispatch_queue_t concurrentQueue
= dispatch_queue_create ( "com.YDW.Queue" , DISPATCH_QUEUE_CONCURRENT
) ;
注意:并發(fā)隊列的并發(fā)功能只有在異步函數(shù)(dispatch_async)下才有效。
③ 主隊列和全局并發(fā)隊列
主隊列(Main Dispatch Queue):GCD 中提供的特殊的串行隊列: 主要用來在主線程上調(diào)度任務(wù)的串行隊列,依賴于主線程、主 Runloop,在 main 函數(shù)調(diào)用之前自動創(chuàng)建; 不會開啟線程; 如果當前主線程正在有任務(wù)執(zhí)行,那么無論主隊列中當前被添加了什么任務(wù),都不會被調(diào)度; 使用dispatch_get_main_queue()獲得主隊列; 通常在返回主線程 更新UI時使用; 注意:主隊列其實并不特殊,實質(zhì)上就是一個普通的串行隊列,只是因為默認情況下,當前代碼是放在主隊列中的,然后主隊列中的代碼,有都會放到主線程中去執(zhí)行,所以才造成了主隊列特殊的現(xiàn)象。
dispatch_queue_t mainQueue
= dispatch_get_main_queue ( ) ;
全局并發(fā)隊列(Global Dispatch Queue) :GCD 提供的默認并發(fā)隊列: 為了方便程序員的使用,蘋果提供了全局隊列dispatch_get_global_queue; 在使用多線程開發(fā)時,如果對隊列沒有特殊需求,在執(zhí)行異步任務(wù)時,可以直接使用全局隊列; 使用 dispatch_get_global_queue 獲取全局并發(fā)隊列,最簡單的是dispatch_get_global_queue(0, 0); 全局隊列是一個并發(fā)隊列;
dispatch_queue_t globalQueue
= dispatch_get_global_queue ( 0 , 0 ) ; - DISPATCH_QUEUE_PRIORITY_HIGH
-- QOS_CLASS_USER_INITIATED
- DISPATCH_QUEUE_PRIORITY_DEFAULT
-- QOS_CLASS_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW
-- QOS_CLASS_UTILITY
- DISPATCH_QUEUE_PRIORITY_BACKGROUND
-- QOS_CLASS_BACKGROUND
日常開發(fā)中,全局并發(fā)隊列與主隊列通常配合使用:
dispatch_async ( dispatch_get_global_queue ( 0 , 0 ) , ^ { dispatch_async ( dispatch_get_main_queue ( ) , ^ { } ) ; } ) ;
三、函數(shù)與隊列的組合
(一)組合方式
既然有兩種隊列(串行隊列 / 并發(fā)隊列),兩種任務(wù)執(zhí)行方式(同步執(zhí)行 / 異步執(zhí)行),那么就有了四種不同的組合方式,這四種不同的組合方式是:
同步執(zhí)行
+ 串行隊列異步執(zhí)行
+ 串行隊列同步執(zhí)行
+ 并發(fā)隊列異步執(zhí)行
+ 并發(fā)隊列
實際上,上面還提到了兩種默認隊列:全局并發(fā)隊列和主隊列。全局并發(fā)隊列可以作為普通并發(fā)隊列來使用,但是當前代碼默認放在主隊列中,所以就又多了兩種組合方式,這樣就有六種不同的組合方式:
同步執(zhí)行
+ 主隊列異步執(zhí)行
+ 主隊列
① 同步執(zhí)行 + 串行隊列
任務(wù)按順序執(zhí)行,即任務(wù)一個接一個的在當前線程執(zhí)行,不會開辟新線程:
dispatch_queue_t serialQueue
= dispatch_queue_create ( "com.YDW.queue" , NULL ) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_sync ( serialQueue
, ^ { NSLog ( @"同步執(zhí)行 + 串行隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 33 : 25.553587 + 0800 GCD
[ 48756 : 7803380 ] 同步執(zhí)行
+ 串行隊列
: 0 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 33 : 25.553810 + 0800 GCD
[ 48756 : 7803380 ] 同步執(zhí)行
+ 串行隊列
: 1 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 33 : 25.553893 + 0800 GCD
[ 48756 : 7803380 ] 同步執(zhí)行
+ 串行隊列
: 2 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 33 : 25.553984 + 0800 GCD
[ 48756 : 7803380 ] 同步執(zhí)行
+ 串行隊列
: 3 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 33 : 25.554072 + 0800 GCD
[ 48756 : 7803380 ] 同步執(zhí)行
+ 串行隊列
: 4 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
}
② 異步執(zhí)行 + 串行隊列
任務(wù)按順序執(zhí)行,即任務(wù)一個接一個的執(zhí)行,但會開辟新線程:
dispatch_queue_t serialQueue
= dispatch_queue_create ( "com.YDW.queue" , NULL ) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_async ( serialQueue
, ^ { NSLog ( @"異步執(zhí)行 + 串行隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 35 : 18.365089 + 0800 GCD
[ 48776 : 7805999 ] 異步執(zhí)行
+ 串行隊列
: 0 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) } 2021 - 03 - 24 20 : 35 : 18.365240 + 0800 GCD
[ 48776 : 7805999 ] 異步執(zhí)行
+ 串行隊列
: 1 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) } 2021 - 03 - 24 20 : 35 : 18.365366 + 0800 GCD
[ 48776 : 7805999 ] 異步執(zhí)行
+ 串行隊列
: 2 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) } 2021 - 03 - 24 20 : 35 : 18.365491 + 0800 GCD
[ 48776 : 7805999 ] 異步執(zhí)行
+ 串行隊列
: 3 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) } 2021 - 03 - 24 20 : 35 : 18.365726 + 0800 GCD
[ 48776 : 7805999 ] 異步執(zhí)行
+ 串行隊列
: 4 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) }
③ 同步執(zhí)行 + 并發(fā)隊列
任務(wù)按順序執(zhí)行,即任務(wù)一個接一個的執(zhí)行,不開辟線程:
dispatch_queue_t concurrentQueue
= dispatch_queue_create ( "com.YDW.queue" , DISPATCH_QUEUE_CONCURRENT
) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_sync ( concurrentQueue
, ^ { NSLog ( @"同步執(zhí)行 + 并發(fā)隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 39 : 46.185808 + 0800 GCD
[ 48809 : 7810771 ] 同步執(zhí)行
+ 并發(fā)隊列
: 0 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 39 : 46.185940 + 0800 GCD
[ 48809 : 7810771 ] 同步執(zhí)行
+ 并發(fā)隊列
: 1 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 39 : 46.186041 + 0800 GCD
[ 48809 : 7810771 ] 同步執(zhí)行
+ 并發(fā)隊列
: 2 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 39 : 46.186150 + 0800 GCD
[ 48809 : 7810771 ] 同步執(zhí)行
+ 并發(fā)隊列
: 3 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 39 : 46.186258 + 0800 GCD
[ 48809 : 7810771 ] 同步執(zhí)行
+ 并發(fā)隊列
: 4 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
}
④ 異步執(zhí)行 + 并發(fā)隊列
任務(wù)無序執(zhí)行,即任務(wù)執(zhí)行無順序,會開辟新線程:
dispatch_queue_t concurrentQueue
= dispatch_queue_create ( "com.YDW.queue" , DISPATCH_QUEUE_CONCURRENT
) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_async ( concurrentQueue
, ^ { NSLog ( @"異步執(zhí)行 + 并發(fā)隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 42 : 55.252746 + 0800 GCD
[ 48847 : 7815248 ] 異步執(zhí)行
+ 并發(fā)隊列
: 2 - < NSThread
: 0x600002f6a980 > { number
= 5 , name
= ( null
) } 2021 - 03 - 24 20 : 42 : 55.252747 + 0800 GCD
[ 48847 : 7815249 ] 異步執(zhí)行
+ 并發(fā)隊列
: 1 - < NSThread
: 0x600002f74240 > { number
= 4 , name
= ( null
) } 2021 - 03 - 24 20 : 42 : 55.252750 + 0800 GCD
[ 48847 : 7815256 ] 異步執(zhí)行
+ 并發(fā)隊列
: 4 - < NSThread
: 0x600002f6c140 > { number
= 7 , name
= ( null
) } 2021 - 03 - 24 20 : 42 : 55.252750 + 0800 GCD
[ 48847 : 7815251 ] 異步執(zhí)行
+ 并發(fā)隊列
: 0 - < NSThread
: 0x600002f1ac00 > { number
= 3 , name
= ( null
) } 2021 - 03 - 24 20 : 42 : 55.252756 + 0800 GCD
[ 48847 : 7815250 ] 異步執(zhí)行
+ 并發(fā)隊列
: 3 - < NSThread
: 0x600002f74180 > { number
= 6 , name
= ( null
) }
⑤ 同步執(zhí)行 + 主隊列
NSLog ( @"同步執(zhí)行 + 主隊列 : %@" , [ NSThread currentThread
] ) ; dispatch_queue_t mainQueue
= dispatch_get_main_queue ( ) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_sync ( mainQueue
, ^ { NSLog ( @"同步執(zhí)行 + 主隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
造成死鎖的原因分析: 主隊列有兩個任務(wù),順序為:NSLog 任務(wù) - 同步 block; 執(zhí)行 NSLog 任務(wù)后,執(zhí)行同步 Block,會將任務(wù)1(即i=1時)加入到主隊列,主隊列順序為:NSLog任務(wù) - 同步block - 任務(wù)1; 任務(wù)1的執(zhí)行需要等待同步block執(zhí)行完畢才會執(zhí)行,而同步block的執(zhí)行需要等待任務(wù)1執(zhí)行完畢,所以就造成了任務(wù)互相等待的情況,即造成死鎖崩潰。
⑥ 異步執(zhí)行 + 主隊列
任務(wù)按順序執(zhí)行,即為任務(wù)一個接一個的執(zhí)行,不開辟線程:
dispatch_queue_t mainQueue
= dispatch_get_main_queue ( ) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_async ( mainQueue
, ^ { NSLog ( @"異步執(zhí)行 + 主隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 50 : 18.098874 + 0800 GCD
[ 48915 : 7823351 ] 異步執(zhí)行
+ 主隊列
: 0 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 50 : 18.099025 + 0800 GCD
[ 48915 : 7823351 ] 異步執(zhí)行
+ 主隊列
: 1 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 50 : 18.099132 + 0800 GCD
[ 48915 : 7823351 ] 異步執(zhí)行
+ 主隊列
: 2 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 50 : 18.099232 + 0800 GCD
[ 48915 : 7823351 ] 異步執(zhí)行
+ 主隊列
: 3 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 50 : 18.099338 + 0800 GCD
[ 48915 : 7823351 ] 異步執(zhí)行
+ 主隊列
: 4 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
}
(二)組合區(qū)別
① 主線程
主線程中,不同隊列 +不同任務(wù)簡單組合的區(qū)別:
區(qū)別并發(fā)隊列串行隊列主隊列 同步 沒有開啟新線程,串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù) 死鎖卡住不執(zhí)行 異步 有開啟新線程,并發(fā)執(zhí)行任務(wù) 有開啟新線程(1條),串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù)
從上邊可看出: 『主線程』 中調(diào)用 『主隊列』+『同步執(zhí)行』 會導致死鎖問題: 這是因為主隊列中追加的同步任務(wù)和主線程本身的任務(wù)兩者之間相互等待,阻塞了 主隊列,最終造成了主隊列所在的線程(主線程)死鎖問題; 而如果我們在其他線程調(diào)用主隊列 + 同步執(zhí)行,則不會阻塞主隊列,自然也不會造成死鎖問題。最終的結(jié)果是:不會開啟新線程,串行執(zhí)行任務(wù)。
② 隊列
不同隊列 + 不同任務(wù)組合,以及隊列中嵌套隊列使用的區(qū)別:
區(qū)別異步執(zhí)行+并發(fā)隊列嵌套同一個并發(fā)隊列同步執(zhí)行+并發(fā)隊列嵌套同一個并發(fā)隊列異步執(zhí)行+串行隊列嵌套同一個串行隊列同步執(zhí)行+串行隊列嵌套同一個串行隊列 同步(sync) 沒有開啟新的線程,串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù) 死鎖卡住不執(zhí)行 死鎖卡住不執(zhí)行 異步(async) 有開啟新線程,并發(fā)執(zhí)行任務(wù) 有開啟新線程,并發(fā)執(zhí)行任務(wù) 有開啟新線程(1 條),串行執(zhí)行任務(wù) 有開啟新線程(1 條),串行執(zhí)行任務(wù)
除了上邊提到的主線程中調(diào)用主隊列 + 同步執(zhí)行會導致死鎖問題。實際在使用串行隊列的時候,也可能出現(xiàn)阻塞串行隊列所在線程的情況發(fā)生,從而造成死鎖問題。這種情況多見于同一個串行隊列的嵌套使用。 下面代碼這樣:在異步執(zhí)行 + 串行任務(wù)的任務(wù)中,又嵌套了當前的串行隊列,然后進行同步執(zhí)行,如下:
dispatch_queue_t queue
= dispatch_queue_create ( "com.YDW.queue" , DISPATCH_QUEUE_SERIAL
) ; dispatch_async ( queue
, ^ { dispatch_sync ( queue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"1---%@" , [ NSThread currentThread
] ) ; } ) ; } ) ;
執(zhí)行上面的代碼會導致串行隊列中追加的任務(wù)和串行隊列中原有的任務(wù)兩者之間相互等待,阻塞了串行隊列,最終造成了串行隊列所在的線程(子線程)死鎖問題。
四、GCD 線程間的通信
在 iOS 開發(fā)過程中,我們一般在主線程里邊進行 UI 刷新,例如:點擊、滾動、拖拽等事件。通常把一些耗時的操作放在其他線程,比如說圖片下載、文件上傳等耗時操作。而有時候在其他線程完成了耗時操作時,需要回到主線程,那么就用到了線程之間的通訊。
dispatch_queue_t queue
= dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0 ) ; dispatch_queue_t mainQueue
= dispatch_get_main_queue ( ) ; dispatch_async ( queue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"1 : %@" , [ NSThread currentThread
] ) ; dispatch_async ( mainQueue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"2 : %@" , [ NSThread currentThread
] ) ; } ) ; } ) ;
2021 - 03 - 24 21 : 12 : 08.140921 + 0800 GCD
[ 48987 : 7842436 ] 1 : < NSThread
: 0x6000029e5340 > { number
= 5 , name
= ( null
) } 2021 - 03 - 24 21 : 12 : 10.142362 + 0800 GCD
[ 48987 : 7842289 ] 2 : < NSThread
: 0x6000029a8000 > { number
= 1 , name
= main
}
可以看到在其他線程中先執(zhí)行任務(wù),執(zhí)行完了之后回到主線程執(zhí)行主線程的相應操作。
五、GCD 其他方法
① GCD 柵欄方法:dispatch_barrier_async
我們有時需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作,這樣就需要一個相當于“柵欄”一樣的一個方法將兩組異步執(zhí)行的操作組給分割起來,當然這里的操作組里可以包含一個或多個任務(wù),這就需要用到dispatch_barrier_async 方法在兩個操作組間形成柵欄。 dispatch_barrier_async 方法會等待前邊追加到并發(fā)隊列中的任務(wù)全部執(zhí)行完畢之后,再將指定的任務(wù)追加到該異步隊列中。然后在 dispatch_barrier_async 方法追加的任務(wù)執(zhí)行完畢之后,異步隊列才恢復為一般動作,接著追加任務(wù)到該異步隊列并開始執(zhí)行。具體如下圖所示:
dispatch_queue_t queue
= dispatch_queue_create ( "net.bujige.testQueue" , DISPATCH_QUEUE_CONCURRENT
) ; dispatch_async ( queue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"1 : %@" , [ NSThread currentThread
] ) ; } ) ; dispatch_async ( queue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"2 : %@" , [ NSThread currentThread
] ) ; } ) ; dispatch_barrier_async ( queue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"barrier : %@" , [ NSThread currentThread
] ) ; } ) ; dispatch_async ( queue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"3 : %@" , [ NSThread currentThread
] ) ; } ) ; dispatch_async ( queue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"4 : %@" , [ NSThread currentThread
] ) ; } ) ;
2021 - 03 - 24 21 : 27 : 48.490812 + 0800 GCD
[ 49065 : 7858826 ] 1 : < NSThread
: 0x600003976000 > { number
= 6 , name
= ( null
) } 2021 - 03 - 24 21 : 27 : 48.490856 + 0800 GCD
[ 49065 : 7858825 ] 2 : < NSThread
: 0x600003973600 > { number
= 7 , name
= ( null
) } 2021 - 03 - 24 21 : 27 : 50.494787 + 0800 GCD
[ 49065 : 7858825 ] barrier
: < NSThread
: 0x600003973600 > { number
= 7 , name
= ( null
) } 2021 - 03 - 24 21 : 27 : 52.496547 + 0800 GCD
[ 49065 : 7858825 ] 3 : < NSThread
: 0x600003973600 > { number
= 7 , name
= ( null
) } 2021 - 03 - 24 21 : 27 : 52.496590 + 0800 GCD
[ 49065 : 7858826 ] 4 : < NSThread
: 0x600003976000 > { number
= 6 , name
= ( null
) }
在 dispatch_barrier_async 執(zhí)行結(jié)果中可以看出:在執(zhí)行完柵欄前面的操作之后,才執(zhí)行柵欄操作,最后再執(zhí)行柵欄后邊的操作。
② GCD 延時執(zhí)行方法:dispatch_after
我們經(jīng)常會遇到這樣的需求:在指定時間(例如 3 秒)之后執(zhí)行某個任務(wù),這個時候,可以用 GCD 的dispatch_after 方法來實現(xiàn)。 需要注意的是:dispatch_after 方法并不是在指定時間之后才開始執(zhí)行處理,而是在指定時間之后將任務(wù)追加到主隊列中。嚴格來說,這個時間并不是絕對準確的,但想要大致延遲執(zhí)行任務(wù),dispatch_after 方法是很有效的。
dispatch_after ( dispatch_time ( DISPATCH_TIME_NOW
, ( int64_t
) ( 2.0 * NSEC_PER_SEC
) ) , dispatch_get_main_queue ( ) , ^ { NSLog ( @"after---%@" , [ NSThread currentThread
] ) ; } ) ;
③ GCD 一次性代碼(只執(zhí)行一次):dispatch_once
在創(chuàng)建單例或者有整個程序運行過程中只執(zhí)行一次的代碼時,就需要用到 GCD 的 dispatch_once 方法。 使用 dispatch_once 方法能保證某段代碼在程序運行過程中只被執(zhí)行 1 次,并且即使在多線程的環(huán)境下,dispatch_once 也可以保證線程安全。
static dispatch_once_t onceToken
; dispatch_once ( & onceToken
, ^ { } ) ;
④ GCD 快速迭代方法:dispatch_apply
通常我們會用 for 循環(huán)遍歷,但是 GCD 給我們提供了快速迭代的方法 dispatch_apply。 dispatch_apply 按照指定的次數(shù)將指定的任務(wù)追加到指定的隊列中,并等待全部隊列執(zhí)行結(jié)束。 如果是在串行隊列中使用 dispatch_apply,那么就和 for 循環(huán)一樣,按順序同步執(zhí)行。 可以利用并發(fā)隊列進行異步執(zhí)行:比如說遍歷 0~5 這 6 個數(shù)字,for 循環(huán)的做法是每次取出一個元素,逐個遍歷,dispatch_apply 可以在多個線程中同時(異步)遍歷多個數(shù)字。 無論是在串行隊列,還是并發(fā)隊列中,dispatch_apply 都會等待全部任務(wù)執(zhí)行完畢,這點就像是同步操作,也像是隊列組中的 dispatch_group_wait方法。
dispatch_queue_t queue
= dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0 ) ; NSLog ( @"apply--begin" ) ; dispatch_apply ( 6 , queue
, ^ ( size_t index
) { NSLog ( @"%zd---%@" , index
, [ NSThread currentThread
] ) ; } ) ; NSLog ( @"apply--end" ) ;
2021 - 03 - 24 21 : 34 : 02.610819 + 0800 GCD
[ 49119 : 7865760 ] apply
-- begin
2021 - 03 - 24 21 : 34 : 02.611022 + 0800 GCD
[ 49119 : 7865760 ] 0 -- - < NSThread
: 0x6000004d41c0 > { number
= 1 , name
= main
} 2021 - 03 - 24 21 : 34 : 02.611026 + 0800 GCD
[ 49119 : 7865874 ] 1 -- - < NSThread
: 0x600000499800 > { number
= 3 , name
= ( null
) } 2021 - 03 - 24 21 : 34 : 02.611056 + 0800 GCD
[ 49119 : 7865872 ] 3 -- - < NSThread
: 0x60000048c3c0 > { number
= 5 , name
= ( null
) } 2021 - 03 - 24 21 : 34 : 02.611056 + 0800 GCD
[ 49119 : 7865871 ] 2 -- - < NSThread
: 0x60000048c840 > { number
= 6 , name
= ( null
) } 2021 - 03 - 24 21 : 34 : 02.611099 + 0800 GCD
[ 49119 : 7865875 ] 4 -- - < NSThread
: 0x60000048dc80 > { number
= 7 , name
= ( null
) } 2021 - 03 - 24 21 : 34 : 02.611140 + 0800 GCD
[ 49119 : 7865760 ] 5 -- - < NSThread
: 0x6000004d41c0 > { number
= 1 , name
= main
} 2021 - 03 - 24 21 : 34 : 02.611215 + 0800 GCD
[ 49119 : 7865760 ] apply
-- end
這是因為在并發(fā)隊列中異步執(zhí)行任務(wù),所以各個任務(wù)的執(zhí)行時間長短不定,最后結(jié)束順序也不定。但是 apply—end 一定在最后執(zhí)行,是因為 dispatch_apply 方法會等待全部任務(wù)執(zhí)行完畢。
⑤ GCD 隊列組:dispatch_group
有時候會有這樣的需求:分別異步執(zhí)行2個耗時任務(wù),然后當2個耗時任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù),這時候我們可以用到 GCD 的隊列組。 調(diào)用隊列組的 dispatch_group_async 先把任務(wù)放到隊列中,然后將隊列放入隊列組中,或者使用隊列組的 dispatch_group_enter、dispatch_group_leave 組合來實現(xiàn) dispatch_group_async。 調(diào)用隊列組的 dispatch_group_notify 回到指定線程執(zhí)行任務(wù),或者使用 dispatch_group_wait 回到當前線程繼續(xù)向下執(zhí)行(會阻塞當前線程)。 dispatch_group_notify 監(jiān)聽 group 中任務(wù)的完成狀態(tài),當所有的任務(wù)都執(zhí)行完成后,追加任務(wù)到 group 中,并執(zhí)行任務(wù)。
NSLog ( @"group--begin" ) ; dispatch_group_t group
= dispatch_group_create ( ) ; dispatch_group_async ( group
, dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0 ) , ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"1 : %@" , [ NSThread currentThread
] ) ; } ) ; dispatch_group_async ( group
, dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0 ) , ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"2 : %@" , [ NSThread currentThread
] ) ; } ) ; dispatch_group_notify ( group
, dispatch_get_main_queue ( ) , ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"3 : %@" , [ NSThread currentThread
] ) ; NSLog ( @"group--end" ) ; } ) ;
從 dispatch_group_notify 相關(guān)代碼運行輸出結(jié)果可以看出: 當所有任務(wù)都執(zhí)行完成之后,才執(zhí)行 dispatch_group_notify 相關(guān) block 中的任務(wù)。 dispatch_group_wait 暫停當前線程(阻塞當前線程),等待指定的 group 中的任務(wù)執(zhí)行完成后,才會往下繼續(xù)執(zhí)行。
NSLog ( @"group--begin" ) ; dispatch_group_t group
= dispatch_group_create ( ) ; dispatch_group_async ( group
, dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0 ) , ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"1 : %@" , [ NSThread currentThread
] ) ; } ) ; dispatch_group_async ( group
, dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0 ) , ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"2 : %@" , [ NSThread currentThread
] ) ; } ) ; dispatch_group_wait ( group
, DISPATCH_TIME_FOREVER
) ; NSLog ( @"group--end" ) ;
從 dispatch_group_wait 相關(guān)代碼運行輸出結(jié)果可以看出: 當所有任務(wù)執(zhí)行完成之后,才執(zhí)行 dispatch_group_wait 之后的操作。 注意:使用dispatch_group_wait 會阻塞當前線程。 dispatch_group_enter、dispatch_group_leave dispatch_group_enter 標志著一個任務(wù)追加到 group,執(zhí)行一次,相當于 group 中未執(zhí)行完畢任務(wù)數(shù) +1; dispatch_group_leave 標志著一個任務(wù)離開了 group,執(zhí)行一次,相當于 group 中未執(zhí)行完畢任務(wù)數(shù) -1; 當 group 中未執(zhí)行完畢任務(wù)數(shù)為0的時候,才會使 dispatch_group_wait 解除阻塞,以及執(zhí)行追加到 dispatch_group_notify 中的任務(wù)。
NSLog ( @"group--begin" ) ; dispatch_group_t group
= dispatch_group_create ( ) ; dispatch_queue_t queue
= dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0 ) ; dispatch_group_enter ( group
) ; dispatch_async ( queue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"1 : %@" , [ NSThread currentThread
] ) ; dispatch_group_leave ( group
) ; } ) ; dispatch_group_enter ( group
) ; dispatch_async ( queue
, ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"2 : %@" , [ NSThread currentThread
] ) ; dispatch_group_leave ( group
) ; } ) ; dispatch_group_notify ( group
, dispatch_get_main_queue ( ) , ^ { [ NSThread sleepForTimeInterval
: 2 ] ; NSLog ( @"3 : %@" , [ NSThread currentThread
] ) ; NSLog ( @"group--end" ) ; } ) ;
從 dispatch_group_enter、dispatch_group_leave 相關(guān)代碼運行結(jié)果中可以看出:當所有任務(wù)執(zhí)行完成之后,才執(zhí)行 dispatch_group_notify 中的任務(wù),這里的dispatch_group_enter、dispatch_group_leave 組合,其實也等同于dispatch_group_async。
⑥ GCD 信號量:dispatch_semaphore
GCD 中的信號量是指 Dispatch Semaphore,是持有計數(shù)的信號,類似于過高速路收費站的欄桿,可以通過時,打開欄桿,不可以通過時,關(guān)閉欄桿。 在 Dispatch Semaphore 中,使用計數(shù)來完成這個功能,計數(shù)小于 0 時等待,不可通過。計數(shù)為 0 或大于 0 時,計數(shù)減 1 且不等待,可通過。 Dispatch Semaphore 提供了三個方法: dispatch_semaphore_create:創(chuàng)建一個 Semaphore 并初始化信號的總量; dispatch_semaphore_signal:發(fā)送一個信號,讓信號總量加 1; dispatch_semaphore_wait:可以使總信號量減 1,信號總量小于 0 時就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行。 注意:信號量的使用前提是,想清楚你需要處理哪個線程等待(阻塞),又要哪個線程繼續(xù)執(zhí)行,然后使用信號量。 Dispatch Semaphore 在實際開發(fā)中主要用于: 保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù); 保證線程安全,為線程加鎖。 Dispatch Semaphore 線程同步 在開發(fā)中,可能會遇到這樣的需求:異步執(zhí)行耗時任務(wù),并使用異步執(zhí)行的結(jié)果進行一些額外的操作。換句話說,相當于,將將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)。 比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法,通過引入信號量的方式,等待異步執(zhí)行任務(wù)結(jié)果,獲取到 tasks,然后再返回該 tasks。
- ( NSArray
* ) tasksForKeyPath
: ( NSString
* ) keyPath
{ __block NSArray
* tasks
= nil
; dispatch_semaphore_t semaphore
= dispatch_semaphore_create ( 0 ) ; [ self . session getTasksWithCompletionHandler
: ^ ( NSArray
* dataTasks
, NSArray
* uploadTasks
, NSArray
* downloadTasks
) { if ( [ keyPath isEqualToString
: NSStringFromSelector ( @selector ( dataTasks
) ) ] ) { tasks
= dataTasks
; } else if ( [ keyPath isEqualToString
: NSStringFromSelector ( @selector ( uploadTasks
) ) ] ) { tasks
= uploadTasks
; } else if ( [ keyPath isEqualToString
: NSStringFromSelector ( @selector ( downloadTasks
) ) ] ) { tasks
= downloadTasks
; } else if ( [ keyPath isEqualToString
: NSStringFromSelector ( @selector ( tasks
) ) ] ) { tasks
= [ @ [ dataTasks
, uploadTasks
, downloadTasks
] valueForKeyPath
: @"@unionOfArrays.self" ] ; } dispatch_semaphore_signal ( semaphore
) ; } ] ; dispatch_semaphore_wait ( semaphore
, DISPATCH_TIME_FOREVER
) ; return tasks
; }
下面來利用 Dispatch Semaphore 實現(xiàn)線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)。
NSLog ( @"currentThread--%@" , [ NSThread currentThread
] ) ; NSLog ( @"semaphore--begin" ) ; dispatch_queue_t queue
= dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0 ) ; dispatch_semaphore_t semaphore
= dispatch_semaphore_create ( 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 ( @"semaphore--end,number = %zd" , number
) ;
從 Dispatch Semaphore 實現(xiàn)線程同步的代碼可以看到:semaphore–end 是在執(zhí)行完 number = 100; 之后才打印的,而且輸出結(jié)果 number 為 100。這是因為異步執(zhí)行不會做任何等待,可以繼續(xù)執(zhí)行任務(wù)。執(zhí)行順如下: semaphore 初始創(chuàng)建時計數(shù)為 0。 異步執(zhí)行 將 任務(wù) 1 追加到隊列之后,不做等待,接著執(zhí)行 dispatch_semaphore_wait 方法,semaphore 減 1,此時 semaphore == -1,當前線程進入等待狀態(tài)。 然后,異步任務(wù) 1 開始執(zhí)行。任務(wù) 1 執(zhí)行到 dispatch_semaphore_signal 之后,總信號量加 1,此時 semaphore == 0,正在被阻塞的線程(主線程)恢復繼續(xù)執(zhí)行。 最后打印 semaphore—end,number = 100。 這樣就實現(xiàn)了線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)。 Dispatch Semaphore 線程安全和線程同步(為線程加鎖) 線程安全:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。 若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。 線程同步:可理解為線程 A 和 線程 B 一塊配合,A 執(zhí)行到一定程度時要依靠線程 B 的某個結(jié)果,于是停下來,示意 B 運行;B 依言執(zhí)行,再將結(jié)果給 A;A 再繼續(xù)操作。
⑦ dispatch_source
dispatch_source 是基礎(chǔ)數(shù)據(jù)類型,用于協(xié)調(diào)特定底層系統(tǒng)事件的處理。 dispatch_source 替代了異步回調(diào)函數(shù),來處理系統(tǒng)相關(guān)的事件,當配置一個dispatch 時,你需要指定監(jiān)測的事件、dispatch queue、以及處理事件的代碼(block或函數(shù))。當事件發(fā)生時,dispatch source會提交你的block或函數(shù)到指定的queue去執(zhí)行; 使用 dispatch_source 而不使用 dispatch_async 的主要原因就是利用聯(lián)結(jié)的優(yōu)勢; 聯(lián)結(jié)就是:在任一線程上調(diào)用它的的一個函數(shù) dispatch_ source_ merge_ data 后,會執(zhí)行 dispatch_source 事先定義好的句柄(可以把句柄簡單理解為一個block),這個過程叫 Custom event 用戶事件,是 dispatch source 支持處理的一種事件,即為調(diào)用 dispatch_source_merge_data 函數(shù)來向自己發(fā)出的信號。 句柄是一種指向指針的指針,它指向的就是一個類或者結(jié)構(gòu),和系統(tǒng)有很密切的關(guān)系,HINSTANCE (實例句柄)、HBITMAP (位圖句柄) 、HDC (設(shè)備表述句柄)、 HICON(圖標句柄)等,這當中還有一個通用的句柄,就是HANDLE; 實例句柄 HINSTANCE 位圖句柄 HBITMAP 設(shè)備表句柄 HDC 圖標句柄 HICON dispatch_source 的CPU負荷非常小,盡量不占用資源 。 dispatch_source 的創(chuàng)建:
dispatch_source_t source
= dispatch_source_create ( dispatch_source_type_t type
, uintptr_t handle
, unsigned long mask
, dispatch_queue_t queue
)
種類說明 DISPATCH_SOURCE_TYPE_DATA_ADD 自定義的事件,變量增加 DISPATCH_SOURCE_TYPE_DATA_OR 自定義的事件,變量OR DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口發(fā)送 DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收 DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 內(nèi)存壓力 (注:iOS8后可用) DISPATCH_SOURCE_TYPE_PROC 進程監(jiān)聽,如進程的退出、創(chuàng)建一個或更多的子線程、進程收到UNIX信號 DISPATCH_SOURCE_TYPE_READ IO操作,如對文件的操作、socket操作的讀響應 DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信號時響應 DISPATCH_SOURCE_TYPE_TIMER 定時器 DISPATCH_SOURCE_TYPE_VNODE 文件狀態(tài)監(jiān)聽,文件被刪除、移動、重命名 DISPATCH_SOURCE_TYPE_WRITE IO操作,如對文件的操作、socket操作的寫響應
dispatch_source 常用函數(shù):
dispatch_suspend ( queue
) dispatch_resume ( source
) dispatch_source_merge_data dispatch_source_set_event_handler dispatch_source_get_data uintptr_t
dispatch_source_get_handle ( dispatch_source_t source
) ; unsigned long dispatch_source_get_mask ( dispatch_source_t source
) ; void dispatch_source_cancel ( dispatch_source_t source
) ; long dispatch_source_testcancel ( dispatch_source_t source
) ; void dispatch_source_set_cancel_handler ( dispatch_source_t source
, dispatch_block_t cancel_handler
) ; void dispatch_source_set_registration_handler ( dispatch_source_t source
, dispatch_block_t registration_handler
) ;
dispatch_source 使用場景:由于 dispatch_source 不依賴于 Runloop,而是直接和底層內(nèi)核交互,準確性更高,所以經(jīng)常用于驗證碼倒計時。
- ( void ) use
{ __block
int timeout
= 3 ; dispatch_queue_t globalQueue
= dispatch_get_global_queue ( 0 , 0 ) ; dispatch_source_t timer
= dispatch_source_create ( DISPATCH_SOURCE_TYPE_TIMER
, 0 , 0 , globalQueue
) ; dispatch_source_set_timer ( timer
, dispatch_walltime ( NULL , 0 ) , 1.0 * NSEC_PER_SEC
, 0 ) ; dispatch_source_set_event_handler ( timer
, ^ { if ( timeout
<= 0 ) { dispatch_source_cancel ( timer
) ; } else { timeout
-- ; dispatch_async ( dispatch_get_main_queue ( ) , ^ { NSLog ( @"倒計時 - %d" , timeout
) ; } ) ; } } ) ; dispatch_resume ( timer
) ; }
總結(jié)
以上是生活随笔 為你收集整理的iOS之深入分析GCD的函数与队列以及多种组合使用 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。