標籤:
iOS有三種多線程編程的技術,分別是:(一)NSThread (二)Cocoa NSOperation(三)GCD(全稱:Grand Central Dispatch)這三種編程方式從上到下,抽象度層次是從低到高的,抽象度越高的使用越簡單,也是Apple最推薦使用的。
1 。非同步呼叫和並發:
非同步呼叫的實現中往往採用並發機制,然而並不是所有非同步都是並發機制,也有可能是其他機制,比如一些依靠中斷進行的操作。
GCD: 以最佳化的應用程式支援多核心處理器和其他的對稱式多處理系統的系統。這建立在任務並存執行的線程池模式的基礎上的。它首次發布在Mac OS X 10.6 ,iOS 4及以上也可用。
GCD的工作原理是:讓程式平行排隊的特定任務,根據可用的處理資源,安排他們在任何可用的處理器核心上執行任務。一個任務可以是一個函數(function)或者是一個block。 GCD的底層依然是用線程實現,不過這樣可以讓程式員不用關注實現的細節。
GCD中的FIFO隊列稱為dispatch queue。
1.GCD的一個重要概念是隊列(dispatch queue),它的核心理念:將長期啟動並執行任務拆分成多個工作單元,並將這些單元添加到dispath queue中,系統會為我們管理這些dispath queue,為我們在多個線程上執行工作單元,我們不需要直接啟動和管理後台線程。
2.系統提供了許多預定義的dispath queue,包括可以保證始終在主線程上執行工作的dispath queue。也可以建立自己的dispath queue,而且可以建立任意多個。GCD的dispath queue嚴格遵循FIFO(先進先出)原則,添加到dispath queue的工作單元將始終按照加入dispath queue的順序啟動。
3.dispatch queue按先進先出的順序,串列或並發地執行任務
1> serial dispatch queue一次只能執行一個任務, 當前任務完成才開始出列並啟動下一個任務
2> concurrent dispatch queue則儘可能多地啟動任務並發執行
2 。三種隊列及管理:
串列隊 Serial 又稱為private dispatch queues,同時只執行一個任務。Serial queue通常用於同步訪問特定的資源或資料。當你建立多個Serial queue時,雖然它們各自是同步執行的,但Serial queue與Serial queue之間是並發執行的。1> 串列queue每次只能執行一個任務。你可以使用串列queue來替代鎖,保護共用資源 或可變的資料結構。和鎖不一樣的是,串列queue確保任務按可預測的順序執行。而且只要你非同步地提交任務到串列queue,就永遠不會產生死結
2> 你必須顯式地建立和管理所有你使用的串列queue,應用可以建立任意數量的串列queue,但不要為了同時執行更多任務而建立更多的串列queue。如果你需要並發地執行大量任務,應該把任務提交到全域並發queue
3> 利用dispatch_queue_create函數建立串列queue,兩個參數分別是queue名和一組queue屬性
運行時獲得公用隊 Concurrent又稱為global dispatch queue,
系統給每一個應用程式提供了三個,
這三個並發調度隊列是全域的,它們只有優先順序的不同。可以並發地執行多個任務,但是執行完成的順序是隨機的。
GCD提供了函數讓應用訪問幾個公用dispatch queue:
1> 使用dispatch_get_current_queue函數作為調試用途,或者測試當前queue的標識。在block對象中調用這個函數會返回block提交到的queue(這個時候queue應該正在執行中)。在block對象之外調用這個函數會返回應用的預設並發queue。
2> 使用dispath_get_global_queue可以擷取去得到concurrent dispatch queues隊列,如下:
let globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一個參數用於指定優先順序,分別使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW兩個常量來擷取高和低優先順序的兩個queue;第二個參數目前未使用到,預設0即可
Main dispatch queue它是全域可用的serial queue,它是在應用程式主線程上執行任務的。1> 使用dispatch_get_main_queue函數獲得應用主線程關聯的串列dispatch queue,添加到這個queue的任務由主線程序列化執行
// 非同步下載圖片 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"]; UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]; // 回到主線程顯示圖片 dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); });
3. 建立隊列:
let queue = dispatch_queue_create("gcdtest.rongfzh.yc", nil) //串列let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_SERIAL) //串列let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT) //並行
4. 新增工作入隊要執行一個任務,你需要將它添加到一個適當的dispatch queue,你可以單個或按組來添加,也可以同步或非同步地執行一個任務,也。一旦進入到queue,queue會負責儘快地執行你的任務。一般可以用一個block來封裝任務內容。
1.添加單個任務到queue
1> 非同步新增工作
你可以非同步或同步地添加一個任務到Queue,儘可能地使用dispatch_async或dispatch_async_f函數非同步地調度任務。因為新增工作到Queue中時,無法確定這些代碼什麼時候能夠執行(GCD會自動根據任務在多核處理器上分配資源,最佳化程式)。因此非同步地添加block或函數,可以讓你立即調度這些代碼的執行,然後調用線程可以繼續去做其它事情。特別是應用主線程一定要非同步地 dispatch 任務,這樣才能及時地響應使用者事件
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { // 耗時的操作 dispatch_async(dispatch_get_main_queue(), { // 更新介面 }) })
2> 同步新增工作
少數時候你可能希望同步地調度任務,以避免競爭條件或其它同步錯誤。 使用dispatch_sync和dispatch_sync_f函數同步地新增工作到Queue,這兩個函數會阻塞當前調用線程,直到相應任務完成執行。注意:絕對不要在任務中調用 dispatch_sync或dispatch_sync_f函數,並同步調度新任務到當前正在執行的 queue。對於串列queue這一點特別重要,因為這樣做肯定會導致死結;而並發queue也應該避免這樣做。
// 調用前,查看下當前線程 NSLog("當前調用線程:%@", NSThread.currentThread()) // 建立一個串列queue let queue = dispatch_queue_create("cn.itcast.queue", nil) dispatch_async(queue, { NSLog("開啟了一個非同步任務,當前線程:%@", NSThread.currentThread()) } ) dispatch_sync(queue, { NSLog("開啟了一個同步任務,當前線程:%@", NSThread.currentThread()) } )
列印結果:
2015-05-09 00:49:27.539 ImageLoaderExample[2122:81150] 當前調用線程:<NSThread: 0x7fcbdad27890>{number = 1, name = main}
2015-05-09 00:49:27.541 ImageLoaderExample[2122:81222] 開啟了一個非同步任務,當前線程:<NSThread: 0x7fcbdae66a30>{number = 2, name = (null)}
2015-05-09 00:49:27.541 ImageLoaderExample[2122:81150] 開啟了一個同步任務,當前線程:<NSThread: 0x7fcbdad27890>{number = 1, name = main}
5. 暫停和繼續queue
我們可以使用dispatch_suspend函數暫停一個queue以阻止它執行block對象;使用dispatch_resume函數繼續dispatch queue。調用dispatch_suspend會增加queue的引用計數,調用dispatch_resume則減少queue的引用計數。當引用計數大於0時,queue就保持掛起狀態。因此你必須對應地調用suspend和resume函數。掛起和繼續是非同步,而且只在執行block之間(比如在執行一個新的block之前或之後)生效。掛起一個queue不會導致正在執行的block停止。
6. dispatch_group_async的使用
dispatch_group_async可以實現監聽一組任務是否完成,完成後得到通知執行其他的操作。這個方法很有用,比如你執行三個下載任務,當三個任務都下載完成後你才通知介面說完成的了。下面是一段例子代碼:
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) let group = dispatch_group_create() dispatch_group_async(group, queue, { NSThread.sleepForTimeInterval(1) NSLog("group1"); }) dispatch_group_async(group, queue, { NSThread.sleepForTimeInterval(2) NSLog("group2"); }) dispatch_group_async(group, queue, { NSThread.sleepForTimeInterval(3) NSLog("group3"); }) dispatch_group_async(group, queue, { NSThread.sleepForTimeInterval(3) NSLog("group34"); }) dispatch_group_notify(group, dispatch_get_main_queue(), { NSLog("updateUi"); });dispatch_group_async是非同步方法,運行後可以看到列印結果:
2015-05-08 23:23:40.344 ImageLoaderExample[1504:48633] group1
2015-05-08 23:23:41.340 ImageLoaderExample[1504:48631] group2
2015-05-08 23:23:42.340 ImageLoaderExample[1504:48638] group34
2015-05-08 23:23:42.340 ImageLoaderExample[1504:48634] group3
2015-05-08 23:23:42.341 ImageLoaderExample[1504:48547] updateUi
每個一秒列印一個,當第三個任務執行後,upadteUi被列印。
// 根據url擷取UIImage - (UIImage *)imageWithURLString:(NSString *)urlString { NSURL *url = [NSURL URLWithString:urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; // 這裡並沒有自動釋放UIImage對象 return [[UIImage alloc] initWithData:data]; } - (void)downloadImages { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 非同步下載圖片 dispatch_async(queue, ^{ // 建立一個組 dispatch_group_t group = dispatch_group_create(); __block UIImage *image1 = nil; __block UIImage *image2 = nil; // 關聯一個任務到group dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 下載第一張圖片 NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"; image1 = [self imageWithURLString:url1]; }); // 關聯一個任務到group dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 下載第一張圖片 NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg"; image2 = [self imageWithURLString:url2]; }); // 等待組中的任務執行完畢,回到主線程執行block回調 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ self.imageView1.image = image1; self.imageView2.image = image2; // 千萬不要在非同步線程中自動釋放UIImage,因為當非同步線程結束,非同步線程的自動釋放池也會被銷毀,那麼UIImage也會被銷毀 // 在這裡釋放圖片資源 [image1 release]; [image2 release]; }); // 釋放group dispatch_release(group); }); }
7. dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任務執行結束後它才執行,而且它後面的任務等它執行完成之後才會執行
NSLog("begin"); let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT) dispatch_async(queue, { NSThread.sleepForTimeInterval(2) NSLog("dispatch_async1"); } ); dispatch_async(queue, { NSThread.sleepForTimeInterval(4) NSLog("dispatch_async2"); } ); dispatch_barrier_async(queue, { NSLog("dispatch_barrier_async"); NSThread.sleepForTimeInterval(4) } ); dispatch_async(queue, { NSThread.sleepForTimeInterval(1) NSLog("dispatch_async3"); } ); dispatch_async(queue, { NSThread.sleepForTimeInterval(1) NSLog("dispatch_async4"); } );列印結果:
2015-05-08 23:39:11.729 ImageLoaderExample[1635:55195] begin
2015-05-08 23:39:13.731 ImageLoaderExample[1635:55268] dispatch_async1
2015-05-08 23:39:15.730 ImageLoaderExample[1635:55267] dispatch_async2
2015-05-08 23:39:15.731 ImageLoaderExample[1635:55267] dispatch_barrier_async
2015-05-08 23:39:20.742 ImageLoaderExample[1635:55268] dispatch_async4
2015-05-08 23:39:20.742 ImageLoaderExample[1635:55267] dispatch_async3
8、dispatch_apply 並發地執行迴圈迭代
如果你使用迴圈執行固定次數的迭代, 並發dispatch queue可能會提高效能。可以指定串列抑或並行
和普通for迴圈一樣,dispatch_apply和dispatch_apply_f函數也是在所有迭代完成之後才會返回,因此這兩個函數會阻塞當前線程。如果你傳遞的參數是串列queue,而且正是執行當前代碼的queue,就會產生死結。
let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT)dispatch_apply(30, queue, {print("|*\($0)*|")})
列印結果:
|*0*||*4*||*5*|||||****6312****||||*|7||***|8|9**|*1|*|01|*1|*|**1|||12**3*15*||*1|*16|4*|**|1|||**|711**98|2**|0||**||2|*|1*2**2223|*4*|*||||**|2|2*56*2**2|7|8*||**|29*|
let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_SERIAL)dispatch_apply(30, queue, {print("|*\($0)*|")})
列印結果:
|*0*||*1*||*2*||*3*||*4*||*5*||*6*||*7*||*8*||*9*||*10*||*11*||*12*||*13*||*14*||*15*||*16*||*17*||*18*||*19*||*20*||*21*||*22*||*23*||*24*||*25*||*26*||*27*||*28*||*29*|
IOS並發編程