ios多線程操作(六)—— GCD全域隊列與主隊列,iosgcd
GCD預設已經提供了全域的並發隊列供整個應用使用,所以可以不用手動建立。 建立全域隊列的函數為
dispatch_queue_t q = dispatch_get_global_queue(long identifier, unsigned long flags)
參數類型為:long identifier:ios 8.0 告訴隊列執行任務的“服務品質 quality of service”,系統提供的參數有:
QOS_CLASS_USER_INTERACTIVE 0x21,
使用者互動(希望儘快完成,使用者對結果很期望,不要放太耗時操作)
QOS_CLASS_USER_INITIATED 0x19, 使用者期望(不要放太耗時操作)
QOS_CLASS_DEFAULT 0x15, 預設(不是給程式員使用的,用來重設對列使用的)
QOS_CLASS_UTILITY 0x11, 工具 + 生產力(耗時操作,可以使用這個選項)
QOS_CLASS_BACKGROUND 0x09, 後台
QOS_CLASS_UNSPECIFIED 0x00, 未指定
iOS 7.0 之前 優先順序
DISPATCH_QUEUE_PRIORITY_HIGH 2 高優先順序
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 預設優先順序
DISPATCH_QUEUE_PRIORITY_LOW (-2) 低優先順序
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 後台優先順序
BACKGROUND表示使用者不需要知道任務什麼時候完成,如果選擇這個選項速度慢得令人髮指,非常不利於調試!對於優先順序推薦不要搞得太負責,就用最簡單,以免發生優先順序反轉。 unsigned long flags:蘋果官方文檔是這樣解釋的: Flags that are reserved for future use。標記是為了未來使用保留的!所以這個參數應該永遠指定為0 如果做ios8.0與ios7.0的適配,可以這樣建立全域隊列:
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
試著用全域隊列來做一下非同步作業,看看是否為並發執行,如下代碼
dispatch_queue_t q = dispatch_get_global_queue(0, 0); // 2. 非同步執行 for (int i = 0; i < 10; ++i) { dispatch_async(q, ^{ NSLog(@"%@ %d", [NSThread currentThread], i); }); } NSLog(@"come here");
運行結果如下:
驗證正確 現在我們來類比一次操作,使用者需要先登入之後才能執行兩個下載操作,登入和下載都是個耗時操作,所以我們需要在後台開啟一條子線程,根據現實情況兩個下載操作需要並發執行,那如何?該需要?藉此,我們引出同步函數的作用!有如下代碼
// 1. 隊列 dispatch_queue_t q = dispatch_get_global_queue(0, 0); // 2. 任務 void (^task)() = ^ { dispatch_sync(q, ^{ NSLog(@"Login %@", [NSThread currentThread]); }); dispatch_async(q, ^{ NSLog(@"Download A %@", [NSThread currentThread]); }); dispatch_async(q, ^{ NSLog(@"Download B %@", [NSThread currentThread]); }); }; // 3. 非同步執行 task dispatch_async(q, task);
執行結果如下:
再來一次:
結果無論如何都是在子線程上執行,而且登入永遠都在最前面,至於哪條線程先下載我們不得而知,以上結果只是簡單展示,如果開多下載基本書就可以展示更好的效果,這裡就不示範了。 在MRC環境下,全域隊列不需要釋放記憶體。
當然,GCD本身內建了一種特殊的串列隊列,所有放在主隊列中的任務都會在主線程上執行。 程式一啟動,主線程就已經存在,主隊列也同時就存在了,所以主隊列不需要建立,只需要擷取,如下
dispatch_queue_t q = dispatch_get_main_queue();
試著在主隊列中執行非同步任務:
dispatch_queue_t q = dispatch_get_main_queue(); // 2. 非同步執行 for (int i = 0; i < 10; ++i) { dispatch_async(q, ^{ NSLog(@"%@ - %d", [NSThread currentThread], i); }); } NSLog(@"線程休眠1s"); [NSThread sleepForTimeInterval:1.0]; NSLog(@"come here - %@",[NSThread currentThread]);
執行的結果如下:
據此可得,主隊列中,即使有非同步任務,也會依次在主線程上執行,該例子中,主線程上的代碼還未執行完,所以非同步任務會等主線程上的任務執行完再執行。 那麼如果在主線程上執行同步任務會如何呢?如下代碼:
// 1.隊列 dispatch_queue_tq = dispatch_get_main_queue(); NSLog(@"now I'm here"); // 2. 同步執行 dispatch_sync(q, ^{ NSLog(@"%@", [NSThreadcurrentThread]); }); NSLog(@"come here");
此時列印台列印出來的結果如下程式只執行到第二句代碼就無法執行下去了,為什麼會這樣呢?答案就是主線程被阻塞了,也就是死結了!!!如果這時候螢幕上有與使用者互動的UI,此時也會失去互動功能,像死機了一樣!!! 我們都應該清楚,同步任務有一個特性,只要一添加到隊列中就要馬上執行,主隊列中永遠就只要一條線程——主線程,此時主線程在等待著主隊列調度同步任務,而主隊列發現主線程上還有任務未執行完,就不會讓同步任務添加到主線程上,由此就造成了互相等待(主隊列在等待主線程執行完已有的任務,而主線程又在等待主隊列調度同步任務!),此時也就是所謂的死結了!!! 那麼如果有在主隊列執行同步任務的需要呢?我們可以用一個非同步任務包裹一個同步任務添加到主隊列中!如下:
// 全域隊列 dispatch_queue_t q = dispatch_get_global_queue(0, 0); // 任務 void (^task)() = ^ { NSLog(@"%@", [NSThread currentThread]); // 主隊列上執行同步任務 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"come here %@", [NSThread currentThread]); }); NSLog(@"hahaha %@", [NSThread currentThread]); }; // 非同步執行任務 dispatch_async(q, task); NSLog(@"now i'm here - %@",[NSThread currentThread]);
執行結果如下:
主線程沒有被阻塞!!!"now i'm here“出現的位置不確定,但總會先於主隊列中的同步函數!主隊列的同步任務被添加到全域隊列的非同步任務中,全域隊列會先讓主線程上的任務先執行完再執行同步任務!