开头
多线程
实际用处
- 处理进行网络访问的操作,存在失败的可能,成功后,再返回主线程进行数据处理或者显示
- 处理耗时操作,处理完成后,再返回主线程
- 我的理解是,一台app的运行过程就好比
复仇者联盟
的主世界,而耗时操作就是古一法师演示给绿巨人看的那个画面
,一个正常运行的时空,由于被借走了几颗宝石,产生了新的时空
,也就是子线程,执行完后,将宝石物归原主,也就回到了主世界了,而app本身在这个过程也是按照自己节奏执行的
- 经常是网络访问或者数据传递的工作放在子线程,再回到主线程进行刷新
实现方法
名词解释
- 进程,类比公司。在系统中运行的某个应用程序,各个进程之间相互独立,运行在其专用的且受保护的内存空间
- 线程,类比员工。一个进程由多个线程组成,是进程的基本执行单元,一个进程的所有执行任务在线程中执行
- 主线程,类似老板。app默认执行任务的线程
- 同步执行,一条路径,不具备开启新线程的能力
- 异步执行,多条路径,具备开启新线程的能力
- 串行队列,一个接着一个执行
- 并发队列,同时执行,只在异步函数有效
- 主队列,先执行完主线程的代码,才会执行主队列的任务,本质是串行队列,但是会等待主线程的代码
- 线程安全,在某一时刻有且仅有一个线程对变量进行写操作,即是线程安全;相反,则是线程不安全
- 线程同步,线程A和线程B配合执行,线程A执行到某部分时,需要通过线程B获取一些数据,等待B执行完毕后,线程A获取到数据之后,接下来执行
NSTread
使用
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil]
[thread start]
//线程名
thread1.name = @"testThread"
thread2.name = @"thread2"
//优先级
thread.qualityOfService = NSQualityOfServiceUserInitiated
[NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
[self performSelectorInBackground:@selector(demo) withObject:nil];
[self performSelectorOnMainThread:@selector(main) withObject:nil waitUntilDone:YES];
案例-下载网络图片
- (void)loadView{
self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds]
self.scrollView.backgroundColor = [UIColor blackColor]
self.view = self.scrollView
self.imageView = [UIImageView new]
[self.scrollView addSubview:self.imageView]
}
- (void)viewDidLoad {
[super viewDidLoad]
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil]
[thread start]
}
//下载网络图片
- (void)downloadImage{
NSURL *url = [NSURL URLWithString:@"https://p4.itc.cn/images01/20210306/c866be67263f4c8484228b593315f7c4.jpeg"]
NSData *data = [NSData dataWithContentsOfURL:url]
//二进制属性转换成uiimage
UIImage *img = [UIImage imageWithData:data]
//在主线程上执行
//waitUntilDone 值是YES 等待方法执行完
//在子线程中回到主线程
[self performSelectorOnMainThread:@selector(updataUI:) withObject:img waitUntilDone:YES]
}
- (void)updataUI:(UIImage *)img{
self.imageView.image = img
[self.imageView sizeToFit]
self.scrollView.contentSize = img.size
}
GCD
组合
- GCD有两个概念,
队列
和同\异步
- 队列,理解为执行的通道,分为
串行队列
和并发队列
,也就是顺序执行
和同时执行
,决定了执行顺序
- 同\异步,理解为是否有开启新线程的能力,同步没有开启新线程的能力,异步有开启新线程的能力
串行队列、同步执行
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL)
for (int i = 0
dispatch_sync(queue, ^{
NSLog(@"串行队列,同步执行 %@----- %d", [NSThread currentThread], i)
})
}
串行队列、异步执行
- 串行队列,顺序执行;异步执行,开启新线程,只开一个线程
- 如果同时有主线程的任务再执行,也是一起执行的
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL)
for (int i = 0
dispatch_async(queue, ^{
NSLog(@"串行队列,异步执行 %@ ----- %d", [NSThread currentThread], i)
})
}
并发队列、同步执行
- 并行队列,同时执行;同步执行,不开启新线程,导致还是顺序执行
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT)
for (int i = 0
dispatch_sync(queue, ^{
NSLog(@"并行队列,同步执行 %@ ----- %d", [NSThread currentThread], i)
})
}
并发队列、异步执行
- 并行队列,同时执行;异步执行,开启新线程,开启多个线程,无序执行
- 如果同时有主线程的任务再执行,也是一起执行的,也就是说只要开启了新线程,相同优先级,跟主线程的任务是同时执行的
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT)
for (int i = 0
dispatch_async(queue, ^{
NSLog(@"并行队列,异步执行 %@ ----- %d", [NSThread currentThread], i)
})
}
主队列、异步执行
- 主队列,等待主线程任务全部完成后执行;
- 效果,在主线程上,顺序执行
- 不开启新线程
for (int i = 0
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主队列,异步执行 %@ ----- %d", [NSThread currentThread], i)
})
}
主队列、同步执行
- 不会开启新线程,且会因为同步执行的特点,等待第一个任务执行完成才会往后执行,导致添加到主线程的任务相互等待,造成崩溃,发生死锁
- 主队列是串行队列,任务必须按照顺序执行,添加同步任务后,必须等到主队列的任务执行完,但是,程序向下执行的时候,又必须等同步任务执行
for (int i = 0
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"同步执行 %@ ----- %d", [NSThread currentThread], i)
})
}
- 解决死锁,在子线程上执行主线程的任务
- 会等待主线程上的任务执行完毕后执行
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT)
dispatch_async(queue, ^{
NSLog(@"%@", [NSThread currentThread])
for (int i = 0
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"在子线程上,执行主队列的任务 %@ ----- %d", [NSThread currentThread], i)
})
}
})
全局队列
- 本质上是并发队列,没有名称
- 在实际使用中,其实就相当于手动建立了并发队列
dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
- 全局队列模拟图片下载
- 回到主线程的地方,async和sync的效果一致
for (int i = 0
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"下载图片 %@ ----- %d", [NSThread currentThread], i)
[NSThread sleepForTimeInterval:3]
NSLog(@"下载完成 %@ ----- %d", [NSThread currentThread], i)
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"刷新UI %@ ----- %d", [NSThread currentThread], i)
})
})
}
Barrier阻塞
- 负责阻塞多线程任务,必须完成
Barrier
中的任务,才会返回
- 以下的效果为,在子线程上顺序执行
dispatch_queue_t queue = dispatch_queue_create("mzx", DISPATCH_QUEUE_CONCURRENT)
for (int i = 0
dispatch_barrier_async(queue, ^{
NSLog(@"阻塞 %@ ----- %d", [NSThread currentThread], i)
[NSThread sleepForTimeInterval:3]
})
}
}
after延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"after %@ ----- %d", [NSThread currentThread], i);
});
}
});
once执行一次
- 原理 判断静态的全局变量的值 onceToken必须被初始化成0,执行一次后为-1,once内部判断,如果是0才执行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"%@", [NSThread currentThread]);
});
+ (instancetype)shared {
static id instance = nil
static dispatch_once_t onceToken
dispatch_once(&onceToken, ^{
if (instance == nil) {
instance = [self new]
}
})
return instance
}
apply
- 多线程的循环执行
- 放在并发队列中,直接同时执行任务;放在串行队列中,会顺序执行;放在主队列中,会崩溃,原因同主队列,同步执行
- 并发队列
NSLog(@"apply---begin");
dispatch_apply(6, dispatch_get_global_queue(0, 0), ^(size_t index) {
[NSThread sleepForTimeInterval:3];
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
NSLog(@"apply---begin")
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL)
dispatch_apply(6, queue, ^(size_t index) {
[NSThread sleepForTimeInterval:3]
NSLog(@"%zd---%@",index, [NSThread currentThread])
})
NSLog(@"apply---end")
调度组
- 目的,管理多个异步任务
- 将多个异步执行的代码放入组内,等组内任务执行完成后再执行notify中的任务
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"下载歌曲1---%@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"下载歌曲2---%@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"下载完成---%@",[NSThread currentThread]);
NSLog(@"group---end");
});
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"下载歌曲1---%@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:10];
NSLog(@"下载歌曲2---%@",[NSThread currentThread]);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
- 用leave和enter实现group原理
- 通过控制group的leave和enter数量是否对等来决定是否执行完这个调度组是否完成,再执行
notfiy
或者打开wait
阻塞
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task1");
NSLog(@"%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task2");
NSLog(@"%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task3");
NSLog(@"%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"hello");
信号量
- dispatch_semaphore,计数的信号,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,可通过
- 信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量
dispatch_semaphore_t semphore = dispatch_semaphore_create(0);
dispatch_semaphore_signal(semphore);
dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
- 线程同步,异步任务转换成同步执行
- 信号量会先减1变成-1,进行阻塞;等待异步任务执行完毕后,信号量+1,信号为0,执行后面的代码
NSLog(@"currentThread---%@",[NSThread currentThread])
NSLog(@"semaphore---begin")
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
//初始化信号
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0)
//block内部无法修改外界的局部变量,可以用__block修饰符修饰
__block int number = 0
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]
NSLog(@"异步执行---%@",[NSThread currentThread])
number = 100
//信号量+1
dispatch_semaphore_signal(semaphore)
})
//信号量-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
NSLog(@"semaphore---end,number = %d",number);
NSOpreation
- 基于GCD、面向对象
- 是抽象类,没有实现的类,用作父类,约束子类都具有共同的属性和方法
与GCD的区别
- GCD将任务添加到队列中(串行/并发/全局/主)中,以同步/异步的方式执行
- NSOpreation将异步操作添加到并发队列中,可以设置最大并发数,队列的暂停和继续,取消所有的操作,制定操作之间的依赖关系
NSInvocationOperation
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invOPdemo) object:nil]
NSOperationQueue *queue = [NSOperationQueue new]
//开启新线程,并调用demo方法
[queue addOperation:op]
//在主线程调用demo方法
// [op start]
[op setCompletionBlock:^{
NSLog(@"%@", [NSThread currentThread])
}]
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"更新UI %@", [NSThread currentThread])
}]
NSBlockOperation
NSBlockOperation *bp = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread])
}]
NSOperationQueue *queue = [NSOperationQueue new]
// [queue addOperation:bp]
[bp start]
NSBlockOperation *bp = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread])
}]
//添加额外的操作
[bp addExecutionBlock:^{
NSLog(@"1.5 ---- %@", [NSThread currentThread])
}]
NSOperationQueue *queue = [NSOperationQueue new]
[queue addOperation:bp]
[queue addOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread])
}]
最大并发数
queue.maxConcurrentOperationCount = 3
依赖
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2]
NSLog(@"下载 %@", [NSThread currentThread])
}]
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1]
NSLog(@"解压 %@", [NSThread currentThread])
}]
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"升级完成 %@", [NSThread currentThread])
}]
[op2 addDependency:op1]
[op3 addDependency:op2]
NSOperationQueue *queue = [NSOperationQueue new]
//waitUntilFinished 为no不阻塞,为yes阻塞,执行完后,才向下执行
[queue addOperations:@[op1, op2] waitUntilFinished:YES]
[[NSOperationQueue mainQueue] addOperation:op3]
服务优先级
qualityOfService
/**
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
*/
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0
//sleep(1)
NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread])
}
}]
// 设置最高优先级
bo1.qualityOfService = NSQualityOfServiceUserInitiated
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0
NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread])
}
}]
// 设置最低优先级
bo2.qualityOfService = NSQualityOfServiceUserInteractive
NSOperationQueue *queue = [[NSOperationQueue alloc] init]
[queue addOperation:bo1]
[queue addOperation:bo2]
暂停、继续、取消
- 这里也可以看出,
NSOperationQueue
是将所有任务添加到队列中,并根据最大并发数进行同时执行任务
- 取消操作会将当前任务执行完毕,取消后续的操作
- (IBAction)cancel:(id)sender {
[self.queue cancelAllOperations];
NSLog(@"cancel - %zd", self.queue.operationCount);
}
- (IBAction)suspend:(id)sender {
self.queue.suspended = YES;
NSLog(@"suspend");
}
- (IBAction)continue:(id)sender {
self.queue.suspended = NO;
NSLog(@"continue");
}
自定义Operation
- 每次调用
new
方法都会调用main
- 重写
main
方法,
- (void)main{
@autoreleasepool {
NSAssert(self.finishedBlock != nil, @"finishBlock 不能为 nil");
[NSThread sleepForTimeInterval:2];
if (self.isCancelled) {
return;
}
NSLog(@"下载图片 %@ %@", [NSThread currentThread], self.urlString);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.finishedBlock(self.urlString);
}];
}
}
+ (instancetype)downloadOperationWithURLString:(NSString *)urlString andFinishedBlock:(void (^)(NSString *))finishedBlock
{
MYDownloadOperation *op = [SSDownloadOperation new]
op.urlString = urlString
op.finishedBlock = finishedBlock
return op
}
- 使用效果,可以直接取消所有操作,并不会让当前任务执行完成
for (int i = 0
SSDownloadOperation *op = [SSDownloadOperation downloadOperationWithURLString:@"abc.jpg" andFinishedBlock:^(NSString *img) {
//图片下载完成更新ui
NSLog(@"更新UI %@ %@ %d", img, [NSThread currentThread], i)
}]
[self.queue addOperation:op]
}