一、GCD 簡介
① 什么是 GCD ?
GCD 是 Apple 開發的一個多核編程的較新的解決方法; GCD 全稱:Grand Central Dispatch,是純 C 語言,提供非常多強大的函數; 它主要用于優化應用程序以支持多核處理器以及其他對稱多處理系統; 它是一個在線程池模式的基礎上執行的并發任務; 在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。 簡而言之,GCD 就是將任務添加到隊列,并指定任務執行的函數 ;
② GCD 優勢
GCD 是蘋果公司為多核的并行運算提出的解決方案; GCD 會自動利用更多的CPU內核(比如雙核、四核); GCD 會自動管理線程的生命周期(創建線程、調度任務、銷毀線程); 程序員只需要告訴 GCD 想要執行什么任務,不需要編寫任何線程管理代碼。
二、任務與隊列
① 任務
任務就是執行操作的意思,換句話說就是在線程中執行的那段代碼,在 GCD 中是放在 block 中的。 執行任務有兩種方式:『同步執行』 和 『異步執行』。兩者的主要區別是:是否等待隊列的任務執行結束,以及是否具備開啟新線程的能力。 同步執行(sync): 同步添加任務到指定的隊列中,在添加的任務執行結束之前,會一直等待,直到隊列里面的任務完成之后再繼續執行; 只能在當前線程中執行任務,不具備開啟新線程的能力。 異步執行(async): 異步添加任務到指定的隊列中,它不會做任何等待,可以繼續執行任務; 可以在新的線程中執行任務,具備開啟新線程的能力。 任務的創建:GCD 提供了同步執行任務的創建方法 dispatch_sync 和異步執行任務創建方法 dispatch_async。
dispatch_sync ( queue
, ^ { } ) ; dispatch_async ( queue
, ^ { } ) ;
② 隊列
隊列指執行任務的等待隊列,即用來存放任務的隊列。 隊列是一種特殊的線性表,采用 FIFO(先進先出)的原則,即新任務總是被插入到隊列的末尾,而讀取任務的時候總是從隊列的頭部開始讀取。每讀取一個任務,則從隊列中釋放一個任務。 隊列的結構可參考下圖:
在 GCD 中有兩種隊列:『串行隊列』 和 『并發隊列』,兩者都符合 FIFO(先進先出)的原則,主要區別是:執行順序不同,以及開啟線程數不同。 串行隊列(Serial Dispatch Queue) :每次只有一個任務被執行,讓任務一個接著一個地執行(只開啟一個線程,一個任務執行完畢后,再執行下一個任務),如下:
使用dispatch_queue_create(“xx”, DISPATCH_QUEUE_SERIAL)創建串行隊列,其中的 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
) ;
并發隊列(Concurrent Dispatch Queue) :可以讓多個任務并發(同時)執行(可以開啟多個線程,并且同時執行任務),如下:
使用 dispatch_queue_create(“xxx”, DISPATCH_QUEUE_CONCURRENT) 創建并發隊列:
dispatch_queue_t concurrentQueue
= dispatch_queue_create ( "com.YDW.Queue" , DISPATCH_QUEUE_CONCURRENT
) ;
注意:并發隊列的并發功能只有在異步函數(dispatch_async)下才有效。
③ 主隊列和全局并發隊列
主隊列(Main Dispatch Queue):GCD 中提供的特殊的串行隊列: 主要用來在主線程上調度任務的串行隊列,依賴于主線程、主 Runloop,在 main 函數調用之前自動創建; 不會開啟線程; 如果當前主線程正在有任務執行,那么無論主隊列中當前被添加了什么任務,都不會被調度; 使用dispatch_get_main_queue()獲得主隊列; 通常在返回主線程 更新UI時使用; 注意:主隊列其實并不特殊,實質上就是一個普通的串行隊列,只是因為默認情況下,當前代碼是放在主隊列中的,然后主隊列中的代碼,有都會放到主線程中去執行,所以才造成了主隊列特殊的現象。
dispatch_queue_t mainQueue
= dispatch_get_main_queue ( ) ;
全局并發隊列(Global Dispatch Queue) :GCD 提供的默認并發隊列: 為了方便程序員的使用,蘋果提供了全局隊列dispatch_get_global_queue; 在使用多線程開發時,如果對隊列沒有特殊需求,在執行異步任務時,可以直接使用全局隊列; 使用 dispatch_get_global_queue 獲取全局并發隊列,最簡單的是dispatch_get_global_queue(0, 0); 全局隊列是一個并發隊列;
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
dispatch_async ( dispatch_get_global_queue ( 0 , 0 ) , ^ { dispatch_async ( dispatch_get_main_queue ( ) , ^ { } ) ; } ) ;
三、函數與隊列的組合
(一)組合方式
既然有兩種隊列(串行隊列 / 并發隊列),兩種任務執行方式(同步執行 / 異步執行),那么就有了四種不同的組合方式,這四種不同的組合方式是:
同步執行
+ 串行隊列異步執行
+ 串行隊列同步執行
+ 并發隊列異步執行
+ 并發隊列
實際上,上面還提到了兩種默認隊列:全局并發隊列和主隊列。全局并發隊列可以作為普通并發隊列來使用,但是當前代碼默認放在主隊列中,所以就又多了兩種組合方式,這樣就有六種不同的組合方式:
同步執行
+ 主隊列異步執行
+ 主隊列
① 同步執行 + 串行隊列
任務按順序執行,即任務一個接一個的在當前線程執行,不會開辟新線程:
dispatch_queue_t serialQueue
= dispatch_queue_create ( "com.YDW.queue" , NULL ) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_sync ( serialQueue
, ^ { NSLog ( @"同步執行 + 串行隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 33 : 25.553587 + 0800 GCD
[ 48756 : 7803380 ] 同步執行
+ 串行隊列
: 0 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 33 : 25.553810 + 0800 GCD
[ 48756 : 7803380 ] 同步執行
+ 串行隊列
: 1 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 33 : 25.553893 + 0800 GCD
[ 48756 : 7803380 ] 同步執行
+ 串行隊列
: 2 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 33 : 25.553984 + 0800 GCD
[ 48756 : 7803380 ] 同步執行
+ 串行隊列
: 3 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 33 : 25.554072 + 0800 GCD
[ 48756 : 7803380 ] 同步執行
+ 串行隊列
: 4 - < NSThread
: 0x600003f3cac0 > { number
= 1 , name
= main
}
② 異步執行 + 串行隊列
任務按順序執行,即任務一個接一個的執行,但會開辟新線程:
dispatch_queue_t serialQueue
= dispatch_queue_create ( "com.YDW.queue" , NULL ) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_async ( serialQueue
, ^ { NSLog ( @"異步執行 + 串行隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 35 : 18.365089 + 0800 GCD
[ 48776 : 7805999 ] 異步執行
+ 串行隊列
: 0 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) } 2021 - 03 - 24 20 : 35 : 18.365240 + 0800 GCD
[ 48776 : 7805999 ] 異步執行
+ 串行隊列
: 1 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) } 2021 - 03 - 24 20 : 35 : 18.365366 + 0800 GCD
[ 48776 : 7805999 ] 異步執行
+ 串行隊列
: 2 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) } 2021 - 03 - 24 20 : 35 : 18.365491 + 0800 GCD
[ 48776 : 7805999 ] 異步執行
+ 串行隊列
: 3 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) } 2021 - 03 - 24 20 : 35 : 18.365726 + 0800 GCD
[ 48776 : 7805999 ] 異步執行
+ 串行隊列
: 4 - < NSThread
: 0x60000013a100 > { number
= 8 , name
= ( null
) }
③ 同步執行 + 并發隊列
任務按順序執行,即任務一個接一個的執行,不開辟線程:
dispatch_queue_t concurrentQueue
= dispatch_queue_create ( "com.YDW.queue" , DISPATCH_QUEUE_CONCURRENT
) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_sync ( concurrentQueue
, ^ { NSLog ( @"同步執行 + 并發隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 39 : 46.185808 + 0800 GCD
[ 48809 : 7810771 ] 同步執行
+ 并發隊列
: 0 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 39 : 46.185940 + 0800 GCD
[ 48809 : 7810771 ] 同步執行
+ 并發隊列
: 1 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 39 : 46.186041 + 0800 GCD
[ 48809 : 7810771 ] 同步執行
+ 并發隊列
: 2 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 39 : 46.186150 + 0800 GCD
[ 48809 : 7810771 ] 同步執行
+ 并發隊列
: 3 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 39 : 46.186258 + 0800 GCD
[ 48809 : 7810771 ] 同步執行
+ 并發隊列
: 4 - < NSThread
: 0x600003388240 > { number
= 1 , name
= main
}
④ 異步執行 + 并發隊列
dispatch_queue_t concurrentQueue
= dispatch_queue_create ( "com.YDW.queue" , DISPATCH_QUEUE_CONCURRENT
) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_async ( concurrentQueue
, ^ { NSLog ( @"異步執行 + 并發隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 42 : 55.252746 + 0800 GCD
[ 48847 : 7815248 ] 異步執行
+ 并發隊列
: 2 - < NSThread
: 0x600002f6a980 > { number
= 5 , name
= ( null
) } 2021 - 03 - 24 20 : 42 : 55.252747 + 0800 GCD
[ 48847 : 7815249 ] 異步執行
+ 并發隊列
: 1 - < NSThread
: 0x600002f74240 > { number
= 4 , name
= ( null
) } 2021 - 03 - 24 20 : 42 : 55.252750 + 0800 GCD
[ 48847 : 7815256 ] 異步執行
+ 并發隊列
: 4 - < NSThread
: 0x600002f6c140 > { number
= 7 , name
= ( null
) } 2021 - 03 - 24 20 : 42 : 55.252750 + 0800 GCD
[ 48847 : 7815251 ] 異步執行
+ 并發隊列
: 0 - < NSThread
: 0x600002f1ac00 > { number
= 3 , name
= ( null
) } 2021 - 03 - 24 20 : 42 : 55.252756 + 0800 GCD
[ 48847 : 7815250 ] 異步執行
+ 并發隊列
: 3 - < NSThread
: 0x600002f74180 > { number
= 6 , name
= ( null
) }
⑤ 同步執行 + 主隊列
NSLog ( @"同步執行 + 主隊列 : %@" , [ NSThread currentThread
] ) ; dispatch_queue_t mainQueue
= dispatch_get_main_queue ( ) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_sync ( mainQueue
, ^ { NSLog ( @"同步執行 + 主隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
造成死鎖的原因分析: 主隊列有兩個任務,順序為:NSLog 任務 - 同步 block; 執行 NSLog 任務后,執行同步 Block,會將任務1(即i=1時)加入到主隊列,主隊列順序為:NSLog任務 - 同步block - 任務1; 任務1的執行需要等待同步block執行完畢才會執行,而同步block的執行需要等待任務1執行完畢,所以就造成了任務互相等待的情況,即造成死鎖崩潰。
⑥ 異步執行 + 主隊列
任務按順序執行,即為任務一個接一個的執行,不開辟線程:
dispatch_queue_t mainQueue
= dispatch_get_main_queue ( ) ; for ( int i
= 0 ; i
< 5 ; i
++ ) { dispatch_async ( mainQueue
, ^ { NSLog ( @"異步執行 + 主隊列 : %d-%@" , i
, [ NSThread currentThread
] ) ; } ) ; }
2021 - 03 - 24 20 : 50 : 18.098874 + 0800 GCD
[ 48915 : 7823351 ] 異步執行
+ 主隊列
: 0 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 50 : 18.099025 + 0800 GCD
[ 48915 : 7823351 ] 異步執行
+ 主隊列
: 1 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 50 : 18.099132 + 0800 GCD
[ 48915 : 7823351 ] 異步執行
+ 主隊列
: 2 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 50 : 18.099232 + 0800 GCD
[ 48915 : 7823351 ] 異步執行
+ 主隊列
: 3 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
} 2021 - 03 - 24 20 : 50 : 18.099338 + 0800 GCD
[ 48915 : 7823351 ] 異步執行
+ 主隊列
: 4 - < NSThread
: 0x600003530300 > { number
= 1 , name
= main
}
(二)組合區別
① 主線程
區別并發隊列串行隊列主隊列 同步 沒有開啟新線程,串行執行任務 沒有開啟新線程,串行執行任務 死鎖卡住不執行 異步 有開啟新線程,并發執行任務 有開啟新線程(1條),串行執行任務 沒有開啟新線程,串行執行任務
從上邊可看出: 『主線程』 中調用 『主隊列』+『同步執行』 會導致死鎖問題: 這是因為主隊列中追加的同步任務和主線程本身的任務兩者之間相互等待,阻塞了 主隊列,最終造成了主隊列所在的線程(主線程)死鎖問題; 而如果我們在其他線程調用主隊列 + 同步執行,則不會阻塞主隊列,自然也不會造成死鎖問題。最終的結果是:不會開啟新線程,串行執行任務。
② 隊列
不同隊列 + 不同任務組合,以及隊列中嵌套隊列使用的區別:
區別異步執行+并發隊列嵌套同一個并發隊列同步執行+并發隊列嵌套同一個并發隊列異步執行+串行隊列嵌套同一個串行隊列同步執行+串行隊列嵌套同一個串行隊列 同步(sync) 沒有開啟新的線程,串行執行任務 沒有開啟新線程,串行執行任務 死鎖卡住不執行 死鎖卡住不執行 異步(async) 有開啟新線程,并發執行任務 有開啟新線程,并發執行任務 有開啟新線程(1 條),串行執行任務 有開啟新線程(1 條),串行執行任務
除了上邊提到的主線程中調用主隊列 + 同步執行會導致死鎖問題。實際在使用串行隊列的時候,也可能出現阻塞串行隊列所在線程的情況發生,從而造成死鎖問題。這種情況多見于同一個串行隊列的嵌套使用。 下面代碼這樣:在異步執行 + 串行任務的任務中,又嵌套了當前的串行隊列,然后進行同步執行,如下:
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
] ) ; } ) ; } ) ;
執行上面的代碼會導致串行隊列中追加的任務和串行隊列中原有的任務兩者之間相互等待,阻塞了串行隊列,最終造成了串行隊列所在的線程(子線程)死鎖問題。
四、GCD 線程間的通信
在 iOS 開發過程中,我們一般在主線程里邊進行 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
}
可以看到在其他線程中先執行任務,執行完了之后回到主線程執行主線程的相應操作。
五、GCD 其他方法
① GCD 柵欄方法:dispatch_barrier_async
我們有時需要異步執行兩組操作,而且第一組操作執行完之后,才能開始執行第二組操作,這樣就需要一個相當于“柵欄”一樣的一個方法將兩組異步執行的操作組給分割起來,當然這里的操作組里可以包含一個或多個任務,這就需要用到dispatch_barrier_async 方法在兩個操作組間形成柵欄。 dispatch_barrier_async 方法會等待前邊追加到并發隊列中的任務全部執行完畢之后,再將指定的任務追加到該異步隊列中。然后在 dispatch_barrier_async 方法追加的任務執行完畢之后,異步隊列才恢復為一般動作,接著追加任務到該異步隊列并開始執行。具體如下圖所示:
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 執行結果中可以看出:在執行完柵欄前面的操作之后,才執行柵欄操作,最后再執行柵欄后邊的操作。
② GCD 延時執行方法:dispatch_after
我們經常會遇到這樣的需求:在指定時間(例如 3 秒)之后執行某個任務,這個時候,可以用 GCD 的dispatch_after 方法來實現。 需要注意的是:dispatch_after 方法并不是在指定時間之后才開始執行處理,而是在指定時間之后將任務追加到主隊列中。嚴格來說,這個時間并不是絕對準確的,但想要大致延遲執行任務,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 一次性代碼(只執行一次):dispatch_once
在創建單例或者有整個程序運行過程中只執行一次的代碼時,就需要用到 GCD 的 dispatch_once 方法。 使用 dispatch_once 方法能保證某段代碼在程序運行過程中只被執行 1 次,并且即使在多線程的環境下,dispatch_once 也可以保證線程安全。
static dispatch_once_t onceToken
; dispatch_once ( & onceToken
, ^ { } ) ;
④ GCD 快速迭代方法:dispatch_apply
通常我們會用 for 循環遍歷,但是 GCD 給我們提供了快速迭代的方法 dispatch_apply。 dispatch_apply 按照指定的次數將指定的任務追加到指定的隊列中,并等待全部隊列執行結束。 如果是在串行隊列中使用 dispatch_apply,那么就和 for 循環一樣,按順序同步執行。 可以利用并發隊列進行異步執行:比如說遍歷 0~5 這 6 個數字,for 循環的做法是每次取出一個元素,逐個遍歷,dispatch_apply 可以在多個線程中同時(異步)遍歷多個數字。 無論是在串行隊列,還是并發隊列中,dispatch_apply 都會等待全部任務執行完畢,這點就像是同步操作,也像是隊列組中的 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
這是因為在并發隊列中異步執行任務,所以各個任務的執行時間長短不定,最后結束順序也不定。但是 apply—end 一定在最后執行,是因為 dispatch_apply 方法會等待全部任務執行完畢。
⑤ GCD 隊列組:dispatch_group
有時候會有這樣的需求:分別異步執行2個耗時任務,然后當2個耗時任務都執行完畢后再回到主線程執行任務,這時候我們可以用到 GCD 的隊列組。 調用隊列組的 dispatch_group_async 先把任務放到隊列中,然后將隊列放入隊列組中,或者使用隊列組的 dispatch_group_enter、dispatch_group_leave 組合來實現 dispatch_group_async。 調用隊列組的 dispatch_group_notify 回到指定線程執行任務,或者使用 dispatch_group_wait 回到當前線程繼續向下執行(會阻塞當前線程)。 dispatch_group_notify 監聽 group 中任務的完成狀態,當所有的任務都執行完成后,追加任務到 group 中,并執行任務。
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 相關代碼運行輸出結果可以看出: 當所有任務都執行完成之后,才執行 dispatch_group_notify 相關 block 中的任務。 dispatch_group_wait 暫停當前線程(阻塞當前線程),等待指定的 group 中的任務執行完成后,才會往下繼續執行。
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 相關代碼運行輸出結果可以看出: 當所有任務執行完成之后,才執行 dispatch_group_wait 之后的操作。 注意:使用dispatch_group_wait 會阻塞當前線程。 dispatch_group_enter、dispatch_group_leave dispatch_group_enter 標志著一個任務追加到 group,執行一次,相當于 group 中未執行完畢任務數 +1; dispatch_group_leave 標志著一個任務離開了 group,執行一次,相當于 group 中未執行完畢任務數 -1; 當 group 中未執行完畢任務數為0的時候,才會使 dispatch_group_wait 解除阻塞,以及執行追加到 dispatch_group_notify 中的任務。
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 相關代碼運行結果中可以看出:當所有任務執行完成之后,才執行 dispatch_group_notify 中的任務,這里的dispatch_group_enter、dispatch_group_leave 組合,其實也等同于dispatch_group_async。
⑥ GCD 信號量:dispatch_semaphore
GCD 中的信號量是指 Dispatch Semaphore,是持有計數的信號,類似于過高速路收費站的欄桿,可以通過時,打開欄桿,不可以通過時,關閉欄桿。 在 Dispatch Semaphore 中,使用計數來完成這個功能,計數小于 0 時等待,不可通過。計數為 0 或大于 0 時,計數減 1 且不等待,可通過。 Dispatch Semaphore 提供了三個方法: dispatch_semaphore_create:創建一個 Semaphore 并初始化信號的總量; dispatch_semaphore_signal:發送一個信號,讓信號總量加 1; dispatch_semaphore_wait:可以使總信號量減 1,信號總量小于 0 時就會一直等待(阻塞所在線程),否則就可以正常執行。 注意:信號量的使用前提是,想清楚你需要處理哪個線程等待(阻塞),又要哪個線程繼續執行,然后使用信號量。 Dispatch Semaphore 在實際開發中主要用于: 保持線程同步,將異步執行任務轉換為同步執行任務; 保證線程安全,為線程加鎖。 Dispatch Semaphore 線程同步 在開發中,可能會遇到這樣的需求:異步執行耗時任務,并使用異步執行的結果進行一些額外的操作。換句話說,相當于,將將異步執行任務轉換為同步執行任務。 比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法,通過引入信號量的方式,等待異步執行任務結果,獲取到 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 實現線程同步,將異步執行任務轉換為同步執行任務。
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 實現線程同步的代碼可以看到:semaphore–end 是在執行完 number = 100; 之后才打印的,而且輸出結果 number 為 100。這是因為異步執行不會做任何等待,可以繼續執行任務。執行順如下: 異步執行 將 任務 1 追加到隊列之后,不做等待,接著執行 dispatch_semaphore_wait 方法,semaphore 減 1,此時 semaphore == -1,當前線程進入等待狀態。 然后,異步任務 1 開始執行。任務 1 執行到 dispatch_semaphore_signal 之后,總信號量加 1,此時 semaphore == 0,正在被阻塞的線程(主線程)恢復繼續執行。 最后打印 semaphore—end,number = 100。 這樣就實現了線程同步,將異步執行任務轉換為同步執行任務。 Dispatch Semaphore 線程安全和線程同步(為線程加鎖) 線程安全:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。 若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。 線程同步:可理解為線程 A 和 線程 B 一塊配合,A 執行到一定程度時要依靠線程 B 的某個結果,于是停下來,示意 B 運行;B 依言執行,再將結果給 A;A 再繼續操作。
⑦ dispatch_source
dispatch_source 是基礎數據類型,用于協調特定底層系統事件的處理。 dispatch_source 替代了異步回調函數,來處理系統相關的事件,當配置一個dispatch 時,你需要指定監測的事件、dispatch queue、以及處理事件的代碼(block或函數)。當事件發生時,dispatch source會提交你的block或函數到指定的queue去執行; 使用 dispatch_source 而不使用 dispatch_async 的主要原因就是利用聯結的優勢; 聯結就是:在任一線程上調用它的的一個函數 dispatch_ source_ merge_ data 后,會執行 dispatch_source 事先定義好的句柄(可以把句柄簡單理解為一個block),這個過程叫 Custom event 用戶事件,是 dispatch source 支持處理的一種事件,即為調用 dispatch_source_merge_data 函數來向自己發出的信號。 句柄是一種指向指針的指針,它指向的就是一個類或者結構,和系統有很密切的關系,HINSTANCE (實例句柄)、HBITMAP (位圖句柄) 、HDC (設備表述句柄)、 HICON(圖標句柄)等,這當中還有一個通用的句柄,就是HANDLE; 實例句柄 HINSTANCE 位圖句柄 HBITMAP 設備表句柄 HDC 圖標句柄 HICON dispatch_source 的CPU負荷非常小,盡量不占用資源 。 dispatch_source 的創建:
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端口發送 DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收 DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 內存壓力 (注:iOS8后可用) DISPATCH_SOURCE_TYPE_PROC 進程監聽,如進程的退出、創建一個或更多的子線程、進程收到UNIX信號 DISPATCH_SOURCE_TYPE_READ IO操作,如對文件的操作、socket操作的讀響應 DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信號時響應 DISPATCH_SOURCE_TYPE_TIMER 定時器 DISPATCH_SOURCE_TYPE_VNODE 文件狀態監聽,文件被刪除、移動、重命名 DISPATCH_SOURCE_TYPE_WRITE IO操作,如對文件的操作、socket操作的寫響應
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,而是直接和底層內核交互,準確性更高,所以經常用于驗證碼倒計時。
- ( 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
) ; }
總結
以上是生活随笔 為你收集整理的iOS之深入分析GCD的函数与队列以及多种组合使用 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。