零基础iOS开发学习日记-功能篇-多线程

440 阅读9分钟

开头

多线程

实际用处

  • 处理进行网络访问的操作,存在失败的可能,成功后,再返回主线程进行数据处理或者显示
  • 处理耗时操作,处理完成后,再返回主线程
  • 我的理解是,一台app的运行过程就好比复仇者联盟的主世界,而耗时操作就是古一法师演示给绿巨人看的那个画面,一个正常运行的时空,由于被借走了几颗宝石,产生了新的时空,也就是子线程,执行完后,将宝石物归原主,也就回到了主世界了,而app本身在这个过程也是按照自己节奏执行的
  • 经常是网络访问或者数据传递的工作放在子线程,再回到主线程进行刷新

实现方法

  • NSThread
  • GCD
  • NSOpreation

名词解释

  • 进程,类比公司。在系统中运行的某个应用程序,各个进程之间相互独立,运行在其专用的且受保护的内存空间
  • 线程,类比员工。一个进程由多个线程组成,是进程的基本执行单元,一个进程的所有执行任务在线程中执行
  • 主线程,类似老板。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];
  • 回到主线程执行
//object 传入的参数
//untilDone 只要不要等待执行完毕,再执行后续的任务
[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; i < 10; i++) {
    dispatch_sync(queue, ^{
        NSLog(@"串行队列,同步执行 %@----- %d", [NSThread currentThread], i);

    });
}

串行队列、异步执行

  • 串行队列,顺序执行;异步执行,开启新线程,只开一个线程
  • 如果同时有主线程的任务再执行,也是一起执行的
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        NSLog(@"串行队列,异步执行 %@ ----- %d", [NSThread currentThread], i);

    });
}

并发队列、同步执行

  • 并行队列,同时执行;同步执行,不开启新线程,导致还是顺序执行
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
    dispatch_sync(queue, ^{
        NSLog(@"并行队列,同步执行 %@ ----- %d", [NSThread currentThread], i);

    });
}

并发队列、异步执行

  • 并行队列,同时执行;异步执行,开启新线程,开启多个线程,无序执行
  • 如果同时有主线程的任务再执行,也是一起执行的,也就是说只要开启了新线程,相同优先级,跟主线程的任务是同时执行的
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        NSLog(@"并行队列,异步执行 %@ ----- %d", [NSThread currentThread], i);
        
    });
}

主队列、异步执行

  • 主队列,等待主线程任务全部完成后执行;
  • 效果,在主线程上,顺序执行
  • 不开启新线程
for (int i = 0; i < 10; i++) {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"主队列,异步执行 %@ ----- %d", [NSThread currentThread], i);

    });
}

主队列、同步执行

  • 不会开启新线程,且会因为同步执行的特点,等待第一个任务执行完成才会往后执行,导致添加到主线程的任务相互等待,造成崩溃,发生死锁
  • 主队列是串行队列,任务必须按照顺序执行,添加同步任务后,必须等到主队列的任务执行完,但是,程序向下执行的时候,又必须等同步任务执行
for (int i = 0; i < 10; i++) {
    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; i < 10; i++) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"在子线程上,执行主队列的任务 %@ ----- %d", [NSThread currentThread], i);
            
        });
    }
});

全局队列

  • 本质上是并发队列,没有名称
  • 在实际使用中,其实就相当于手动建立了并发队列
dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
//第一个参数
/**
    服务质量
 *  - QOS_CLASS_USER_INTERACTIVE
 *  - QOS_CLASS_USER_INITIATED
 *  - QOS_CLASS_DEFAULT
 *  - QOS_CLASS_UTILITY
 *  - QOS_CLASS_BACKGROUND
 *  队列优先级
 *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
 *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:  0     QOS_CLASS_DEFAULT
 *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
 *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND
 */
//第二个参数没有什么用
/**
 *@param flags
 *Reserved for future use. Passing any value other than zero may result in
 *a NULL return value.
 */
  • 全局队列模拟图片下载
  • 回到主线程的地方,async和sync的效果一致
for (int i = 0; i < 10; i++) {
    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; i < 10; i++) {
        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), ^{
    // 追加任务 1
    [NSThread sleepForTimeInterval:2];
    NSLog(@"下载歌曲1---%@",[NSThread currentThread]);
});

dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    // 追加任务 2
    [NSThread sleepForTimeInterval:2];
    NSLog(@"下载歌曲2---%@",[NSThread currentThread]);
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行任务
    NSLog(@"下载完成---%@",[NSThread currentThread]);

    NSLog(@"group---end");
});
  • 通过wait阻塞
NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    // 追加任务 1
    [NSThread sleepForTimeInterval:2];
    NSLog(@"下载歌曲1---%@",[NSThread currentThread]);
});

dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    // 追加任务 2
    [NSThread sleepForTimeInterval:10];
    NSLog(@"下载歌曲2---%@",[NSThread currentThread]);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); //第二个参数设置为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_notify(group, dispatch_get_main_queue(), ^{
//        NSLog(@"over");
//    });
//等待组中的任务都执行完毕,才会执行后续的代码
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); // 信号量+1
dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER); //信号量-1
  • 线程同步,异步任务转换成同步执行
  • 信号量会先减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线程上执行
[op setCompletionBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];
  • 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    NSLog(@"更新UI %@", [NSThread currentThread]);
}];

NSBlockOperation

  • 效果如上,直接调用block中的代码
NSBlockOperation *bp = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];
NSOperationQueue *queue = [NSOperationQueue new];
//    [queue addOperation:bp];
[bp start];
  • 只要封装的操作大于1,就会进行并发队列异步执行
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; i < 5; i++) {
        //sleep(1);
        NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread]);
    }
}];
// 设置最高优先级
bo1.qualityOfService = NSQualityOfServiceUserInitiated;
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 5; i++) {
        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);
//        if (self.finishedBlock) {
            //回到主线程更新UI
            [[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; i < 20; i++) {
        SSDownloadOperation *op = [SSDownloadOperation downloadOperationWithURLString:@"abc.jpg" andFinishedBlock:^(NSString *img) {
        //图片下载完成更新ui
        NSLog(@"更新UI %@ %@ %d", img, [NSThread currentThread], i);
    }];
    [self.queue addOperation:op];
}