iOS 多執行緒 設定執行順序
//
// ViewController.m
// GCDDownload
//
// Created by ql on 2018/4/4.
// Copyright © 2018年 carystaloptech. All rights reserved.
//
#import "ViewController.h"
@interfaceViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
[selfDownloadFilesWithGCD];
}
//經常有這樣的需求:
//1,有m個網路請求。
//2,先併發執行其中n幾個。
//3,待這n個請求完成之後再執行第n+1個請求。
//4然後等 第n+1個請求完成後再併發執行剩下的m-(n+1)個請求。
- (void)DownloadFilesWithGCD{
dispatch_queue_t queue = dispatch_queue_create("downQueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"gcd任務1");
sleep(3);
});
dispatch_async(queue, ^{
NSLog(@"gcd任務2");
sleep
});
dispatch_async(queue, ^{
NSLog(@"gcd任務3");
sleep(3);
});
//void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
//這個函式可以設定同步執行的block,它會等到在它加入佇列之前的block執行完畢後,才開始執行。在它之後加入佇列的block,則等到這個block執行完畢後才開始執行。
dispatch_barrier_async(queue, ^{
NSLog(@"gcd處理任務 1 2 3");
});
dispatch_async(queue, ^{
NSLog(@"gcd任務4");
[selfdownloadFilesWithNSOperation];
});
dispatch_async(queue, ^{
NSLog(@"gcd任務5");
sleep(3);
});
/*
Dispatch Queues的生成可以有這幾種方式:
1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一個序列佇列,佇列中的block按照先進先出(FIFO)的順序去執行,實際上為單執行緒執行。第一個引數是佇列的名稱,在除錯程式時會非常有用,所有儘量不要重名了。
2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一個併發執行佇列,block被分發到多個執行緒去執行
3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //獲得程式程序預設產生的併發佇列,可設定優先順序來選擇高、中、低三個優先順序佇列。由於是系統預設生成的,所以無法呼叫dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。需要注意的是,三個佇列不代表三個執行緒,可能會有更多的執行緒。併發佇列可以根據實際情況來自動產生合理的執行緒數,也可理解為dispatch佇列實現了一個執行緒池的管理,對於程式邏輯是透明的。
官網文件解釋說共有三個併發佇列,但實際還有一個更低優先順序的佇列,設定優先順序為DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode除錯時可以觀察到正在使用的各個dispatch佇列。
4. dispatch_queue_t queue = dispatch_get_main_queue(); //獲得主執行緒的dispatch佇列,實際是一個序列佇列。同樣無法控制主執行緒dispatch佇列的執行繼續或中斷。
接下來我們可以使用dispatch_async或dispatch_sync函式來載入需要執行的block。
dispatch_async(queue, ^{
//block具體程式碼
}); //非同步執行block,函式立即返回
dispatch_sync(queue, ^{
//block具體程式碼
}); //同步執行block,函式不返回,一直等到block執行完畢。編譯器會根據實際情況優化程式碼,所以有時候你會發現block其實還在當前執行緒上執行,並沒用產生新執行緒。
實際程式設計經驗告訴我們,儘可能避免使用dispatch_sync,巢狀使用時還容易引起程式死鎖。
如果queue1是一個序列佇列的話,這段程式碼立即產生死鎖:
dispatch_sync(queue1, ^{
dispatch_sync(queue1, ^{
......
});
......
});
那實際運用中,一般可以用dispatch這樣來寫,常見的網路請求資料多執行緒執行模型:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//子執行緒中開始網路請求資料
//更新資料模型
dispatch_sync(dispatch_get_main_queue(), ^{
//在主執行緒中更新UI程式碼
});
});
程式的後臺執行和UI更新程式碼緊湊,程式碼邏輯一目瞭然。
dispatch佇列是執行緒安全的,可以利用序列佇列實現鎖的功能。比如多執行緒寫同一資料庫,需要保持寫入的順序和每次寫入的完整性,簡單地利用序列佇列即可實現:
dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb", DISPATCH_QUEUE_SERIAL);
- (void)writeDB:(NSData *)data
{
dispatch_async(queue1, ^{
//write database
});
}
下一次呼叫writeDB:必須等到上次呼叫完成後才能進行,保證writeDB:方法是執行緒安全的。
dispatch佇列還實現其它一些常用函式,包括:
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重複執行block,需要注意的是這個方法是同步返回,也就是說等到所有block執行完畢才返回,如需非同步返回則巢狀在dispatch_async中來使用。多個block的執行是否併發或序列執行也依賴queue的是否併發或序列。
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //這個函式可以設定同步執行的block,它會等到在它加入佇列之前的block執行完畢後,才開始執行。在它之後加入佇列的block,則等到這個block執行完畢後才開始執行。
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函式
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延遲執行block
最後再來看看dispatch佇列的一個很有特色的函式:
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
它會把需要執行的任務物件指定到不同的佇列中去處理,這個任務物件可以是dispatch佇列,也可以是dispatch源(以後博文會介紹)。而且這個過程可以是動態的,可以實現佇列的動態排程管理等等。比如說有兩個佇列dispatchA和dispatchB,這時把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB);
那麼dispatchA上還未執行的block會在dispatchB上執行。這時如果暫停dispatchA執行:
dispatch_suspend(dispatchA);
則只會暫停dispatchA上原來的block的執行,dispatchB的block則不受影響。而如果暫停dispatchB的執行,則會暫停dispatchA的執行。
*/
// 參考:http://www.cnblogs.com/sunfrog/p/3305614.html
}
- (void)downloadFilesWithNSOperation{
NSOperationQueue *queue = [[NSOperationQueuealloc]init];
NSBlockOperation *op1 = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"op任務1");
sleep(5);
}];
NSBlockOperation *op2 = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"op任務2");
sleep(2);
}];
NSBlockOperation *op3 = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"op任務3");
sleep(3);
}];
[op3 addDependency:op1]; // 新增依賴關係 op3 在 op1 完成以後執行
[op3 addDependency:op2]; // 新增依賴關係 op3 在 op2 完成以後執行
//設定佇列中操作同時執行的最大數目,也就是說當前佇列中呢最多由幾個執行緒在同時執行,一般情況下允許最大的併發數2或者3
[queue setMaxConcurrentOperationCount:3];
[queue addOperations:@[op1,op2,op3]waitUntilFinished:YES];
NSBlockOperation *op4 = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"op任務4");
sleep(3);
}];
NSBlockOperation *op5 = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"op任務5");
}];
NSOperationQueue *queue1 = [[NSOperationQueuealloc]init];
[op4 addDependency:op3]; // 新增依賴關係 op4 在 op3 完成以後執行
[op5 addDependency:op3]; // 新增依賴關係 op5 在 op3 完成以後執行
// - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
//批量加入執行operation,wait標誌是否當前執行緒等待所有operation結束後,才返回
[queue1 addOperations:@[op4,op5]waitUntilFinished:YES];
/*
NSOperation 常用方法:
- (void)start; //在當前任務狀態和依賴關係合適的情況下,啟動NSOperation的main方法任務,需要注意預設實現只是在當前執行緒執行。如果需要併發執行,子類必須重寫這個方法,並且使 - (BOOL)isConcurrent 方法返回YES
- (void)main; //定義NSOperation的主要任務程式碼
- (BOOL)isCancelled; //當前任務狀態是否已標記為取消
- (void)cancel; //取消當前NSOperation任務,實質是標記isCancelled狀態
- (BOOL)isExecuting; //NSOperation任務是否在執行
- (BOOL)isFinished; //NSOperation任務是否已結束
NSOperation其它常用方法,包括依賴關係:
- (BOOL)isReady; //是否能準備執行,這個值和任務的依賴關係相關
- (void)addDependency:(NSOperation *)op; //加上任務的依賴,也就是說依賴的任務都完成後,才能執行當前任務
- (void)removeDependency:(NSOperation *)op; //取消任務的依賴,依賴的任務關係不會自動消除,必須呼叫該方法
- (NSArray *)dependencies; //得到所有依賴的NSOperation任務
以及用於任務同步:
- (void)waitUntilFinished; //阻塞當前執行緒,直到該NSOperation結束。可用於執行緒執行順序的同步
- (void)setCompletionBlock:(void (^)(void))block; //設定NSOperation結束後執行的block程式碼,由於NSOperation有可能被取消,所以這個block執行的程式碼應該和NSOperation的核心任務無關。
NSOperationQueue的其它常用方法:
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; //批量加入執行operation,wait標誌是否當前執行緒等待所有operation結束後,才返回
- (void)addOperationWithBlock:(void (^)(void))block; //相當於加入一個NSBlockOperation執行任務
- (NSArray *)operations; //返回已加入執行operation的陣列,當某個operation結束後會自動從這個陣列清除
- (NSUInteger)operationCount //返回已加入執行operation的數目
- (void)setSuspended:(BOOL)b; //是否暫停將要執行的operation,但不會暫停已開始的operation
- (BOOL)isSuspended; //返回暫停標誌
- (void)cancelAllOperations; //取消所有operation的執行,實質是呼叫各個operation的cancel方法
+ (id)currentQueue; //返回當前NSOperationQueue,如果當前執行緒不是在NSOperationQueue上執行則返回nil
+ (id)mainQueue; //返回主執行緒的NSOperationQueue,預設總是有一個queue
*/
// 參考: http://www.jb51.net/article/130108.htm
}
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end