iOS 多线程(NNSThread,GCD,NSOperation)

线程基本概念

线程是进程内假想的持有 cpu 使用权的执行单位,一个进程下可以创建多个线程并行执行;使用多线程的程序称为多线程运行,从程序开始执行是运行的程序成为主线程,除此之外之后生成的线程为次线程或子线程。

线程安全和注意点

多个线程操作某个实例时,没有得到错误的结果或实例时,那么该类就称为线程安全。结果不能保证时,则称为非线程安全。

一般情况下,常数对象是线程安全的,变量对象不是线程安全的。

要想使用多线程不出错且高效执行,并行编程的知识必不可少,线程间的任务分配和信息交换、共享资源的互斥、与 GUI 的交互以及动画显示等,使用时都要格外小心。

iOS 多线程对比

NSThread

简介

NSThread 是苹果官方提供的面向对象操作线程技术,简单方便,可以直接操作对象,不过需要自己控制线程的生命周期,在平时较少使用。初始化创建 NSThread 的方法有如下几种:

/*
使用target对象的selector作为线程的任务执行体,该selector方法最多可以接收一个参数,该参数即为argument
*/
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

/*
使用block作为线程的任务执行体
*/
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

/*
类方法,返回值为void
使用一个block作为线程的执行体,并直接启动线程
上面的实例方法返回NSThread对象需要手动调用start方法来启动线程执行任务
*/
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

/*
类方法,返回值为void
使用target对象的selector作为线程的任务执行体,该selector方法最多接收一个参数,该参数即为argument
同样的,该方法创建完县城后会自动启动线程不需要手动触发
*/
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

线程的相关用法和线程状态的控制方法

// 获得主线程
+ (NSThread *)mainThread;    

// 判断是否为主线程(对象方法)
- (BOOL)isMainThread;

// 判断是否为主线程(类方法)
+ (BOOL)isMainThread;    

// 获得当前线程
NSThread *current = [NSThread currentThread];

// 线程的名字——setter方法
- (void)setName:(NSString *)n;    

// 线程的名字——getter方法
- (NSString *)name;    

// 线程进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
- (void)start;


// 线程进入阻塞状态
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 线程进入死亡状态
+ (void)exit;

线程之间的通信

// 在主线程上执行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;
  // 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);

// 在当前线程上执行操作,调用 NSObject 的 performSelector:相关方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

GCD

概念

Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。

优点

  • GCD 可用于多核的并行运算
  • GCD 会自动利用更多的 CPU 内核(比如双核、四核)
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

GCD 的使用

1、创建队列(串行队列或并发队列)
2、将任务追加到队列中,系统根据任务类型执行任务(同步或者异步)

队列的创建方法/获取方法

// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

任务的创建方法

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

GCD的基本使用

我们可以看到,GCD 有两种创建任务的方法:同步或异步;三种队列:并发队列、串行队列和主队列,一共有六种的组合方式,我们逐个进行分析:

#pragma mark ------------------------GCD 基本使用(六种不同的组合)
#pragma mark -----------------------异步执行主队列:在主线程中串行执行任务
- (void)asyncMain {
    NSLog(@"mainThread---%@",[NSThread currentThread]); // 打印当前线程
    NSLog(@"asyncSerial---begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        for(int i=0;i<2;i++){//        任务1
            [NSThread sleepForTimeInterval:2];//模拟耗时操作
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任务2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任务3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"main---end");
}

#pragma mark -----------------------同步执行主队列
//在主线程中使用 同步执行主队列,程序会出现死锁
- (void)syncMain {
    NSLog(@"mainThread---%@",[NSThread currentThread]); // 打印当前线程
    NSLog(@"syncSerial---begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        for(int i=0;i<2;i++){//        任务1
            [NSThread sleepForTimeInterval:2];//模拟耗时操作
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任务2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任务3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"main---end");
}



#pragma mark -----------------------异步执行串行对类:开启新线程,在当前线程下串行执行任务,任务不做等待
- (void)asyncSerial {
    NSLog(@"serialThread---%@",[NSThread currentThread]); // 打印当前线程
    NSLog(@"asyncSerial---begin");
    dispatch_queue_t queue = dispatch_queue_create("serial.queue.test", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        //        任务1
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];//模拟耗时操作
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任务2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任务3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"asyncSerial---end");
}

#pragma mark -----------------------同步执行串行对类:不开启新线程,在当前线程下串行执行任务
- (void)syncSerial {
    NSLog(@"serialThread---%@",[NSThread currentThread]); // 打印当前线程
    NSLog(@"syncSerial---begin");
    dispatch_queue_t queue = dispatch_queue_create("serial.queue.test", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        //        任务1
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];//模拟耗时操作
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任务2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任务3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncSerial---end");
}



#pragma mark -------------------------异步执行并发队列:开启多个线程,任务交替(同时)执行
- (void)asyncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
    NSLog(@"asyncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue.test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        //        任务1
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];//模拟耗时操作
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任务2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任务3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"asyncConcurrent---end");
}
#pragma mark ------------------------同步执行并发队列:不开启新线程,执行完一个任务在执行下一个任务,因为只有一个线程
- (void)syncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue.test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
//        任务1
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];//模拟耗时操作
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任务2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任务3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncConcurrent---end");
}

线程间的通信

/**
 * 线程间通信
 */
- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
    
    dispatch_async(queue, ^{
        // 异步追加任务
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
        
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });
}

GCD 栅栏方法:dispatch_barrier_async

我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"barrier---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
}

GCD 延时执行方法:dispatch_after

/**
 * 延时执行方法 dispatch_after
 */
- (void)after {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncMain---begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0秒后异步追加任务代码到主队列,并开始执行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
    });
}

GCD 一次性代码(只执行一次):dispatch_once

/**
 * 一次性代码(只执行一次)dispatch_once
 */
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行1次的代码(这里面默认是线程安全的)
    });
}

GCD 快速迭代方法:dispatch_apply

  • 通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数dispatch_apply。dispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
  • dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
  • 无论是在串行队列,还是异步队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。

GCD 队列组:dispatch_group

dispatch_group_notify

监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。

/**
 * 队列组 dispatch_group_notify
 */
- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
        NSLog(@"group---end");
    });
}

dispatch_group_wait

暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。

队列组 dispatch_group_wait

-(void)groupWait {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
}

dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1
  • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1。
  • 当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。
- (void)groupEnterAndLeave
{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程.
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
        NSLog(@"group---end");
    });
    
    //    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    //    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    //
    //    NSLog(@"group---end");
}

Dispatch Semaphore 线程同步

使用Dispatch Semaphore 可以实现线程同步,将异步执行任务转换为同步执行任务。

- (void)semaphoreSync {
    
    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 int number = 0;
    dispatch_async(queue, ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}

NSOperation

简介

NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。

NSOperation、NSOperationQueue 使用步骤

NSOperation 需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行。

NSOperation 实现多线程的使用步骤分为三步:

  • 创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。
  • 创建队列:创建 NSOperationQueue 对象。
  • 将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。

NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有三种方式来封装操作。

使用子类 NSInvocationOperation
使用子类 NSBlockOperation
自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作。

使用子类 NSInvocationOperation

/**
 * 使用子类 NSInvocationOperation
 */
- (void)useInvocationOperation {

    // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 2.调用 start 方法开始执行操作
    [op start];
}

/**
 * 任务1
 */
- (void)task1 {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
}

使用子类 NSBlockOperation

/**
 * 使用子类 NSBlockOperation
 */
- (void)useBlockOperation {

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

    // 2.调用 start 方法开始执行操作
    [op start];
}

addExecutionBlock :

NSBlockOperation 还提供了一个方法 addExecutionBlock:通过 addExecutionBlock: 就可以为 NSBlockOperation 添加额外的操作。

NSOperationQueue

NSOperationQueue 一共有两种队列:主队列、自定义队列。其中自定义队列同时包含了串行、并发功能。下边是主队列、自定义队列的基本创建方法和特点。

// 主队列获取方法 队列中代码在主线程运行
NSOperationQueue *queue = [NSOperationQueue mainQueue];

// 自定义队列创建方法  队列中代码在子线程运行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

将操作加入到队列中

/**
 * 使用 addOperation: 将操作加入到操作队列中
 */
- (void)addOperationToQueue {

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

    // 2.创建操作
    // 使用 NSInvocationOperation 创建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 使用 NSInvocationOperation 创建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

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

    // 3.使用 addOperation: 添加所有操作到队列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
}

NSOperationQueue 控制串行执行、并发执行

最大并发操作数:maxConcurrentOperationCount

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

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

    // 2.设置最大并发操作数
    queue.maxConcurrentOperationCount = 1; // 串行队列
// queue.maxConcurrentOperationCount = 2; // 并发队列
// queue.maxConcurrentOperationCount = 8; // 并发队列

    // 3.添加操作
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
}

NSOperation 操作依赖

NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序

  • -(void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
  • -(void)removeDependency:(NSOperation )op; 移除依赖,取消当前操 作对操作 op 的依赖。
  • @property (readonly, copy) NSArray<NSOperation > dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

例:比如说有 A、B 两个操作,其中 A 执行完操作,B 才能执行操作。

- (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];

NSOperation、NSOperationQueue 线程间的通信

- (void)communication {

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

    // 2.添加操作
    [queue addOperationWithBlock:^{
        // 异步进行耗时操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }

        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 进行一些 UI 刷新等操作
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
            }
        }];
    }];
}

线程安全

把这一块单独提出来是因为无论是使用 NSThread、GCD、NSOperation 等,在多个地方异步同时调用同一方法,会造成结果不符合预期,也就是线程不安全。

线程不安全解决方案:可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。iOS 实现线程加锁有很多种方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) 等等各种方式,@synchronized、 NSLock这两种方式比较常用。

银行取钱案例(例子是使用NSThread开辟的线程,给执行的代码加锁(或同步代码块),其他两种多线程方式同理)

- (void)getMoney {
    Account *account = [[Account alloc] init];
    account.accountNumber = @"1603121434";
    account.balance = 1500.0;
    
    NSThread *thread1 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)];
    [thread1 setName:@"Thread1"];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)];
    [thread2 setName:@"Thread2"];
    
    [thread1 start];
    [thread2 start];
}

- (void)draw:(id)money
{
//    当多个线程同时操作的时候,会存在竞争条件,数据结果就无法保证
//    double drawMoney = [money doubleValue];
//    //判断余额是否足够
//    if (self.balance >= drawMoney)
//    {
//        //当前线程睡1毫秒
//        [NSThread sleepForTimeInterval:0.001];
//        self.balance -= drawMoney;
//        NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance);
//    }else{
//        //余额不足,提示
//        NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]);
//    }
    
    
//    我们对draw:方法添加了一个同步代码块,使用@synchronized包围的代码即为同步代码块,同步代码块需要一个监听器,我们使用account对象本身作为监听器,因为是account对象产生的竞争条件,当执行同步代码块时需要先获取监听器,如果获取不到则线程会被阻塞,当同步代码块执行完成则释放监听器
//    @synchronized (self) {
//        double drawMoney = [money doubleValue];
//        if (self.balance >= drawMoney)
//        {
//            //当前线程睡1毫秒
//            [NSThread sleepForTimeInterval:1];
//            self.balance -= drawMoney;
//            NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance);
//        }else{
//            //余额不足,提示
//            NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]);
//        }
//    }
    
    
    
//    我们使用锁机制,创建了一个NSLock类的锁对象,lock方法用于获取锁,如果锁被其他对象占用则线程被阻塞,unlock方法用于释放锁,以便其他线程加锁。
    [self.lock lock];
    double drawMoney = [money doubleValue];
    if (self.balance >= drawMoney)
    {
        //当前线程睡1毫秒
        [NSThread sleepForTimeInterval:1];
        self.balance -= drawMoney;
        NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance);
    }else{
        //余额不足,提示
        NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]);
    }
    [self.lock unlock];
}

本文所涉及的代码:threads

相关推荐