iOS 多執行緒之GCD
一、GCD簡介
Grand Central Dispatch(GCD)是由蘋果開發的多執行緒排程框架,能夠優化多執行緒應用程式的執行過程以支援多核處理器。GCD底層有執行緒池,執行緒池的概念在上一篇文章中有簡單介紹,執行緒池是系統自動來維護,開發者只關注佇列(Dispatch Queue)及任務的建立和同步非同步呼叫即可。 iOS中使用GCD來實現執行緒操作很簡單:建立佇列 > 同步/非同步呼叫 > 將任務放在上一步呼叫的block中。下面我們來實現非同步執行耗時操作,當耗時操作執行完畢時,回到主執行緒來更新相應的UI的功能,簡易的程式碼如下:
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 放一些極其耗時間的任務在此執行
dispatch_async(dispatch_get_main_queue(), ^{
// 耗時任務完成,拿到資源,更新UI更新UI只可以在主執行緒中更新
});
});
複製程式碼
在呼叫GCD中方法執行多執行緒操作時,GCD可自動利用CPU的多核實現非同步處理任務,自動管理執行緒的生命週期(建立執行緒、排程任務、銷燬執行緒),開發者只需要設定GCD需要執行的任務,不用編寫任何執行緒管理程式碼。因此GCD也是蘋果最為推薦開發者使用的多執行緒框架。
二、GCD詳解
首先我們使用GCD實現非同步載入多張網路圖片,來熟悉一下GCD的使用過程:
//// 自定義圖片Model
@interface GCDImage : NSObject
@property (nonatomic, assign) NSInteger index;
@property (nonatomic, strong) NSData *imgData;
@end
@implementation GCDImage
@end
//// GCD載入一張網路圖片
#define ColumnCount 4
#define RowCount 5
#define Margin 10
@interface MultiThread_GCD ()
@property (nonatomic, strong) NSMutableArray *imageViews;
@end
@implementation MultiThread_GCD
- (void)viewDidLoad {
[super viewDidLoad];
[self setTitle:@"GCD"];
[self.view setBackgroundColor:[UIColor whiteColor]];
self.edgesForExtendedLayout = UIRectEdgeNone;
[self layoutViews];
}
- (void)layoutViews {
CGSize size = self.view.frame.size;
CGFloat imgWidth = (size.width - Margin * (ColumnCount + 1)) / ColumnCount;
_imageViews=[NSMutableArray array];
for (int row=0; row<RowCount; row++) {
for (int colomn=0; colomn<ColumnCount; colomn++) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(Margin + colomn * (imgWidth + Margin), Margin + row * (imgWidth + Margin), imgWidth, imgWidth)];
imageView.backgroundColor = [UIColor cyanColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];
}
}
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.frame = CGRectMake(15, (imgWidth + Margin) * RowCount + Margin, size.width - 15 * 2, 45);
[button addTarget:self action:@selector(loadImageWithMultiOperation) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"點選載入" forState:UIControlStateNormal];
[self.view addSubview:button];
}
#pragma mark - 多執行緒下載圖片
//- (void)loadImageWithMultiOperation {
//
// int count = RowCount * ColumnCount;
//
// // 建立一個序列佇列 第一個引數:佇列名稱 第二個引數:佇列型別
// // 注意queue物件不是指標型別
// dispatch_queue_t serialQueue=dispatch_queue_create("QiShareSerialQueue", DISPATCH_QUEUE_SERIAL);
// // 建立多個執行緒用於填充圖片
// for (int i=0; i<count; ++i) {
// //非同步執行佇列任務
// dispatch_async(serialQueue, ^{
// GCDImage *gcdImg = [[GCDImage alloc] init];
// gcdImg.index = i;
// [self loadImg:gcdImg];
// });
//
// }
//}
- (void)loadImageWithMultiOperation {
int count = RowCount * ColumnCount;
// 取得全域性佇列 第一個引數:執行緒優先順序 第二個引數:標記引數,目前沒有用,一般傳入0
dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 建立多個執行緒用於填充圖片
for (int i=0; i<count; ++i) {
//非同步執行佇列任務
dispatch_sync(globalQueue, ^{
GCDImage *gcdImg = [[GCDImage alloc] init];
gcdImg.index = i;
[self loadImg:gcdImg];
});
}
}
#pragma mark - 載入圖片
- (void)loadImg:(GCDImage *)gcdImg {
// 請求資料
gcdImg.imgData = [self requestData];
// 更新UI介面(mainQueue是UI主執行緒
// dispatch_sync(dispatch_get_main_queue(), ^{
[self updateImage:gcdImg];
// });
// 列印當前執行緒
NSLog(@"current thread: %@", [NSThread currentThread]);
}
#pragma mark - 請求圖片資料
- (NSData *)requestData {
NSURL *url = [NSURL URLWithString:@"https://store.storeimages.cdn-apple.com/8756/as-images.apple.com/is/image/AppleInc/aos/published/images/a/pp/apple/products/apple-products-section1-one-holiday-201811?wid=2560&hei=1046&fmt=jpeg&qlt=95&op_usm=0.5,0.5&.v=1540576114151"];
NSData *data = [NSData dataWithContentsOfURL:url];
return data;
}
#pragma mark - 將圖片顯示到介面
- (void)updateImage:(GCDImage *)gcdImg {
UIImage *image = [UIImage imageWithData:gcdImg.imgData];
UIImageView *imageView = _imageViews[gcdImg.index];
imageView.image = image;
}
@end
複製程式碼
下面我們就來逐步詳細介紹GCD相關的細節...
2.1 GCD中用來執行任務的常用方法
// 同步執行任務(在當前執行緒中執行任務,不會開啟新執行緒)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 非同步執行任務(可以在新執行緒中執行任務,有開啟新執行緒的能力)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
複製程式碼
其中,第一個引數為任務所要加入的佇列;第二個引數是一個dispatch_block_t型別的block,即任務(一段程式碼)。
2.2 GCD中的佇列
// 序列佇列的建立方法
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_SERIAL);
// 併發佇列的建立方法
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);
複製程式碼
其中,第一個引數為佇列的名稱;第二個引數根據取值命名,就可看出為佇列的型別。
兩個特殊的佇列:
- 主佇列(Main Dispatch Queue):是序列佇列,所有放在主佇列中的任務,都會放到主執行緒中執行; 獲取主佇列:dispatch_queue_t queue = dispatch_get_main_queue();
- 全域性佇列(Global Dispatch Queue):是併發行佇列; 獲取全域性佇列:dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 其中,第一個引數為佇列優先順序,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT;第二個引數用0即可。
則排程方法與任務所在佇列的組合如下:
排程方法 | 併發佇列 | 序列佇列 | 主佇列 |
---|---|---|---|
非同步(dispatch_async) | 開啟多個新執行緒,併發執行任務 | 開啟1個新執行緒,序列執行任務 | 沒有開啟新執行緒,序列執行任務 |
同步(dispatch_sync) | 沒有開啟新執行緒,序列執行任務 | 沒有開啟新執行緒,序列執行任務 | 主執行緒呼叫:死鎖導致程式卡死 其他執行緒呼叫:沒有開啟新執行緒,序列執行任務 |
我們可以執行下面的程式碼來驗證上列表組合中每一項的正確性,如下:
- (void)addTask:(NSInteger)tag {
[NSThread sleepForTimeInterval:2];
NSLog(@"addTask%ld--->> %@", (long)tag, [NSThread currentThread]);
NSURLSessionTask *task = [NSURLSession.sharedSession dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.so.com/"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"任務完成%ld--->> %@", (long)tag, [NSThread currentThread]);
}];
[task resume];
}
#pragma mark - 序列/併發佇列 + 同步/非同步呼叫組合
// 非同步執行
- (void)asyncExecute {
NSLog(@"CurrentThread---%@", [NSThread currentThread]);
NSLog(@"asyncExecute start");
// + 併發佇列(開啟多個執行緒,併發執行任務)
dispatch_queue_t queue = dispatch_queue_create(QiShareQueue, DISPATCH_QUEUE_CONCURRENT);
// // + 序列佇列(開啟1個新執行緒,序列執行任務)
// dispatch_queue_t queue = dispatch_queue_create(QiShareQueue, DISPATCH_QUEUE_SERIAL);
// // + 主佇列(沒有開啟新執行緒,序列執行任務)
// dispatch_queue_t queue = dispatch_get_main_queue();
for (NSInteger i=0; i<10; i++) {
dispatch_async(queue, ^{
// 追加任務
[self addTask:i];
});
}
NSLog(@"asyncExecute end");
}
// 同步執行
- (void)syncExecute {
NSLog(@"currentThread---%@", [NSThread currentThread]);
NSLog(@"syncExecute start");
// // + 併發佇列(沒有開啟新執行緒,序列執行任務)
// dispatch_queue_t queue = dispatch_queue_create(QiShareQueue, DISPATCH_QUEUE_CONCURRENT);
// // + 序列佇列(沒有開啟新執行緒,序列執行任務)
// dispatch_queue_t queue = dispatch_queue_create(QiShareQueue, DISPATCH_QUEUE_SERIAL);
// + 主佇列(1.主執行緒呼叫:死鎖;2.其他執行緒呼叫:不會開啟新執行緒,執行完一個任務,再執行下一個任務)
dispatch_queue_t queue = dispatch_get_main_queue();
for (NSInteger i=0; i<10; i++) {
dispatch_sync(queue, ^{
// 追加任務
[self addTask:i];
});
}
NSLog(@"syncExecute end");
}
複製程式碼
2.3 GCD 執行緒間通訊
例如,從非主執行緒中非同步執行完一個操作後,回到主執行緒更新UI的大致操作如下:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 如果下載結束回到主執行緒更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 更新UI
});
});
複製程式碼
2.4 關於GCD 死鎖
在主執行緒中,直接執行以下程式碼:
dispatch_sync(dispatch_get_main_queue(), ^{
});
複製程式碼
就會介面卡死,發生GCD 死鎖。原因就是主執行緒正在執行dispatch_sync操作(同步,並等待其中block完成),而dispatch_sync操作需要等待主執行緒執行完當前操作才能將block加入主佇列,這樣就形成了“互相等待”。 通常情況下,在一個執行緒正在執行一個序列佇列sQueue上任務的過程中,再次呼叫dispatch_sync同步執行這個序列佇列sQueue在上的任務,就會引起死鎖,如下:
- (void)deadLock {
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
});
NSLog(@"4");
}
//// 列印日誌(介面卡死)
//2019-01-14 18:07:22.161085+0800 QiMultiThread[469:47692] 1
//2019-01-14 18:07:22.161286+0800 QiMultiThread[469:47692] 4
//2019-01-14 18:07:22.161346+0800 QiMultiThread[469:47707] 2
複製程式碼
三、GCD中的其他常用方法
3.1 dispatch_once
一般用來實現建立一個單例,將某個類的例項化部分放在dispatch_once的block中來實現。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
})
複製程式碼
3.2 dispatch_after
在一個queue中,延時執行某個操作,比如app啟動後先執行,開螢幕動畫,延時載入介面等。
dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
// 在queue裡面延遲執行的一段程式碼...
});
複製程式碼
3.3 dispatch_apply
用dispatch_apply方法可以執行queue中的一個指定任務(即block)n次。
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t i) {
NSLog(@"執行 第%lu次", (long)i);
});
複製程式碼
注意:如果佇列是併發佇列,則會併發執行block任務,dispatch_apply是一個同步呼叫,block任務執行n次後才返回。
3.4 dispatch_barrier
dispatch_barrier有兩個方法:dispatch_barrier_async和dispatch_barrier_sync。在同一個佇列中dispatch_barrier方法需要等待其前面所有任務執行完畢,再執行自己,自己執行完之後再執行其後面的任務。
-(void)diapatchBarrier {
NSLog(@"currentThread: %@", [NSThread currentThread]);
NSLog(@"---start---");
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:7];
NSLog(@"asyncTask_1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"asyncTask_2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier_asyncTask");
[NSThread sleepForTimeInterval:3];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"asyncTask_4");
});
NSLog(@"---end---");
}
//// 列印日誌
//2019-01-14 16:56:40.673822+0800 QiMultiThread[401:32253] currentThread: <NSThread: 0x1c42622c0>{number = 1, name = main}
//2019-01-14 16:56:40.674137+0800 QiMultiThread[401:32253] ---start---
//2019-01-14 16:56:40.674364+0800 QiMultiThread[401:32253] ---end---
//2019-01-14 16:56:45.681232+0800 QiMultiThread[401:32340] asyncTask_2
//2019-01-14 16:56:47.682411+0800 QiMultiThread[401:32285] asyncTask_1
//2019-01-14 16:56:47.682631+0800 QiMultiThread[401:32285] barrier_asyncTask
//2019-01-14 16:56:51.688996+0800 QiMultiThread[401:32285] asyncTask_4
複製程式碼
3.5 dispatch_group
在序列佇列中,如果想讓一任務A在其他一系列任務B、C、D完成之後再執行,那麼在這個序列佇列中,將任務A追加在這一些列任務之後就可以了。但是在並行佇列中,就需要用dispatch_group來實現這波操作。
1)dispatch_group_notify
因為dispatch_group_notify是非同步執行的,所以不會阻塞當前執行緒。
-(void)asyncGroupNotify
{
NSLog(@"star");
dispatch_group_t group=dispatch_group_create();
dispatch_queue_t queue=dispatch_queue_create("com.GCD_demo.www", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"group_work_1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:6];
NSLog(@"group_work_2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group_work_3");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"dispatch_group_Notify 結束");
});
}
//// 列印日誌
//2019-01-14 15:21:15.194094+0800 QiMultiThread[295:9717] ---start---
//2019-01-14 15:21:15.194270+0800 QiMultiThread[295:9717] ---end---
//2019-01-14 15:21:17.198617+0800 QiMultiThread[295:9820] groupTask_1
//2019-01-14 15:21:17.199424+0800 QiMultiThread[295:9820] currentThread: <NSThread: 0x1c4660640>{number = 3, name = (null)}
//2019-01-14 15:21:19.200911+0800 QiMultiThread[295:9825] groupTask_3
//2019-01-14 15:21:19.201224+0800 QiMultiThread[295:9825] currentThread: <NSThread: 0x1c0278640>{number = 4, name = (null)}
//2019-01-14 15:21:22.200290+0800 QiMultiThread[295:9745] groupTask_2
//2019-01-14 15:21:22.200595+0800 QiMultiThread[295:9745] currentThread: <NSThread: 0x1c4666b00>{number = 5, name = (null)}
//2019-01-14 15:21:22.200810+0800 QiMultiThread[295:9745] dispatch_group_Notify 結束
複製程式碼
2)dispatch_group_wait
顧名思義,dispatch_group_wait會阻塞當前執行緒,直到任務都完成時才會繼續執行之後的程式碼。
-(void)dispatchGroupWait {
NSLog(@"currentThread: %@", [NSThread currentThread]);
NSLog(@"---start---");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"groupTask_1");
NSLog(@"currentThread: %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:7];
NSLog(@"groupTask_2");
NSLog(@"currentThread: %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"groupTask_3");
NSLog(@"currentThread: %@", [NSThread currentThread]);
});
long result = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
NSLog(@"dispatch_group_wait result = %ld", result);
NSLog(@"---end---");
}
//// 列印日誌
//2019-01-14 15:46:37.226768+0800 QiMultiThread[323:15757] ---start---
//2019-01-14 15:46:39.231152+0800 QiMultiThread[323:15795] groupTask_1
//2019-01-14 15:46:39.231367+0800 QiMultiThread[323:15795] currentThread: <NSThread: 0x1c0073600>{number = 3, name = (null)}
//2019-01-14 15:46:41.232170+0800 QiMultiThread[323:15801] groupTask_3
//2019-01-14 15:46:41.232644+0800 QiMultiThread[323:15801] currentThread: <NSThread: 0x1c00736c0>{number = 4, name = (null)}
//2019-01-14 15:46:44.231956+0800 QiMultiThread[323:15827] groupTask_2
//2019-01-14 15:46:44.232328+0800 QiMultiThread[323:15827] currentThread: <NSThread: 0x1c446b440>{number = 5, name = (null)}
//2019-01-14 15:46:44.232468+0800 QiMultiThread[323:15757] dispatch_group_wait result = 0
//2019-01-14 15:46:44.232525+0800 QiMultiThread[323:15757] ---end---
複製程式碼
注: dispatch_group_wait中設定了10秒的等待時間,如果group所有任務的執行時間<=10秒就返回0,如果>10秒則返回非0。
3)dispatch_group_enter和dispatch_group_leave
用dispatch_group_enter跟dispatch_group_leave方法,並配合dispatch_async方法使用,可以代替dispatch_group_async。不過這樣操作更顯麻煩,dispatch_group_enter與dispatch_group_leave兩個方法要配對出現,且group操作結尾仍需要dispatch_group_notify。
-(void)dispatchGroupEnter {
NSLog(@"currentThread: %@", [NSThread currentThread]);
NSLog(@"---start---");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:7];
NSLog(@"asyncTask_1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"asyncTask_2");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"dispatch_group_notify block end");
});
NSLog(@"---end---");
}
}
//// 列印日誌
//2019-01-14 15:47:56.818998+0800 QiMultiThread[326:16283] currentThread: <NSThread: 0x1c007bc00>{number = 1, name = main}
//2019-01-14 15:47:56.819064+0800 QiMultiThread[326:16283] ---start---
//2019-01-14 15:47:56.819122+0800 QiMultiThread[326:16283] ---end---
//2019-01-14 15:48:00.824143+0800 QiMultiThread[326:16317] asyncTask_2
//2019-01-14 15:48:03.824189+0800 QiMultiThread[326:16314] asyncTask_1
//2019-01-14 15:48:03.824322+0800 QiMultiThread[326:16283] dispatch_group_notify block end
複製程式碼
3.6 dispatch_block
如下面將任務(block)新增到佇列中的兩個方法:
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
複製程式碼
其中,我們可以把第二個引數單獨定義一個dispatch_block_t型變數,再傳入相應方法中:
// 單獨定義一個dispatch_block_t型變數
dispatch_block_t block=dispatch_block_create(0, ^{
NSLog(@"dispatchBlock_work");
});
// 呼叫過程 dispatch_async(queue, block);
複製程式碼
1)dispatch_block_wait
dispatch_block_wait的執行效果與dispatch_group_wait類似,同樣會阻塞當前執行緒。下面的程式碼中,將wait操作放在了dispatch_async中來執行(且queue為DISPATCH_QUEUE_CONCURRENT型),避免了阻塞主執行緒。
- (void)dispatchBlockWait {
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"---before---");
[NSThread sleepForTimeInterval:7];
NSLog(@"---after---");
});
dispatch_async(queue, block);
dispatch_async(queue, ^{
long result = dispatch_block_wait(block, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
NSLog(@"dispatch_block_wait result = %ld", result);
});
}
//2019-01-14 16:34:59.389940+0800 QiMultiThread[382:27805] ---before---
//2019-01-14 16:35:02.396144+0800 QiMultiThread[382:27809] dispatch_block_wait result = 49
//2019-01-14 16:35:06.395332+0800 QiMultiThread[382:27805] ---after---
複製程式碼
2)dispatch_block_notify
dispatch_block_notify當觀察的某個block執行結束之後立刻通知提交另一特定的block到指定的queue中執行,該函式有三個引數,第一引數是需要觀察的block,第二個引數是被通知block提交執行的queue,第三引數是當需要被通知執行的block
- (void)dispatchBlockNotify {
//dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t preBlock = dispatch_block_create(0, ^{
NSLog(@"preBlock start");
[NSThread sleepForTimeInterval:2];
NSLog(@"preBlock end");
});
dispatch_async(queue, preBlock);
dispatch_block_t afterBlock = dispatch_block_create(0, ^{
NSLog(@"has been notifyed");
});
dispatch_block_notify(preBlock, queue, afterBlock);
}
//// 列印日誌
//2019-01-14 17:20:33.893522+0800 QiMultiThread[425:37900] preBlock start
//2019-01-14 17:20:35.898765+0800 QiMultiThread[425:37900] preBlock end
//2019-01-14 17:20:35.899008+0800 QiMultiThread[425:37900] has been notifyed
複製程式碼
注:此處queue為DISPATCH_QUEUE_SERIAL或DISPATCH_QUEUE_CONCURRENT均可;當preBlock執行完後,afterBlock會自動提交到queue中執行(queue可以是其他自定義佇列)。
3)dispatch_block_cancel 提交到佇列的block,是可以撤銷的,如下
dispatch_block_t block = dispatch_block_create(0, ^{
});
dispatch_async(queue, block);
dispatch_block_cancel(block); // 撤銷一個任務
複製程式碼
3.7 dispatch_set_target_queue
dispatch_set_target_queue 函式有兩個引數queue和targetQueue,dispatch_set_target_queue有兩個功能:queue和targetQueue的優先順序,targetQueue的優先順序更高;targetQueue可以成為queue中所有任務的參照佇列,也即queue中的任務將依照targetQueue的型別特點來執行。
dispatch_set_target_queue(queue, targetQueue);
複製程式碼
將不同佇列中的任務同步的執行:
- (void) dispatchSet2 {
dispatch_queue_t targetQueue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue1 = dispatch_queue_create("QiShareQueue_1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("QiShareQueue_2", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_async(queue1, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"Task_1");
});
dispatch_async(queue2, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"Task_2");
});
dispatch_async(queue2, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"Task_3");
});
}
//// 列印日誌
//2019-01-14 17:45:29.920351+0800 QiMultiThread[447:42950] currentThread: <NSThread: 0x1c407c600>{number = 1, name = main}
//2019-01-14 17:45:29.920424+0800 QiMultiThread[447:42950] ---start---
//2019-01-14 17:45:29.920474+0800 QiMultiThread[447:42950] ---end---
//2019-01-14 17:45:34.925556+0800 QiMultiThread[447:42987] Task_1
//2019-01-14 17:45:37.930813+0800 QiMultiThread[447:42987] Task_2
//2019-01-14 17:45:38.936053+0800 QiMultiThread[447:42987] Task_3
複製程式碼
上述程式碼中,我們將targetQueue設定為序列,則queue1與queue2均參照targetQueue的型別特點來執行。
ps:
- 非同步執行(async)雖然具有開啟新執行緒的能力,但是並不一定會開啟新執行緒,是否開啟新執行緒跟任務所在佇列型別有關;
- 在dispatch_barrier_(a)sync方法中,我們要注意的是不要使用全域性併發佇列,DISPATCH_QUEUE_CONCURRENT型別的自定義佇列更合適一些。
參考文章連結,感謝!
小編微信:可加並拉入《QiShare技術交流群》。
關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)
推薦文章:
iOS Winding Rules 纏繞規則
iOS 簽名機制
iOS 掃描二維碼/條形碼
iOS 瞭解Xcode Bitcode
iOS 重繪之drawRect
奇舞週刊