iOS多线程(NSThread,GCD,NSOpertion)

最近看资料熟悉了一遍iOS多线程,就整理下来做个分享和记录(如有错误,请指正!)

进入主题

说起多线程,就要从进程和线程开始
进程:进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专门且受保护的内存空间内
线程:一个进程想要执行任务,必须得有线程(一个进程至少有一个线程),一个进程的所有任务都在线程中执行

什么是多线程?

1.一个进程中可以开启多条线程,每条线程可以并行执行不同的任务
2.多线程技术可以提高程序的执行效率

多线程的原理

1.同一时间,CPU只能处理一条线程,只有一条线程再工作
2.多线程并发执行,其实就是CPU快速的再多条线程间调度
3.如果CPU在调度线程的时间足够快,就造成了多线程并发执行的假象

多线程的优缺点

优点:

1.能适当的提高程序的执行效率
2.能适当的提高资源利用率(CPU,内存利用率)

缺点:

1.创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB),栈空间(子线程512KB,主线程1MB,也可以使用setStackSize设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的时间
2.如果开启大量的线程,会降低程序的性能
3.线程越多,CPU再调度线程上的开销就越大
4.程序设计更加复杂:比如线程间的通信,多线程的数据共享

多线程在iOS开发中的应用

什么是主线程

一个iOS程序运行后,默认会开启一条线程,称为"主线程"或"UI线程"

主线程的主要作用

1.显示/刷新UI界面
2.处理UI事件(比如点击事件,滚动事件,拖拽事件等)

主线程的使用注意

1.不要讲比较耗时的操作放到主线程,耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种"卡"的体验
2.如果将耗时操作放到主线程


image.png

3.如果将耗时操作放到子线程


image.png

为什么刷新UI的操作都要放到主线程?

OC在定义属性时,有nonatomic和atomic两种选择
1.atomic : 原子属性,为setter方法加锁(默认就是atomic)
2.nonatomic: 非原子属性,不会为setter方法加锁
atomic:实际上,原子属性内部会有一把自旋锁,同一时间,只有一个线程访问,保证数据安全,但是比较消耗性能,反映到用户面前,就是界面不顺滑,但是nonatomic在多线程下又是不安全的,会造成资源掠夺,数据错乱,所以考虑到用户体验和数据安全等问题,苹果给开发者规范,更新UI的操作都在放到主线程执行,同时,iOS中UIKit库的组件也都是非原子属性

iOS中多线程的实现方案

image2.png

多线程存在什么隐患以及怎么解决?

1,资源共享
1.一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源(比如多个线程访问同一个对象,同一个变量,同一个文件)
2.当多个线程访问通一块资源时,很容易引发数据错乱和数据安全问题

解决方案:加锁(互斥锁)

image3.png

假如两个售票员同时开始售票,怎么样保证仓库的余票是正确的,
1.如果不加锁,假如余票20张,售票员1卖了1张,剩余19张,同时售票员2也卖了1张,这个时候余票也是19张,这是不对的,其实只剩余18张
2.加锁,保证数据安全
售票员1买票的时候,售票员2不能买票,必须等待1结束售票,售票员2才能去售票,这时候,2去售票的时候,余票就是19张,这样就保证了数据安全(上图也同样能说明这个问题)
具体代码实现加锁

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.tickets = 20;
    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
    t1.name = @"seller1";
    [t1 start];
    
    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
    t2.name = @"seller2";
    [t2 start];
}


- (void)sellTicket {
    while (YES) {
        //互斥锁:保证锁内的代码,同一时间只有一个线程执行
        @synchronized (self) {
            if (self.tickets > 0) {
                self.tickets --;
                NSLog(@"剩下%ld张票=====%@",(long)self.tickets,[NSThread currentThread]);
            }else {
                NSLog(@"票售空");
                break;
            }
        }

    }
}

互斥锁的使用前提:多条线程抢夺同一块资源
互斥锁的优缺点:
优点: 能有效防止因多线程抢夺资源造成的数据安全问题
缺点: 需要消耗大量的CPU资源

iOS中多线程的实现

pthread基于C,跨平台,可移植,但是难度大,而且对于iOS开发几乎不用,这里就不在介绍

1.iOS多线程开发-----NSThread(使用频率:偶尔使用)

NSThread是基于OC,使用更加面向对象,可直接操作线程对象
1个NSThread对象就代表一条线程

NSThread创建线程的3个方法

    //创建一个线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:@"thread"];
    //线程的名称(崩溃的时候可以查找崩溃原因)
    thread.name = @"testThread";
    //线程优先级(0.0-1.0,默认0.5,1优先级最高,优先级不是绝对的,只是保证CPU的调度可能性,不能根据优先级来决定线程的执行顺序)
    thread.threadPriority = 1;
//线程一启动,就会在线程thread中执行run方法
    [thread start];
  //创建一个线程
    [NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:@"thread"];
    //创建一个线程
    [self performSelectorInBackground:@selector(test) withObject:nil];

NSThread的其他用法

    //获取主线程
    [NSThread mainThread];
    //判断是否为主线程
    BOOL isMainThread = [thread isMainThread];
    //获取当前线程
    NSThread *currentThread = [NSThread currentThread];
    //阻塞线程
    [NSThread sleepForTimeInterval:2.0];
   //强行终止线程
   [NSThread exit];

线程间的通信(一个线程中执行完任务后,转到另一个线程执行任务)

    //NO:不等待,主线程执行,子线程继续执行
    //YES: 等待主线程完成,子线程才会继续执行
//转到另外一个线程
[self performSelector:@selector(setDownloadImage) onThread:thread withObject:nil waitUntilDone:NO];
//回到主线程
[self performSelectorOnMainThread:@selector(setDownloadImage) withObject:nil waitUntilDone:NO];

最简单的线程通信的例子(子线程下载图片,下载完成,回到主线程刷新UI)

- (void)viewDidLoad {
    [super viewDidLoad];
    self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.imageView];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];
    [thread start];
    
    
    
}

- (void)downloadImage {
    //耗时操作放到子线程
    NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/image/pic/item/a8ec8a13632762d0b09949edadec08fa513dc639.jpg"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    self.image = [UIImage imageWithData:data];
    //NO:不等待,主线程执行,子线程继续执行
    //YES: 等待主线程完成,子线程才会继续执行
    [self performSelectorOnMainThread:@selector(setDownloadImage) withObject:nil waitUntilDone:NO];
    
    
}

- (void)setDownloadImage {
    self.imageView.image = self.image;
}

iOS多线程开发-----GCD

什么是GCD

GCD:Grand Central Dispatch
GCDGCD是苹果公司为多核的并行运算提出的解决方案,也是基于C语言,提供了非常强大的函数
(!!!看资料说多线程的其实只有pthread和NSThread,GCD和NSOpertion只是提供的一个多核并行运算方案,不太明白,待验证)

GCD的优势

1.GCD是苹果公司为多核的并行运算提出的解决方案
2.GCD会自动利用更多的CPU内核
3.GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
4.程序员只需要告诉GCD想要执行什么任务,不需要写任何线程管理的代码

GCD的两个核心概念

1.任务(执行什么操作)
2.队列(用来存放什么任务)

GCD的使用

1.定制任务
2.将任务添加到队列
1.GCD会自动将队列中的任务取出,放到对应的线程中执行
2.任务的取出遵循FIFO原则,即 先进先出

使用GCD钱要先清楚四个概念

同步

只能在当前线程执行任务,不具备开启新线程的能力

异步

可以在新的线程中执行任务,具备开启新线程的能力

串行队列

让任务一个接一个的执行(一个任务执行完毕,再执行下一个)

并发(并行)队列

可以让多个任务并发执行(自动开启多个线程同时执行任务),并发功能只有在异步函数下有效

GCD的各种任务在队列的执行情况

1.串行队列执行同步任务(不会开辟子线程,同步执行)
 //串行队列执行同步任务
    dispatch_queue_t q = dispatch_queue_create("q", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(q, ^{
            NSLog(@"%@=======%d",[NSThread currentThread],i);
        });
    }
2018-11-14 16:55:00.674622+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======0
2018-11-14 16:55:00.674815+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======1
2018-11-14 16:55:00.674950+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======2
2018-11-14 16:55:00.675064+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======3
2018-11-14 16:55:00.675171+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======4
2018-11-14 16:55:00.675295+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======5
2018-11-14 16:55:00.675413+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======6
2018-11-14 16:55:00.675538+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======7
2018-11-14 16:55:00.675652+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======8
2018-11-14 16:55:00.675757+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======9
2.串行队列执行异步任务(会开辟一条子线程,同步执行)
 //串行队列执行异步任务
    dispatch_queue_t q = dispatch_queue_create("q", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        dispatch_async(q, ^{
            NSLog(@"%@=======%d",[NSThread currentThread],i);
        });
    }
2018-11-14 17:08:29.331567+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======0
2018-11-14 17:08:29.332030+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======1
2018-11-14 17:08:29.332268+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======2
2018-11-14 17:08:29.332546+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======3
2018-11-14 17:08:29.333324+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======4
2018-11-14 17:08:29.333495+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======5
2018-11-14 17:08:29.333670+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======6
2018-11-14 17:08:29.333815+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======7
2018-11-14 17:08:29.333944+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======8
2018-11-14 17:08:29.334566+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======9
3.并发队列执行同步任务(不会开辟新线程,同步执行)
   //并发队列执行同步任务
    dispatch_queue_t q = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(q, ^{
            NSLog(@"%@=======%d",[NSThread currentThread],i);
        });
    }
2018-11-14 17:17:28.179815+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======0
2018-11-14 17:17:28.180028+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======1
2018-11-14 17:17:28.180179+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======2
2018-11-14 17:17:28.180311+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======3
2018-11-14 17:17:28.180431+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======4
2018-11-14 17:17:28.180564+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======5
2018-11-14 17:17:28.180688+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======6
2018-11-14 17:17:28.180802+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======7
2018-11-14 17:17:28.180915+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======8
2018-11-14 17:17:28.181028+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======9
4.并发队列执行异步任务(开辟新线程,异步执行)
    //并发队列执行异步任务
    dispatch_queue_t q = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_async(q, ^{
            NSLog(@"%@=======%d",[NSThread currentThread],i);
        });
    }
2018-11-14 17:19:21.958412+0800 GCD[57493:1990212] <NSThread: 0x600002b21240>{number = 3, name = (null)}=======0
2018-11-14 17:19:21.958414+0800 GCD[57493:1990211] <NSThread: 0x600002b2fc00>{number = 6, name = (null)}=======2
2018-11-14 17:19:21.958415+0800 GCD[57493:1990209] <NSThread: 0x600002b21140>{number = 5, name = (null)}=======3
2018-11-14 17:19:21.958472+0800 GCD[57493:1990210] <NSThread: 0x600002b2fb40>{number = 4, name = (null)}=======1
2018-11-14 17:19:21.958700+0800 GCD[57493:1990211] <NSThread: 0x600002b2fc00>{number = 6, name = (null)}=======5
2018-11-14 17:19:21.958724+0800 GCD[57493:1990210] <NSThread: 0x600002b2fb40>{number = 4, name = (null)}=======7
2018-11-14 17:19:21.958744+0800 GCD[57493:1990209] <NSThread: 0x600002b21140>{number = 5, name = (null)}=======4
2018-11-14 17:19:21.958757+0800 GCD[57493:1990212] <NSThread: 0x600002b21240>{number = 3, name = (null)}=======6
2018-11-14 17:19:21.958859+0800 GCD[57493:1990211] <NSThread: 0x600002b2fc00>{number = 6, name = (null)}=======8
2018-11-14 17:19:21.958859+0800 GCD[57493:1990210] <NSThread: 0x600002b2fb40>{number = 4, name = (null)}=======9
5.全局队列执行同步任务(不会开辟新线程,同步执行)
  //全局队列执行同步任务
        /*
     
     ios8  服务质量
    QOS_CLASS_USER_INTERACTIVE    用户交互(希望线程快速执行,不要放一些耗时操作)
    QOS_CLASS_USER_INITIATED      (用户需要,不要放一些耗时操作)
    QOS_CLASS_DEFAULT             默认
    QOS_CLASS_UTILITY             使用工具(耗时操作)
    QOS_CLASS_BACKGROUND          后台
    QOS_CLASS_UNSPECIFIED         没有指定优先级
     
     ios7  调度优先级
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2      高优先级
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0   默认优先级
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)    低优先级
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后台优先级
     
     不要选择BACKGROUND   执行慢到令人发指
    */

    dispatch_queue_t q = dispatch_get_global_queue(0, 0);//0 代表默认优先级  0 保留参数,
    for (int i = 0; i < 10; i++) {
        dispatch_async(q, ^{
            NSLog(@"%@=======%d",[NSThread currentThread],i);
        });
    }
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(q, ^{
            NSLog(@"%@=======%d",[NSThread currentThread],i);
        });
    }
2018-11-14 17:57:17.473335+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======0
2018-11-14 17:57:17.473562+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======1
2018-11-14 17:57:17.473666+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======2
2018-11-14 17:57:17.473827+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======3
2018-11-14 17:57:17.473970+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======4
2018-11-14 17:57:17.474112+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======5
2018-11-14 17:57:17.474248+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======6
2018-11-14 17:57:17.474385+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======7
2018-11-14 17:57:17.474522+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======8
2018-11-14 17:57:17.474654+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======9
6.全局队列执行异步任务(开辟新线程,异步执行)
   //全局队列执行异步任务
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 10; i++) {
        dispatch_async(q, ^{
            NSLog(@"%@=======%d",[NSThread currentThread],i);
        });
    }
2018-11-14 17:55:44.488898+0800 GCD[58100:2039075] <NSThread: 0x600000d61400>{number = 4, name = (null)}=======1
2018-11-14 17:55:44.488904+0800 GCD[58100:2039074] <NSThread: 0x600000d6aa00>{number = 3, name = (null)}=======0
2018-11-14 17:55:44.488931+0800 GCD[58100:2039076] <NSThread: 0x600000d614c0>{number = 6, name = (null)}=======2
2018-11-14 17:55:44.488934+0800 GCD[58100:2039082] <NSThread: 0x600000d6ab00>{number = 5, name = (null)}=======3
2018-11-14 17:55:44.489128+0800 GCD[58100:2039075] <NSThread: 0x600000d61400>{number = 4, name = (null)}=======5
2018-11-14 17:55:44.489136+0800 GCD[58100:2039074] <NSThread: 0x600000d6aa00>{number = 3, name = (null)}=======4
2018-11-14 17:55:44.489183+0800 GCD[58100:2039082] <NSThread: 0x600000d6ab00>{number = 5, name = (null)}=======7
2018-11-14 17:55:44.489220+0800 GCD[58100:2039076] <NSThread: 0x600000d614c0>{number = 6, name = (null)}=======6
2018-11-14 17:55:44.489254+0800 GCD[58100:2039075] <NSThread: 0x600000d61400>{number = 4, name = (null)}=======8
2018-11-14 17:55:44.489314+0800 GCD[58100:2039074] <NSThread: 0x600000d6aa00>{number = 3, name = (null)}=======9

死锁(在主线程执行同步操作)

主线程是串行的,线性往下执行,在执行某一个任务的时候线程被阻塞了,等待这个任务执行完再往下执行,而这个任务(dispatch_sync)在执行时,又要求阻塞主线程,等待主线程的任务执行完再去执行block,互相等待,从而导致了互相的阻塞,也就是死锁。

    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"执行");
    });
    NSLog(@"执行结束");

总结:

同步执行:不论串行还是并行,都不会开辟子线程,都是同步执行

异步执行:会开辟子线程(开辟线程的多少,由CPU决定),(串行:顺序执行,并行:异步执行)

开不开线程,取决于执行任务的函数,同步不开,异步开

开几条线程,取决于队列,串行一条,并行多条(异步) 全局队列等同并发队列

GCD的其他应用

单例(一次执行)
    static dispatch_once_t oncetoken;
    dispatch_once(&oncetoken, ^{//代码只会被执行一次
        NSLog(@"%@",[NSThread currentThread]);
        
    });
延时操作
//延时3秒执行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
调度组(被添加到调度组的队列,都执行结束,会通知调度组,执行调度结束的操作)
    //1.队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    //2.调度组
    dispatch_group_t g = dispatch_group_create();
    //3.添加任务,将队列添加到调度组
    dispatch_group_async(g, q, ^{
        NSLog(@"1111");
    });
    dispatch_group_async(g, q, ^{
        NSLog(@"2222");
    });
    dispatch_group_async(g, q, ^{
        NSLog(@"3333");
    });
    dispatch_group_async(g, q, ^{
        NSLog(@"4444");
    });
    dispatch_group_notify(g, dispatch_get_main_queue(), ^{
        
        NSLog(@"执行结束");
    });
2018-11-15 17:37:52.263300+0800 GCD[69146:2964535] 1111
2018-11-15 17:37:52.263331+0800 GCD[69146:2964882] 2222
2018-11-15 17:37:52.263408+0800 GCD[69146:2964883] 3333
2018-11-15 17:37:52.263439+0800 GCD[69146:2964884] 4444
2018-11-15 17:37:52.263765+0800 GCD[69146:2964437] 执行结束

注意!!!

我们在工作开发中假如首页有5个接口,需要5个接口全部请求结束,再去刷新UI,能不能用上面的方式来实现?

答案是不可以,因为group的设计就是为了方便我们执行完一系列的任务之后再执行其他的任务,但是不能忽视的是,这里的任务是有要求的,这里的任务必须要是同步执行的!!如果任务是异步的,group只会执行完任务里面异步之前的代码以及分发异步任务就返回了!!也就代表分发group的当前这个任务完成了!但事实却是这个任务的一部分子任务在其他线程执行了,而且不一定已执行结束返回。我们AFN请求的操作都是异步的,所以这个是不能实现我们的需求,这时候需要dispatch_group_enter(); dispatch_group_leave();来实现

dispatch_group_enter:通知group,下面的任务马上要放到group中执行了。
dispatch_group_leave:通知group,任务完成了,该任务要从group中移除了。
dispatch_enter和dispatch_leave要成对出现,否则奔溃。
    //调度组
    dispatch_group_t g = dispatch_group_create();
    //模拟网络请求1
    dispatch_group_enter(g);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"接口1请求完成");
            dispatch_group_leave(g);
        });
    });
    
    //模拟网络请求2
    dispatch_group_enter(g);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"接口2请求完成");
            dispatch_group_leave(g);
        });
    });
    
    //模拟网络请求3
    dispatch_group_enter(g);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"接口3请求完成");
            dispatch_group_leave(g);
        });
    });
    
    //模拟网络请求4
    dispatch_group_enter(g);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"接口4请求完成");
            dispatch_group_leave(g);
        });
    });
    
    //模拟网络请求5
    dispatch_group_enter(g);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"接口5请求完成");
            dispatch_group_leave(g);
        });
    });
    dispatch_group_notify(g, dispatch_get_main_queue(), ^{
        
        NSLog(@"执行结束");
    });
2018-11-15 17:50:23.102497+0800 GCD[69395:2984377] 接口1请求完成
2018-11-15 17:50:23.102905+0800 GCD[69395:2984377] 接口2请求完成
2018-11-15 17:50:23.103377+0800 GCD[69395:2984377] 接口3请求完成
2018-11-15 17:50:23.103921+0800 GCD[69395:2984377] 接口4请求完成
2018-11-15 17:50:23.104095+0800 GCD[69395:2984377] 接口5请求完成
2018-11-15 17:50:23.104270+0800 GCD[69395:2984377] 执行结束

NSOpertion

NSOpertion也是苹果公司为多核的并行运算提出的解决方案
NSOpertion是一个抽象类,我们使用需要用他的子类NSInvocationOperation和NSBlockOperation
NSOpertion是对GCD的封装 默认是并发队列,异步操作
NSOpertion的核心概念是将操作添加到队列(对比GCD:将任务添加到队列)

对比GCD

GCD是 ios4.0 推出,主要针对多核处理器来优化的并发技术,C语言
1.GCD:将任务添加到队列,并且要指定任务的同步或者异步
2.线程间通信:dispatch_get_main_queue() 3.提供了一些NSOpertion不具备的功能(一次执行,延时操作)

NSOpertion 在ios2.0,苹果推出GCD之后,对NSOpertion进行了重写
NSOpertion:将操作(异步执行的任务)添加到并发队列,就会立刻执行
提供GCD不具备的功能:(最大并发数,队列暂停,继续,取消所有线程,线程的依赖)

操作(会在当前线程执行)
- (void)demo1 {
    //操作
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
    [op start];//会在当前线程执行
}

- (void)test {
    NSLog(@"%@",[NSThread currentThread]);
}
2018-11-15 17:32:15.237200+0800 NSOpertion[69033:2952177] <NSThread: 0x600003360900>{number = 1, name = main}

队列(操作添加到队列,默认开始异步并发) NSInvocationOperation
       NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
        NSOperationQueue *q = [[NSOperationQueue alloc] init];
        [q addOperation:op];
2018-11-15 14:52:12.420077+0800 NSOpertion[66330:2684747] <NSThread: 0x6000039afdc0>{number = 3, name = (null)}
NSBlockOperation
    NSOperationQueue *q = [[NSOperationQueue alloc] init];
    //异步,并发
    for (int i = 0; i < 5; i++) {
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@",[NSThread currentThread]);
        }];
        [q addOperation:op];
    }
    
    //简写方式
    NSOperationQueue *q = [[NSOperationQueue alloc] init];
    for (int i = 0; i < 5; i++) {
        [q addOperationWithBlock:^{
             NSLog(@"%@",[NSThread currentThread]);
        }];
    }
2018-11-15 14:54:20.747733+0800 NSOpertion[66399:2690054] <NSThread: 0x600003e65bc0>{number = 3, name = (null)}
2018-11-15 14:54:20.747910+0800 NSOpertion[66399:2690056] <NSThread: 0x600003e66240>{number = 4, name = (null)}
2018-11-15 14:54:20.747927+0800 NSOpertion[66399:2690064] <NSThread: 0x600003e66200>{number = 5, name = (null)}
2018-11-15 14:54:20.747959+0800 NSOpertion[66399:2690055] <NSThread: 0x600003e66300>{number = 6, name = (null)}
2018-11-15 14:54:20.748638+0800 NSOpertion[66399:2690054] <NSThread: 0x600003e65bc0>{number = 3, name = (null)}

最大并发数:maxConcurrentOperationCount

maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。

NSOperation 操作依赖

NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。NSOperation 提供了3个接口供我们管理和查看依赖。

  • (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
  • (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
    @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。
    当然,我们经常用到的还是添加依赖操作。现在考虑这样的需求,比如说有 A、B 两个操作,其中 A 执行完操作,B 才能执行操作。

如果使用依赖来处理的话,那么就需要让操作 B 依赖于操作 A

/**
 * 操作依赖
 * 使用方法:addDependency:
 */
- (void)addDependency {

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.创建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.添加依赖
    [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2

    // 4.添加操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}

可以看到:通过添加操作依赖,无论运行几次,其结果都是 op1 先执行,op2 后执行

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • iOS多线程编程 基本知识 1. 进程(process) 进程是指在系统中正在运行的一个应用程序,就是一段程序的执...
    陵无山阅读 5,909评论 1 14
  • 本文首发于我的个人博客:「程序员充电站」[https://itcharge.cn]文章链接:「传送门」[https...
    ITCharge阅读 344,872评论 308 1,921
  • 奇怪的举止 滑稽的表情 站在人群我就像个小丑 傻傻的被人摆弄 却还是云淡风轻 看你大笑的表情 我才醒悟 我是一个小...
    L海岩阅读 131评论 0 1
  • 今天上午师母的一句话,深度感恩是降低自己,像大卫一样,虽是君王,却说自己是神从粪堆中抬举出来,所以大卫的诗篇很多的...
    恩宠爸爸阅读 191评论 0 0
  • 子夏问曰:“‘巧笑倩兮,美目盼兮,素以为绚兮’,何谓也?”子曰:“绘事后素。”曰:“礼后乎?”子曰:“起予者商也,...
    雕琢未来阅读 262评论 0 2