iOS 多线程(NNSThread,GCD,NSOperation)

线程基本概念

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

线程安全和注意点

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

通常状况下,常数对象是线程安全的,变量对象不是线程安全的。程序员

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

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 的使用

一、建立队列(串行队列或并发队列)
二、将任务追加到队列中,系统根据任务类型执行任务(同步或者异步)安全

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

// 串行队列的建立方法
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 中,并执行任务。app

/**
 * 队列组 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(), ^{
        // 等前面的异步任务一、任务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

相关文章
相关标签/搜索