串行隊列
特點
- 以
先進先出
的方式,順序
調度隊列中的任務執行 - 無論隊列中所指定的執行任務函數是同步還是異步,都會等待前一個任務執行完成后,再調度后面的任務
隊列創建
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", NULL);
串行隊列演練
- 串行隊列 同步執行
/**
提問:是否開線程?是否順序執行?come here 的位置?
*/
- (void)gcdDemo1 {
// 1. 隊列
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);
// 2. 執行任務
for (int i = 0; i < 10; ++i) {
NSLog(@"--- %d", i);
dispatch_sync(q, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
- 串行隊列 異步執行
/**
提問:是否開線程?是否順序執行?come here 的位置?
*/
- (void)gcdDemo2 {
// 1. 隊列
dispatch_queue_t q = dispatch_queue_create("itheima", NULL);
// 2. 執行任務
for (int i = 0; i < 10; ++i) {
NSLog(@"--- %@ %d", [NSThread currentThread], i);
dispatch_async(q, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
並發隊列
特點
- 以
先進先出
的方式,並發
調度隊列中的任務執行 - 如果當前調度的任務是
同步
執行的,會等待任務執行完成后,再調度后續的任務 - 如果當前調度的任務是
異步
執行的,同時底層線程池有可用的線程資源,會再新的線程調度后續任務的執行
隊列創建
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
並發隊列演練
- 並發隊列 異步執行
/**
提問:是否開線程?是否順序執行?come here 的位置?
*/
- (void)gcdDemo3 {
// 1. 隊列
dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
// 2. 執行任務
for (int i = 0; i < 10; ++i) {
dispatch_async(q, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
- 並發隊列 同步執行
/**
提問:是否開線程?是否順序執行?come here 的位置?
*/
- (void)gcdDemo4 {
// 1. 隊列
dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
// 2. 執行任務
for (int i = 0; i < 10; ++i) {
dispatch_sync(q, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
NSLog(@"---> %i", i);
}
NSLog(@"come here");
}
主隊列
特點
- 專門用來在主線程上調度任務的隊列
- 不會開啟線程
- 以
先進先出
的方式,在主線程空閑時
才會調度隊列中的任務在主線程執行 - 如果當前主線程正在有任務執行,那么無論主隊列中當前被添加了什么任務,都不會被調度
隊列獲取
- 主隊列是負責在主線程調度任務的
- 會隨着程序啟動一起創建
- 主隊列只需要獲取不用創建
dispatch_queue_t queue = dispatch_get_main_queue();
主隊列演練
- 主隊列,異步執行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self gcdDemo1];
[NSThread sleepForTimeInterval:1];
NSLog(@"over");
}
- (void)gcdDemo1 {
dispatch_queue_t queue = dispatch_get_main_queue();
for (int i = 0; i < 10; ++i) {
dispatch_async(queue, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
NSLog(@"---> %d", i);
}
NSLog(@"come here");
}
2015-07-13 00:44:57.241 testGCD線程[37988:581895] ---> 0
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 1
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 2
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 3
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 4
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 5
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 6
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 7
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 8
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 9
2015-07-13 00:44:57.242 testGCD線程[37988:581895] come here
2015-07-13 00:44:58.243 testGCD線程[37988:581895] over
2015-07-13 00:44:58.244 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 0
2015-07-13 00:44:58.244 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 1
2015-07-13 00:44:58.244 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 2
2015-07-13 00:44:58.244 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 3
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 4
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 5
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 6
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 7
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 8
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 9
在
主線程空閑時
才會調度隊列中的任務在主線程執行
- 主隊列,同步執行
// MARK: 主隊列,同步任務
- (void)gcdDemo6 {
// 1. 隊列
dispatch_queue_t q = dispatch_get_main_queue();
NSLog(@"!!!");
// 2. 同步
dispatch_sync(q, ^{
NSLog(@"%@", [NSThread currentThread]);
});
NSLog(@"come here");
}
主隊列
和主線程
相互等待會造成死鎖
同步任務的作用
同步任務,可以讓其他異步執行的任務,
依賴
某一個同步任務
例如:在用戶登錄之后,再異步下載文件!
- (void)gcdDemo1 {
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"登錄 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下載 A %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下載 B %@", [NSThread currentThread]);
});
}
- 代碼改造,讓登錄也在異步執行
- (void)gcdDemo2 {
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
void (^task)() = ^{
dispatch_sync(queue, ^{
NSLog(@"登錄 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下載 A %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下載 B %@", [NSThread currentThread]);
});
};
dispatch_async(queue, task);
}
- 主隊列調度同步隊列不死鎖
- (void)gcdDemo3 {
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
void (^task)() = ^ {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"死?");
});
};
dispatch_async(queue, task);
}
主隊列在
主線程空閑時
才會調度隊列中的任務在主線程執行Barrier 異步
- 主要用於在多個異步操作完成之后,統一對
非線程安全
的對象進行更新 - 適合於
大規模的 I/O
操作
代碼演練
- 准備工作
@interface ViewController () {
// 加載照片隊列
dispatch_queue_t _photoQueue;
}
@property (nonatomic, strong) NSMutableArray *photoList;
@end
- (NSMutableArray *)photoList {
if (_photoList == nil) {
_photoList = [[NSMutableArray alloc] init];
}
return _photoList;
}
NSMutableArray
是非線程安全的
- viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
_photoQueue = dispatch_queue_create("com.itheima.com", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 20; ++i) {
[self loadPhotos:i];
}
}
- 模擬下載照片並在完成后添加到數組
- (void)loadPhotos:(int)index {
dispatch_async(_photoQueue, ^{
[NSThread sleepForTimeInterval:1.0];
NSString *fileName = [NSString stringWithFormat:@"%02d.jpg", index % 10 + 1];
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
UIImage *image = [UIImage imageWithContentsOfFile:path];
[self.photoList addObject:image];
NSLog(@"添加照片 %@", fileName);
});
}
運行測試
由於
NSMutableArray
是非線程安全的,如果出現兩個線程在同一時間向數組中添加對象,會出現程序崩潰的情況解決辦法
NSLog(@"添加照片 %@", fileName);
dispatch_barrier_async(_photoQueue, ^{
[self.photoList addObject:image];
NSLog(@"OK %@", [NSThread currentThread]);
});
使用
dispatch_barrier_async
添加的 block 會在之前添加的 block 全部運行結束之后,才在同一個線程順序執行,從而保證對非線程安全的對象進行正確的操作!
Barrier 工作示意圖
注意:
dispatch_barrier_async
必須使用自定義隊列,否則執行效果和全局隊列一致
全局隊列
- 是系統為了方便程序員開發提供的,其工作表現與
並發隊列
一致
全局隊列 & 並發隊列的區別
- 全局隊列
- 沒有名稱
- 無論 MRC & ARC 都不需要考慮釋放
- 日常開發中,建議使用
全局隊列
- 並發隊列
- 有名字,和
NSThread
的name
屬性作用類似 - 如果在 MRC 開發時,需要使用
dispatch_release(q);
釋放相應的對象 dispatch_barrier
必須使用自定義的並發隊列- 開發第三方框架時,建議使用並發隊列
- 有名字,和
全局隊列 異步任務
/**
提問:是否開線程?是否順序執行?come here 的位置?
*/
- (void)gcdDemo8 {
// 1. 隊列
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");
}
運行效果與並發隊列相同
參數
服務質量(隊列對任務調度的優先級)/iOS 7.0 之前,是優先級
- iOS 8.0(新增,暫時不能用,今年年底)
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 適配
- iOS 7.0
DISPATCH_QUEUE_PRIORITY_HIGH
2 高優先級DISPATCH_QUEUE_PRIORITY_DEFAULT
0 默認優先級DISPATCH_QUEUE_PRIORITY_LOW
(-2) 低優先級DISPATCH_QUEUE_PRIORITY_BACKGROUND
INT16_MIN 后台優先級
- iOS 8.0(新增,暫時不能用,今年年底)
為未來保留使用的,應該永遠傳入0
結論:如果要適配 iOS 7.0 & 8.0,使用以下代碼:
dispatch_get_global_queue(0, 0);
延遲操作
// MARK: - 延遲執行
- (void)delay {
/**
從現在開始,經過多少納秒,由"隊列"調度異步執行 block 中的代碼
參數
1. when 從現在開始,經過多少納秒
2. queue 隊列
3. block 異步執行的任務
*/
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
void (^task)() = ^ {
NSLog(@"%@", [NSThread currentThread]);
};
// 主隊列
// dispatch_after(when, dispatch_get_main_queue(), task);
// 全局隊列
// dispatch_after(when, dispatch_get_global_queue(0, 0), task);
// 串行隊列
dispatch_after(when, dispatch_queue_create("itheima", NULL), task);
NSLog(@"come here");
}
- (void)after {
[self.view performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0];
NSLog(@"come here");
}
一次性執行
有的時候,在程序開發中,有些代碼只想從程序啟動就只執行一次,典型的應用場景就是“單例”
// MARK: 一次性執行
- (void)once {
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);
dispatch_once(&onceToken, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"一次性嗎?");
});
NSLog(@"come here");
}
- dispatch 內部也有一把鎖,是能夠保證
線程安全
的!而且是蘋果公司推薦使用的 - 以下代碼用於測試多線程的一次性執行
- (void)demoOnce {
for (int i = 0; i < 10; ++i) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self once];
});
}
}
單例測試
單例的特點
- 在內存中只有一個實例
- 提供一個全局的訪問點
單例實現
// 使用 dispatch_once 實現單例
+ (instancetype)sharedSingleton {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
// 使用互斥鎖實現單例
+ (instancetype)sharedSync {
static id syncInstance;
@synchronized(self) {
if (syncInstance == nil) {
syncInstance = [[self alloc] init];
}
}
return syncInstance;
}
面試時只要實現上面
sharedSingleton
方法即可
單例測試
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
long largeNumber = 1000 * 1000;
// 測試互斥鎖
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (long i = 0; i < largeNumber; ++i) {
[Singleton sharedSync];
}
NSLog(@"互斥鎖: %f", CFAbsoluteTimeGetCurrent() - start);
// 測試 dispatch_once
start = CFAbsoluteTimeGetCurrent();
for (long i = 0; i < largeNumber; ++i) {
[Singleton sharedSingleton];
}
NSLog(@"dispatch_once: %f", CFAbsoluteTimeGetCurrent() - start);
}
調度組
常規用法
- (void)group1 {
// 1. 調度組
dispatch_group_t group = dispatch_group_create();
// 2. 隊列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 3. 將任務添加到隊列和調度組
dispatch_group_async(group, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務 1 %@", [NSThread currentThread]);
});
dispatch_group_async(group, q, ^{
NSLog(@"任務 2 %@", [NSThread currentThread]);
});
dispatch_group_async(group, q, ^{
NSLog(@"任務 3 %@", [NSThread currentThread]);
});
// 4. 監聽所有任務完成
dispatch_group_notify(group, q, ^{
NSLog(@"OVER %@", [NSThread currentThread]);
});
// 5. 判斷異步
NSLog(@"come here");
}
enter & leavel
// MARK: - 調度組 2
- (void)group2 {
// 1. 調度組
dispatch_group_t group = dispatch_group_create();
// 2. 隊列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// dispatch_group_enter & dispatch_group_leave 必須成對出現
dispatch_group_enter(group);
dispatch_group_async(group, q, ^{
NSLog(@"任務 1 %@", [NSThread currentThread]);
// dispatch_group_leave 必須是 block 的最后一句
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, q, ^{
NSLog(@"任務 2 %@", [NSThread currentThread]);
// dispatch_group_leave 必須是 block 的最后一句
dispatch_group_leave(group);
});
// 4. 阻塞式等待調度組中所有任務執行完畢
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 5. 判斷異步
NSLog(@"OVER %@", [NSThread currentThread]);
}