多线程基础

什么是线程、多线程?

在学习iOS多线程应用之前,我们先来学习一下什么是线程?

  • 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程的实际运作单位,一条线程指的是进程中一个单一顺序的控制流。
  • 系统中正在运行的每一个应用程序都是一个进程,系统会为每个进程分配独立的内存空间。而一个进程中的所有任务都是在线程中执行的,因此每个进程至少得有一个线程,这也就是我们平常所说的主线程。
  • 一个进程可以开启多条线程,多条线程并行执行不同的任务,这就是多线程。
  • 说到多线程,就不得不提CPU,CPU在任意时刻只能执行一条机器指令。而线程只有获取到CPU的使用权才能执行指令。
  • 多线程并发运行,其实是CPU(单核)快速在多条线程之间调度,由于调度线程的时间足够快,所以就造成了多线程并发执行的假象。调度线程的时间其实就是CPU分配给每个线程可以运行的一段时间,称为时间片。同时为了提高CPU的执行效率,系统采用了时间片轮转调度算法来进行线程调度。

以上线程调度说的是单核设备,多核设备可以通过并行来同时执行多个线程

iOS中常见的多线程方案

在iOS中有四种多线程方案,对比如下

方案 简介 语言 线程生命周期 使用频率
pthread 一套通用的多线程API适用于Unix\Linux\Windows等系统跨平台\可移植使用难度大 C 开发者手动管理 几乎不用
NSThread 底层是pthread 使用更加面向对象 使用方便,可以执行操作线程对象 OC 开发者手动管理 偶尔使用
GCD 替代NSThread 充分利用设备的多核 C 自动管理 常用
NSOperation 对GCD的封装 使用更加面向对象 增加了一些使用功能 OC 自动管理 常用

pthread

pthread是基于c语言的一套多线程API,正是因为底层是C语言,所以pthread能够在不同的操作系统上使用,移植性很强。但是pthread使用起来特别麻烦,而且需要手动管理线程的声明周期,因此基本很少使用,此处也不做过多介绍。

NSThread

NSThread是苹果官方提供的一套操作线程的API,它是面向对象的,并且是轻量级的,使用灵活。但是和pthread一样,NSThread也需要开发者手动管理线程的生命周期。因此也很少使用,但是NSThread提供了一些非常实用的方法

#pragma mark - 线程创建
//获取当前线程
 +(NSThread *)currentThread; 
//创建线程后自动启动线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
//线程休眠,可设置休眠结束时间
+ (void)sleepUntilDate:(NSDate *)date;
//线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//退出线程
+ (void)exit;
// 获得主线程
+ (NSThread *)mainThread;
//初始化方法
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0);
//是否正在执行
- (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
//是否执行完成
- (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
//是否取消线程
- (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//线程启动
- (void)start NS_AVAILABLE(10_5, 2_0);

#pragma mark - 线程通信
//与主线程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
  // equivalent to the first method with kCFRunLoopCommonModes
//与其他子线程通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
  // equivalent to the first method with kCFRunLoopCommonModes
//隐式创建并启动线程
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5, 2_0);

NSThread的用法也非常简单,这里不做介绍,有兴趣的同学可以根据系统提供的API去进行尝试。

  • NSThread在平常开发中也有使用,例如我们经常使用[NSThread currentThread]来获取当前线程,使用[NSThread mainThread]来获取主线程。线程保活也是基于NSThread和RunLoop来实现的。

GCD(重点介绍)

GCD是苹果为解决多核设备并行运算而提出的解决方案,它会合理的利用CPU多核的特性。并且GCD能够自动管理线程的生命周期(比如创建线程、任务调度、销毁线程等等),我们只需要告诉GCD具体要执行的任务,不需要编写任何关于线程的代码。同时GCD结合block使用更加简洁,因此在多线程开发中,GCD是首选。

任务和队列

在学习GCD之前,首先来学习两个比较重要的概念:任务和队列

任务

任务其实就是我们需要执行的操作,在GCD中,我们通常将需要执行的操作放在block中。执行任务有两种方式:同步和异步。

  • 同步:同步表示任务调用一旦开始,那么调用者必须等到任务返回之后,才能进行后续操作。同步任务是在当前线程中执行,不会开辟新的线程。
  • 异步:异步则表示任务一调用就会立即返回,不会阻碍调用者执行下一步操作。而任务实际是在新开辟的线程中执行。

因此,同步和异步最大的区别就是:是否具有开辟新线程的能力。

队列

在GCD中,队列主要分为两种:串行队列和并发队列

  • 串行队列:表示同一时间只会有一个任务执行,执行完毕后才会执行下一个任务。串行队列只会开启一个线程执行任务。
  • 并发队列:表示同一时间有多个任务在执行。这也就意味着并发队列可以开启多个线程同时执行任务。

串行队列和并发队列任务的插入方式都遵循FIFO(先进先出)原则,也就是新的任务总会插入到队列的末尾,但是串行队列中先进入队列的任务会先执行,并且等到任务执行完之后才会执行后面的任务。而并发队列则会同时执行队列中的多个任务,并且任务之间不会相互等待,任务的执行顺序和执行过程也不可预测。

GCD用法

GCD的使用步骤其实很简单,主要分为两个步骤

  • 创建队列
  • 向队列中添加任务(同步任务或异步任务)

创建队列

GCD中的队列有两种,串行队列和并发队列,除此之外,GCD还提供了两种特殊的队列,一种是主队列(其实就是一个串行队列),一种是全局队列(并发队列)。

创建队列是通过dispatch_queue_create函数,它有两个参数:

  • 第一个参数是队列的唯一标识,为char *类型,自定义的队列建议使用全局唯一的标识,防止冲突
  • 第二个参数是队列的类型,DISPATCH_QUEUE_SERIAL表示创建串行队列,DISPATCH_QUEUE_CONCURRENT表示创建并发队列。

创建队列的代码如下:

//创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//创建并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//获取全局并发队列(参数1:队列优先级  参数二:保留字段,一般传0)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

这里需要注意的是:主队列其实就是一个普通的串行队列,任何添加到主队列的任务都会在主线程中执行

同步、异步添加任务

GCD中,添加任务的方式也有两种,使用dispatch_sync创建同步任务和使用dispatch_async创建异步任务。不管是创建同步任务还是异步任务,都需要指定队列dispatch_queue_t

  • 在串行队列中添加同步和异步任务
//创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"任务1");
dispatch_async(serialQueue, ^{
    sleep(3);
    NSLog(@"任务2--%@",[NSThread currentThread]);
});
NSLog(@"任务3");
dispatch_sync(serialQueue, ^{
    sleep(1);
    NSLog(@"任务4--%@",[NSThread currentThread]);
});
NSLog(@"任务5");

最终输出的结果如下:

image.png

任务1和任务3先打印,之后才会打印任务2。执行完任务2之后,才会执行任务4,并且执行完任务4,最后才会执行任务5。由此就可以验证上文中的结论:

  1. 异步任务不会阻塞当前线程,并且异步任务是在新开辟的线程中执行。(任务1和任务3先执行,任务2后执行)
  2. 同步任务会阻塞当前线程,只有执行完同步任务之后才会执行后面的任务(执行完任务4才会执行任务5)。
  3. 串行队列中的任务遵循FIFO(先进先出)原则,先添加进去的任务先执行,并且前面的任务执行完成之后才会执行后面的任务(先执行任务2,后执行任务4)。
  • 在并发队列中添加同步和异步任务
//创建并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"任务1");
dispatch_async(concurrentQueue, ^{
    NSLog(@"开始任务2");
    sleep(3);
    NSLog(@"任务2--%@",[NSThread currentThread]);
});
NSLog(@"任务3");
dispatch_sync(concurrentQueue, ^{
    NSLog(@"开始任务4");
    sleep(3);
    NSLog(@"任务4--%@",[NSThread currentThread]);
});
NSLog(@"任务5");

执行结果如下:


image.png
  1. 异步任务不会阻塞当前线程,所以任务1和任务3先执行,任务2后执行
  2. 并发队列中多个任务可以同时执行,因此任务2和任务4并发执行
  3. 异步任务会开辟新的线程,同步任务会在当前线程执行。因此任务2在子线程中执行,任务4在主线程中执行。
  4. 异步任务阻塞当前线程,因此任务4执行完成之后才会执行任务5

任务和队列组合执行效果

队列存在两种:串行队列和并发队列,加上系统提供的主队列总共三种队列(此处由于主队列中添加的任务都会在主线程中执行,因此将主队列单独作为一种特殊的队列)。

任务又分为两种:同步任务和异步任务,因此队列加任务共有6种组合,所产生的效果及对比如下:

串行队列(手动创建) 主队列 并发队列
同步任务(sync) 不会开辟新线程 串行执行任务 产生死锁 不会开辟新线程 串行执行任务
异步任务(async) 开辟新线程 串行执行任务 不会开辟新线程 串行执行任务 开辟新线程 并发执行任务
  • 只有异步任务才会开启新的线程
  • 只有异步任务添加到并发队列中,才会并发执行任务

还要注意一点:当使用sync向主队列中添加同步任务时,会产生死锁。此处暂时不考虑任务嵌套。

死锁的产生

  • 第一种:在主队列中添加同步任务会产生死锁
- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"任务1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"任务2");
    });
    NSLog(@"任务3");
}

执行图如下


image.png

首先,在执行viewDidLoad方法时,其实是将viewDidLoad添加到主队列中,因为viewDidLoad现在是在队首,所以先执行viewDidLoad方法。

viewDidLoad中有3个任务,都是在主线程中执行,当执行完任务1后,通过dispatch_sync方法又向主队列中添加了任务2(其实是整个block,这里暂且称为任务2),但是由于同步任务的特性是必现执行完且返回才能执行后面的任务,因此必须要执行完任务2才能执行后面的任务3。

此时在主队列中存在两个任务,viewDidLoad和任务2,任务2想要执行,就必须等待viewDidLoad执行完,而viewDidLoad想要执行完,必须要执行完任务2以及任务3,但是任务3想要执行,就必须执行完任务2,因此任务2在等待viewDidLoad执行完,viewDidLoad又在等待任务2执行完,从而造成死锁。

  • 第二种:在异步任务中嵌套同步任务,并且是添加到串行队列中就会产生死锁
//创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{  //此处称为block1
    NSLog(@"任务1");
    dispatch_sync(serialQueue, ^{ //此处称为block2
        NSLog(@"任务2");
    });
    NSLog(@"任务3");
});

执行图如下:

image.png

首先,通过dispatch_async添加异步任务时会开启新的线程,所以此时block1中的任务是在子线程中执行,同时因为是在串行队列中增加的异步任务,所以block1会被添加到串行队列中去,并且在队首。

在子线程中执行block1中的方法,先执行任务1,然后执行dispatch_sync方法,此时会向串行队列中增加同步任务block2,并且需要等到block2执行完成之后才会执行任务3。

此时在串行队列中存在两个任务,block1和block2,block2想要执行,就必须等待block1执行完,而block1想要执行完,必须要执行完block2以及任务3,但是任务3想要执行,又必须执行完block2,因此block1在等待block2执行完,block2又在等待block1执行完,从而造成死锁。

  • 第三种:同步任务中嵌套同步任务,并且添加到串行队列中
//创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
    NSLog(@"任务1");
    dispatch_sync(queue, ^{
            NSLog(@"任务2");
    });
    NSLog(@"任务3");
});

其实这种死锁的方式和第一种类似,同步任务还是在主线程执行,只不过被添加到了自定义的串行队列中,因此造成死锁的原因和第一种基本相同,这里不做介绍。

GCD的其它用法

栅栏方法:dispatch_barrier_async

栅栏方法主要是在多组操作之间增加栅栏,从而分割多组操作,使得各组操作之间顺序执行。例如:有两组操作,需要执行完第一组操作之后再执行第二组操作,此时就需要用到dispatch_barrier_async,代码如下:

//创建并发队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

    //任务组一
    for (int i = 0; i < 5; i++) {
        dispatch_async(concurrentQueue, ^{
            [NSThread sleepForTimeInterval:1];
            NSLog(@"执行组一任务%d",i);
        });
    }
    //栅栏方法
    dispatch_barrier_sync(concurrentQueue, ^{
        NSLog(@"栅栏方法");
    });

    //任务组二
    for (int i = 0; i < 5; i++) {
        dispatch_async(concurrentQueue, ^{
            [NSThread sleepForTimeInterval:1];
            NSLog(@"执行组二任务%d",i);
        });
    }

前提是所有的任务都需要添加到同一个队列中

执行结果如下:

image.png

可以看出任务组一中的5个任务并发执行,执行完成之后会先执行栅栏函数,最后才会执行任务组二中的所有操作,具体如下图:


image.png

还有一点需要注意的是,这个函数传入的并发队列必须是通过dispatch_queue_create手动创建的,如果传入的是一个串行或者一个全局的并发队列,那么这个函数的效果等同于dispatch_async函数

队列组:dispatch_group

队列组是一个非常实用的功能,它可以在一组异步任务都执行完成之后,再执行下一步操作。例如:有多个接口,需要等到所有的接口返回结果之后再到主线程更新UI。

队列组有三种使用方法:

  • 第一种:dispatch_group_async配合dispatch_group_notify
- (void)testGroup1{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, concurrentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"网络任务1:%@", [NSThread currentThread]);
    });

    dispatch_group_async(group, concurrentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"网络任务2:%@", [NSThread currentThread]);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"主线程更新UI:%@", [NSThread currentThread]);
    });
}
  • 第二种:dispatch_group_enter、dispatch_group_leave和dispatch_group_notify搭配使用
- (void)testGroup2{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"网络任务1:%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"网络任务2:%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"主线程更新UI:%@", [NSThread currentThread]);
    });
}
  • 第三种:dispatch_group_async和dispatch_group_wait结合使用
- (void)testGroup3{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, concurrentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"网络任务1:%@", [NSThread currentThread]);
    });

    dispatch_group_async(group, concurrentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"网络任务2:%@", [NSThread currentThread]);
    });
    //等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"主线程更新UI:%@", [NSThread currentThread]);
    });
}

以上三种方式的执行结果相同,如下:


image.png

信号量:dispatch_semaphore

信号量就是一种用来控制访问资源的数量的标识,当我们设置了一个信号量,在线程访问之前加上信号量的处理,就可以告知系统按照我们设定的信号量数量来执行多个线程。信号量其实是用计数来实现的,如果信号量计数小于0,则会一直等待,阻塞线程。如果信号量计数为0或者大于0,则不等待且计数-1。

GCD提供了三个方法来帮助我们使用信号量

函数 作用
dispatch_semaphore_create 创建信号量,初始值可以为0
dispatch_semaphore_signal 发送信号,信号量计数+1
dispatch_semaphore_wait 如果信号量>0,则使信号量-1,执行后续操作 如果信号量<=0,则会阻塞当前线程,直到信号量>0

示例代码如下:

- (void)testSemaphore{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    //信号量初始为0
    dispatch_semaphore_t seq = dispatch_semaphore_create(0);

    NSLog(@"任务1");
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2");
        //信号量+1
        dispatch_semaphore_signal(seq);
    });
    //此时信号量小于0,所以一直等待,当信号量>=0时执行后续代码
    dispatch_semaphore_wait(seq, DISPATCH_TIME_FOREVER);

    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务3");
        //信号量+1
        dispatch_semaphore_signal(seq);
    });
    //信号量-1
    dispatch_semaphore_wait(seq, DISPATCH_TIME_FOREVER);
    NSLog(@"任务4");
}

执行结果如下:


image.png

首先会执行任务1,然后往并发队列中添加异步任务,之后执行dispatch_semaphore_wait时,信号量-1,此时信号量小于0(初始为0),因此线程被阻塞,一直在此处等待。当任务2执行完成后,会调用dispatch_semaphore_signal,此时信号量+1,程序继续往下执行。

因此,信号量也可以用来实现多个异步任务顺序执行,以及多个异步任务全部执行结束之后统一执行某些操作的需求。

NSOperation

NSOperation其实是对GCD更高一层的封装,完全面向对象,使用起来比GCD更加简单易用,代码的可读性也更高。并且NSOperation也提供了一些GCD没有提供的更加实用的功能。比如:

  • 可以设置任务(operation)之间的依赖,用来控制多个异步任务顺序执行
  • 可以设置任务(operation)的优先级
  • 可以取消任务(operation)
  • 可以设置线程的最大并发数

NSOperation的子类

NSOperation是一个抽象类,不能直接使用。想要使用他的功能,就要使用它的子类NSInvocationOperation和NSBlockOperation。也可以自定义NSOperation的子类。

NSBlockOperation

NSBlockOperation是将任务存放到block中,在合适的时机进行调用。

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"任务1:%@", [NSThread currentThread]);
}];
[operation1 start];

并且NSBlockOperation还可以通过addExecutionBlock:方法添加额外操作,并且通过addExecutionBlock:添加的任务和通过blockOperationWithBlock:添加的任务可以在不同的线程中并发执行。

- (void)testBlock{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"主任务:%@",[NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"附加任务1:%@",[NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"附加任务2:%@",[NSThread currentThread]);
    }];
    [op start];
}

执行结果如下:


image.png

通过blockOperationWithBlock:创建的任务默认会在当前线程中同步执行,但是当blockOperationWithBlock:和addExecutionBlock:同时使用,并且addExecutionBlock:添加的任务足够多时,blockOperationWithBlock:创建的任务也会在子线程中执行。

通过addExecutionBlock:添加任务一定会开辟新的线程,在新线程中执行附加任务。

NSInvocationOperation

NSInvocationOperation可以指定target和selector

- (void)testOp{
    NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(opeartion) object:nil];
    [invocationOp start];
}

- (void)opeartion{
    NSLog(@"任务%@", [NSThread currentThread]);
}

默认情况下,NSInvocationOperation在调用start方法的时候不会开启线程,会在当前线程同步执行,只有当operation被添加到NSOperationQueue中才会开启新线程异步执行操作。

NSOperation依赖设置

NSOperation可以设置任务之间的依赖,使任务按照预定的依赖顺序执行

- (void)testOp{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务一:%@",[NSThread currentThread]);
    }];

    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务二:%@",[NSThread currentThread]);
    }];

    NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOp) object:nil];
    //任务二依赖任务一
    [op2 addDependency:op1];
    //任务三依赖任务二
    [op3 addDependency:op2];
    [queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
}

- (void)methond3{
    NSLog(@"任务三:%@",[NSThread currentThread]);
}

原本三个任务是并发执行,但是添加完依赖之后就变成了顺序执行,如下:


image.png

此时因为3个任务顺序执行,所以只需开辟一条线程即可。

NSOperationQueue

NSOperation中也有队列的概念,就是NSOperationQueue,通常NSOperationQueue和NSOperation会结合使用,一旦NSOperation被添加到NSOperationQueue时,会自动开辟新的线程异步执行

- (void)testOperation{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务1:%@", [NSThread currentThread]);
    }];
    [operation1 start];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务2, %@", [NSThread currentThread]);
    }];
    [queue addOperation:operation2];
}

执行结果如下:


image.png

可以看到,任务1没有添加到NSOperationQueue中,在主线程中执行,任务2添加到NSOperationQueue中,在子线程中执行。

注意:NSOperation添加到NSOperationQueue后会自动执行start方法,无需手动调用。

  • NSOperationQueue设置任务最大并发数
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//设置最大并发数
queue.maxConcurrentOperationCount = 1;
for (int i = 0; i < 5; i++) {
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务%d,%@",i, [NSThread currentThread]);
    }];
    [queue addOperation:op];
}

代码中将最大并发数设置为1,任务就会顺序执行,结果如下:


image.png
  • NSOperationQueue可以取消/挂起/恢复队列操作
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//挂起任务
queue.suspended = YES;
//恢复任务
queue.suspended = NO;
//取消队列中所有任务(已经开始的无法取消)
[queue cancelAllOperations];
  • NSOperationQueue可以通过以下方式获取主队列和当前队列
//获取当前队列
[NSOperationQueue currentQueue];
//获取主队列
[NSOperationQueue mainQueue];

NSOperation总结

NSOperation属性和方法总结

  • 操作优先级
//设置操作优先级
@property NSOperationQueuePriority queuePriority;
复制代码
  • 操作状态判断
//操作是否正在执行
@property (readonly, getter=isExecuting) BOOL executing;
//操作是否完成
@property (readonly, getter=isFinished) BOOL finished;
//操作是否是并发执行
@property (readonly, getter=isConcurrent) BOOL concurrent; 
//操作是否是异步执行
@property (readonly, getter=isAsynchronous) BOOL asynchronous;
//操作是否准备就绪
@property (readonly, getter=isReady) BOOL ready;
  • 取消操作
//操作是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled;
//取消操作
- (void)cancel;
  • 操作同步
//添加任务依赖
- (void)addDependency:(NSOperation *)op;
//移除任务依赖
- (void)removeDependency:(NSOperation *)op;
//获取当前任务的所有依赖
@property (readonly, copy) NSArray *dependencies;

//阻塞任务执行线程,直到该任务执行完成
- (void)waitUntilFinished;

//在当前任务执行完成之后调用completionBlock
@property (nullable, copy) void (^completionBlock)(void);

NSOperationQueue属性和方法总结

  • 添加任务
//添加单挑任务
- (void)addOperation:(NSOperation *)op;
//添加多个任务
- (void)addOperations:(NSArray *)ops;
//直接向队列中添加一个NSBlockOperation类型的操作
- (void)addOperationWithBlock:(void (^)(void))block;
//在队列中的所有任务都执行完成之后会执行barrier block,类似栅栏
- (void)addBarrierBlock:(void (^)(void))barrier;
  • 最大并发数
//设置最大并发数
@property NSInteger maxConcurrentOperationCount;
  • 队列状态
//挂起\恢复队列操作  YES:挂起  NO:恢复
@property (getter=isSuspended) BOOL suspended;
//取消队列中所有操作
- (void)cancelAllOperations;
//阻塞当前线程,直到队列中的操作全部执行完
- (void)waitUntilAllOperationsAreFinished;
  • 获取队列
//获取当前队列
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue;
//获取主队列
@property (class, readonly, strong) NSOperationQueue *mainQueue;

你可能感兴趣的:(多线程基础)