經過上一個章節,咱們已經瞭解了一些多線程的基本知識。本章就來說解在平時開發中,最爲經常使用的
GCD
的一些知識git
系列文章傳送門:程序員
☞ iOS底層學習 - 多線程之基礎原理篇github
GCD
全程爲Grand Central Dispatch
,由C語言
實現,是蘋果爲多核的並行運算提出的解決方案,CGD
會自動利用更多的CPU內核,自動管理線程的生命週期,程序員只須要告訴GCD
須要執行的任務,無需編寫任何管理線程的代碼。GCD
也是iOS使用頻率最高的多線程技術。swift
GCD
是蘋果公司爲多核的並行運算提出的解決方案GCD
會自動利用更多的CPU內核(好比雙核、四核)GCD
會自動管理線程的生命週期(建立線程、調度任務、銷燬線程)GCD
想要執行什麼任務,不須要編寫任何線程管理代碼GCD
的使用API
較爲簡單,主要分爲同步和異步執行。在上一章咱們對概念已經有所瞭解,就不作贅述了。markdown
同步執行API多線程
//queue:隊列 block:任務 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); 複製代碼
異步執行API併發
//queue:隊列 block:任務 dispatch_async(dispatch_queue_t queue, dispatch_block_t block); 複製代碼
咱們發如今使用GCD
時,須要傳入兩個參數。分別是dispatch_queue_t
表明添加的隊列類型,dispatch_block_t
爲須要執行的任務,下面咱們來看一下不一樣參數的運行效果。app
dispatch_block_t
任務經過查看其定義可得,該參數其實就是一個沒有參數的block
回調,用來執行任務的。異步
typedef void (^dispatch_block_t)(void); 複製代碼
dispatch_queue_t
隊列dispatch_queue_t
參數表示隊列的類型。根據上一章節咱們知道,隊列(FIFO
)在iOS中主要一下分爲4種:async
main_queue
):由系統建立的串行隊列。
dispatch_get_main_queue()
global_queue
):由系統建立的併發隊列
dispatch_get_global_queue(long identifier, unsigned long flags);
Serial Dispatch Queue
):自定義的串行隊列
dispatch_queue_create(@"隊列名",DISPATCH_QUEUE_SERIAL)
Concurrent Dispatch Queue
):自定義的併發隊列
dispatch_queue_create(@"隊列名",DISPATCH_QUEUE_CONCURRENT);
其中,全局隊列根據入參的不一樣,會獲取到不一樣的隊列,在平常的開發中,咱們通常入參0
,來獲取到主併發隊列。
自定義的隊列都是調用dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr)
方法來進行建立,兩個參數分別爲自定義隊列名稱
和隊列類型
。其中串行隊列DISPATCH_QUEUE_SERIAL
爲宏定義的NULL
,因此傳NULL
也表示爲串行隊列。
如今,咱們來看這2種執行模式,3種隊列是相互搭配是什麼效果
相關代碼以下:
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"任務1:%@",[NSThread currentThread]); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{ NSLog(@"任務2",[NSThread currentThread]); }); NSLog(@"任務3:%@",[NSThread currentThread]); } 複製代碼
運行結果
打印出任務1後,程序死鎖崩潰
爲何會形成以上的結果呢?
首先分析一下代碼:主線程執行完任務1
後,在主隊列dispatch_get_main_queue()
中同步執行(dispatch_sync)
任務2,而後執行任務3。
隊列的特色是FIFO
,主隊列中已經存在任務viewDidLoad
,往主隊列加入任務2,就須要執行完viewDidLoad
才能執行任務2。可是想要執行完viewDidLoad
又必須先執行viewDidLoad
內的任務2和任務3。這就形成了死鎖。
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"任務1:%@",[NSThread currentThread]); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{ NSLog(@"任務2:%@",[NSThread currentThread]); }); NSLog(@"任務3:%@",[NSThread currentThread]); } 複製代碼
運行結果
咱們能夠看到當獲取到主隊列後,使用異步執行的方式,不會形成程序的崩潰。
由於主線程執行任務1以後,須要異步(dispatch_async)
執行任務2;而dispatch_async
不要求立馬在當前線程同步執行任務,也就是不會堵塞;因此主線程接着執行任務3,最後異步執行任務2。
- (void)viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_sync(queue, ^{ for (int i = 0; i<3; i++) { NSLog(@"任務一:%@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (int i = 0; i<3; i++) { NSLog(@"任務二:%@",[NSThread currentThread]); } }); } 複製代碼
運行結果
咱們在全局隊列中同步執行任務1和任務2,經過打印能夠看出,同步執行是 按順序執行任務,執行完任務1再執行任務2。而且打印當前線程,同步執行是在 主線程中執行任務,沒有開啓新線程。且因爲隊列是併發的,並不會阻塞主線程。- (void)viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ for (int i = 0; i<3; i++) { NSLog(@"任務一:%@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (int i = 0; i<3; i++) { NSLog(@"任務二:%@",[NSThread currentThread]); } }); } 複製代碼
運行結果:
能夠看出任務1和任務2交錯執行,並不是同步執行那樣執行完任務1再執行任務2。並且經過線程編號能夠看出,的確開啓了新的線程。說明異步執行具有開啓新線程的能力。
可是經過上面異步+主隊列
的打印結果咱們能夠發現,在主隊列時,異步執行也並無開啓新的線程,而仍然是同一個主線程。說明若是在主隊列中異步執行,是不會開啓新線程的
緣由是由於主隊列是串行隊列,必須執行完一個任務再執行另外一個任務,異步執行只是沒有形成堵塞,可是在主隊列仍是是要一步步走的。
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"任務1"); dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ NSLog(@"任務2"); dispatch_sync(queue, ^{ NSLog(@"任務3"); }); NSLog(@"任務4"); }); NSLog(@"任務5"); } 複製代碼
運行結果
任務1,任務5,任務2,而後程序崩潰
這裏形成死鎖崩潰的緣由和上面同步+串行隊列
的緣由同樣。因爲主隊列是串行的,因此代碼必然是從上到下依次執行的。
打印完任務1後,執行dispatch_async
爲異步,不會阻塞線程,可是會加入自定義的串行隊列中,因此會執行任務5,接着執行異步代碼塊中邏輯,打印任務2,接着執行dispatch_sync
代碼塊,可是因爲其是同步,串行隊列中已經有了dispatch_async
任務未執行完畢,此時dispatch_sync
阻塞當前線程等待任務3執行,形成了相互等待,因此就死鎖崩潰了
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"任務1"); dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ NSLog(@"任務2"); dispatch_sync(queue2, ^{ NSLog(@"任務3"); }); NSLog(@"任務4"); }); NSLog(@"任務5"); } 複製代碼
運行結果
任務1,任務5,任務2,任務3,任務4
經過運行咱們能夠發現,當嵌套執行時,同步異步在不一樣的串行隊列執行,並不會形成死鎖崩潰。而是按照串行的順序,執行代碼。這事由於dispatch_sync
阻塞的並非當前的線程,而是其餘的線程,因此不會形成死鎖等待。
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"任務1"); dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"任務2"); dispatch_sync(queue, ^{ NSLog(@"任務3"); }); NSLog(@"任務4"); }); NSLog(@"任務5"); } 複製代碼
運行結果:
任務1,任務5,任務2,任務3,任務4
主線程執行任務1以後,須要異步dispatch_async
執行任務2;因此先執行主線程的任務5,而後執行任務2;接着須要在併發隊列中同步dispatch_sync
執行任務3,因此會形成阻塞,等待其執行完成,而後執行併發隊列中的任務4。
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"任務1"); dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"任務2"); dispatch_sync(queue2, ^{ NSLog(@"任務3"); }); NSLog(@"任務4"); }); NSLog(@"任務5"); } 複製代碼
運行結果:
任務1,任務5,任務2,任務3,任務4
這個代碼的分析和不一樣串行隊列嵌套
的相似,建立了新的隊列,因此是不會形成當前隊列的阻塞的。