美文网首页
iOS 多线程-基础篇

iOS 多线程-基础篇

作者: Jey | 来源:发表于2018-12-03 17:22 被阅读10次

    学习线程之前我们先温习下

    进程: 屏幕快照 2018-12-01 下午2.52.13.png 线程: 屏幕快照 2018-12-01 下午2.54.40.png

    多线程的原理:同一时间,cpu只能处理一条线程,只有一条线程在工作,多线程并发执行,其实是cpu快速的在多条线程间切换。
    优点:1、提高程序的执行效率
    2、提高cpu的利用率
    缺点: 线程多了就会消耗大量的cpu资源,降低程序性能,线程调度频率也会降低,同时耗电。

    多线程.png

    一、 NSTread介绍

    是苹果官方提供的,使用起来比 pthread 更加面向对象,简单易用,可以直接操作线程对象。不过也需要需要程序员自己管理线程的生命周期。

    //使用target对象的selector作为线程的任务执行体,该selector方法最多可以接收一个参数,该参数即为argument
    - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
    
    //使用block作为线程的任务执行体
    - (instancetype)initWithBlock:(void (^)(void))block
    
    /*
    类方法,返回值为void
    使用一个block作为线程的执行体,并直接启动线程
    上面的实例方法返回NSThread对象需要手动调用start方法来启动线程执行任务
    */
    + (void)detachNewThreadWithBlock:(void (^)(void))block
    
    /*
    类方法,返回值为void
    使用target对象的selector作为线程的任务执行体,该selector方法最多接收一个参数,该参数即为argument
    同样的,该方法创建完县城后会自动启动线程不需要手动触发
    */
    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
    
    NSTread子线程例子
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(firstThread:) object:@"Hello, World"];
    //设置线程的名字,方便查看
    [thread setName:@"firstThread"];
    //启动线程
    [thread start];
    }
    
    //线程的任务执行体并接收一个参数arg
    - (void)firstThread:(id)arg
    {
    NSLog(@"Task %@ %@", [NSThread currentThread], arg);
    NSLog(@"Thread Task Complete");
    }
    
    常见API
    // 获得主线程
    + (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 Dispatch

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

    GCD任务和队列

    GCD 中两个核心概念:任务和队列

    任务

    任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的

    • 同步执行(sync)

      • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
      • 只能在当前线程中执行任务,不具备开启新线程的能力
    • 异步执行(async)

      • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
      • 可以在新的线程中执行任务,具备开启新线程的能力。
    // 同步执行任务创建方法
    dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
    });
    // 异步执行任务创建方法
    dispatch_async(queue, ^{
    // 这里放异步执行任务代码
    });
    

    队列

    队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务

    串行队列(Serial Dispatch Queue)

    每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

    并发队列(Concurrent Dispatch Queue)

    可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
    并发队列的并发功能只有在异步(dispatch_async)函数下才有效

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

    可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。

    // 串行队列的创建方法
    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);
    

    对于串行队列,GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)

    所有放在主队列中的任务,都会放到主线程中执行
    可使用dispatch_get_main_queue()获得主队列。

    // 主队列的获取方法
    dispatch_queue_t queue = dispatch_get_main_queue();
    对于并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)
    

    可以使用dispatch_get_global_queue来获取。需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。

    // 全局并发队列的获取方法
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    GCD 的基本使用

    同步串行队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
        dispatch_sync(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                NSLog(@"1---%@",[NSThread currentThread]);
            }
        });
        
        dispatch_sync(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                NSLog(@"2---%@",[NSThread currentThread]);
            }
        });
    
    2018-12-03 15:58:42.790220+0800 001--Block[4215:271492] 1---<NSThread: 0x604000076a40>{number = 1, name = main}
    2018-12-03 15:58:42.790508+0800 001--Block[4215:271492] 1---<NSThread: 0x604000076a40>{number = 1, name = main}
    2018-12-03 15:58:42.790670+0800 001--Block[4215:271492] 2---<NSThread: 0x604000076a40>{number = 1, name = main}
    2018-12-03 15:58:42.790809+0800 001--Block[4215:271492] 2---<NSThread: 0x604000076a40>{number = 1, name = main}
    

    根据打印结果可知,同步串行队列即没有开启新的线程,也没有异步执行

    同步并行队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
        dispatch_sync(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                NSLog(@"1---%@",[NSThread currentThread]);
            }
        });
        
        dispatch_sync(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                NSLog(@"2---%@",[NSThread currentThread]);
            }
        });
    
    2018-12-03 16:00:13.963915+0800 001--Block[4250:273199] 1---<NSThread: 0x600000076500>{number = 1, name = main}
    2018-12-03 16:00:13.964146+0800 001--Block[4250:273199] 1---<NSThread: 0x600000076500>{number = 1, name = main}
    2018-12-03 16:00:13.964329+0800 001--Block[4250:273199] 2---<NSThread: 0x600000076500>{number = 1, name = main}
    2018-12-03 16:00:13.964605+0800 001--Block[4250:273199] 2---<NSThread: 0x600000076500>{number = 1, name = main}
    

    根据两种打印我们发现:同步函数既不会开启新的线程,也不会执行并发任务

    异步串行队列
    NSLog(@"主线程:%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; ++i) {
                NSLog(@"1====%@",[NSThread currentThread]);      // 打印当前线程
            }
            
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; ++i) {
                NSLog(@"2====%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
    2018-12-03 16:06:46.425846+0800 001--Block[4371:279078] 主线程:<NSThread: 0x60000007a300>{number = 1, name = main}
    2018-12-03 16:06:46.426329+0800 001--Block[4371:279184] 1====<NSThread: 0x604000464fc0>{number = 3, name = (null)}
    2018-12-03 16:06:46.426596+0800 001--Block[4371:279184] 1====<NSThread: 0x604000464fc0>{number = 3, name = (null)}
    2018-12-03 16:06:46.426860+0800 001--Block[4371:279184] 2====<NSThread: 0x604000464fc0>{number = 3, name = (null)}
    2018-12-03 16:06:46.427033+0800 001--Block[4371:279184] 2====<NSThread: 0x604000464fc0>{number = 3, name = (null)}
    

    结果:有开启新的线程,串行执行任务

    异步并行队列
    NSLog(@"主线程:%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; ++i) {
                NSLog(@"1====%@",[NSThread currentThread]);      // 打印当前线程
            }
            
        });
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; ++i) {
                NSLog(@"2====%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    
    2018-12-03 16:04:29.192038+0800 001--Block[4310:276444] 主线程:<NSThread: 0x6000000732c0>{number = 1, name = main}
    2018-12-03 16:04:29.192416+0800 001--Block[4310:276688] 1====<NSThread: 0x60400027cf40>{number = 3, name = (null)}
    2018-12-03 16:04:29.192436+0800 001--Block[4310:276690] 2====<NSThread: 0x60400027c340>{number = 4, name = (null)}
    2018-12-03 16:04:29.192603+0800 001--Block[4310:276688] 1====<NSThread: 0x60400027cf40>{number = 3, name = (null)}
    2018-12-03 16:04:29.192609+0800 001--Block[4310:276690] 2====<NSThread: 0x60400027c340>{number = 4, name = (null)}
    

    结果:有开启新的线程,并发执行任务。想要出现明显的并发执行效果,可以sleep

    死锁问题

    【问题1】
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
    NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");
    
    输出:2018-12-03 16:21:09.388970+0800 001--Block[4468:285688] 执行任务1
    (lldb) 
    

    死锁,任务3执行结束才会执行任务2;而主线程中是执行完sync才会执行任务3,造成互相等待死锁。

    修改成异步,就不会死锁了,如下
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
    NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");
    
    【问题2】
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
    NSLog(@"执行任务2");
    
    dispatch_sync(queue, ^{ // 1
    NSLog(@"执行任务3");
    });
    
    NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");
    
    输出:
    2018-12-03 16:24:08.183248+0800 001--Block[4517:288411] 执行任务1
    2018-12-03 16:24:08.183479+0800 001--Block[4517:288411] 执行任务5
    2018-12-03 16:24:08.183497+0800 001--Block[4517:288659] 执行任务2
    (lldb) 
    

    看打印是死锁了,执行任务3和执行任务4之间造成死锁。

    修改
    NSLog(@"执行任务1");
        dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{ // 0
            NSLog(@"执行任务2");
            
            dispatch_sync(queue, ^{ // 1
                NSLog(@"执行任务3");
            });
            
            NSLog(@"执行任务4");
        });
        
        NSLog(@"执行任务5");
    
    GCD 栅栏方法:dispatch_barrier_async

    在异步执行一些操作的时候,我们使用dispatch_barrier_async函数把异步操作暂时性的做成同步操作,就行一个栅栏一样分开

    @property(nonatomic, strong) dispatch_queue_t queue;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 10; i++) {
            [self read];
            [self read];
            [self read];
            [self write];
        }
    }
    
    - (void)read {
        dispatch_async(self.queue, ^{
            sleep(1);
            NSLog(@"read");
        });
    }
    
    - (void)write
    {
        dispatch_barrier_async(self.queue, ^{
            sleep(1);
            NSLog(@"write");
        });
    }
    
    输出
    2018-12-03 16:42:26.268215+0800 001--Block[4828:304463] read
    2018-12-03 16:42:26.268216+0800 001--Block[4828:304464] read
    2018-12-03 16:42:26.268216+0800 001--Block[4828:304462] read
    2018-12-03 16:42:26.268521+0800 001--Block[4828:304462] write
    2018-12-03 16:42:26.268734+0800 001--Block[4828:304462] read
    2018-12-03 16:42:26.268738+0800 001--Block[4828:304464] read
    2018-12-03 16:42:26.268756+0800 001--Block[4828:304463] read
    2018-12-03 16:42:26.269386+0800 001--Block[4828:304463] write
    2018-12-03 16:42:26.269962+0800 001--Block[4828:304463] read
    2018-12-03 16:42:26.269967+0800 001--Block[4828:304464] read
    2018-12-03 16:42:26.269979+0800 001--Block[4828:304462] read
    2018-12-03 16:42:26.271003+0800 001--Block[4828:304462] write
    2018-12-03 16:42:26.271579+0800 001--Block[4828:304462] read
    
    GCD 延时执行方法: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
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
    });
    
    GCD 队列组:dispatch_group

    有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组

    调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合 来实现dispatch_group_async。
    调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。
    dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1
    dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1
    当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到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");
        });
    }
    
    输出:
    2018-12-03 16:53:17.192618+0800 001--Block[4916:310729] currentThread---<NSThread: 0x604000078d80>{number = 1, name = main}
    2018-12-03 16:53:17.192842+0800 001--Block[4916:310729] group---begin
    2018-12-03 16:53:19.198080+0800 001--Block[4916:310976] 1---<NSThread: 0x600000274900>{number = 4, name = (null)}
    2018-12-03 16:53:19.198079+0800 001--Block[4916:310975] 2---<NSThread: 0x604000462300>{number = 3, name = (null)}
    2018-12-03 16:53:21.203811+0800 001--Block[4916:310975] 2---<NSThread: 0x604000462300>{number = 3, name = (null)}
    2018-12-03 16:53:21.203844+0800 001--Block[4916:310976] 1---<NSThread: 0x600000274900>{number = 4, name = (null)}
    2018-12-03 16:53:23.205322+0800 001--Block[4916:310729] 3---<NSThread: 0x604000078d80>{number = 1, name = main}
    2018-12-03 16:53:25.206537+0800 001--Block[4916:310729] 3---<NSThread: 0x604000078d80>{number = 1, name = main}
    2018-12-03 16:53:25.206825+0800 001--Block[4916:310729] group---end
    

    NSOperation介绍

    NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象
    NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行

    好处:
    1、可添加完成的代码块,在操作完成后执行
    2、添加操作之间的依赖关系,方便的控制执行顺序
    3、设定操作执行的优先级
    4、可以很方便的取消一个操作的执行
    5、使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled
    既然是基于 GCD 的更高一层的封装。那么,GCD 中的一些概念同样适用于 NSOperation、NSOperationQueue。在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念

    NSOperation常用属性和方法

    1、开始取消操作

    • (void)start:对于并发Operation需要重写该方法,也可以不把operation加入到队列中,手动触发执行,与调用普通方法一样
    • (void)main:非并发Operation需要重写该方法
    • (void)cancel:可取消操作,实质是标记 isCancelled 状态

    2、判断操作状态方法

    • (BOOL)isFinished; 判断操作是否已经结束
    • (BOOL)isCancelled 判断操作是否已经标记为取消
    • (BOOL)isExecuting;判断操作是否正在在运行
    • (BOOL)isReady;判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。

    3、操作同步

    • (void)waitUntilFinished;阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步
    • (void)setCompletionBlock:(void (^)(void))block; 会在当前操作执行完毕时执行 completionBlock
    • (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成
    • (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
      @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

    NSOperationQueue 常用属性和方法

    • 1、取消/暂停/恢复操作

      • - (void)cancelAllOperations; 可以取消队列的所有操作
      • - (BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态
      • - (void)setSuspended:(BOOL)b; 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列
    • 2、操作同步

      • - (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕。
    • 3、添加/获取操作

      • - (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象
      • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
      • - (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)
      • - (NSUInteger)operationCount; 当前队列中的操作数
    • 4、获取队列

      • + (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
      • + (id)mainQueue; 获取主队列。

    简单使用

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

    实现步骤

    1、创建操作:先将需要执行的操作封装到一个 NSOperation 对象中
    2、创建队列:创建 NSOperationQueue 对象
    3、将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中
    NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作

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

    使用子类 NSInvocationOperation
    - (void)Operation1{
        //1、创建NSInvocationOperation对象
        NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
        //2、开始调用
        [op start];
    }
    - (void)test{
        for (NSInteger i = 0; i < 2; i++) {
            NSLog(@"当前线程:%@",[NSThread currentThread]);
        }
    }
    
    2018-12-03 17:09:59.378583+0800 001--Block[5018:320650] 当前线程:<NSThread: 0x60400006b540>{number = 1, name = main}
    2018-12-03 17:09:59.378855+0800 001--Block[5018:320650] 当前线程:<NSThread: 0x60400006b540>{number = 1, name = main}
    

    总结:在没有使用 NSOperationQueue、在主线程中单独使用使用子类 NSBlockOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。

    将操作加入到队列中

    NSOperation 需要配合 NSOperationQueue 来实现多线程,总共有两种方法:

    1、- (void)addOperation:(NSOperation *)op; 需要先创建操作,再将创建好的操作加入到创建好的队列中去
    2、- (void)addOperationWithBlock:(void (^)(void))block; 无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。

    addOperation
    - (void)Operation4{
        //1、创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        
        //2、创建操作
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程1:%@",[NSThread currentThread]);
        }];
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程2:%@",[NSThread currentThread]);
        }];
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程3:%@",[NSThread currentThread]);
        }];
        
        //3、添加操作
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
    }
    
    2018-12-03 17:14:48.844950+0800 001--Block[5078:324522] 当前线程1:<NSThread: 0x600000470000>{number = 4, name = (null)}
    2018-12-03 17:14:48.844951+0800 001--Block[5078:324525] 当前线程3:<NSThread: 0x604000275980>{number = 5, name = (null)}
    2018-12-03 17:14:48.844950+0800 001--Block[5078:324523] 当前线程2:<NSThread: 0x60000007afc0>{number = 3, name = (null)}
    
    addOperationWithBlock

    无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。

    - (void)Operation5{
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        [queue addOperationWithBlock:^{
            NSLog(@"当前线程1:%@",[NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"当前线程2:%@",[NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"当前线程3:%@",[NSThread currentThread]);
        }];
    }
    

    这段代码和上面效果是一样的。

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        queue.maxConcurrentOperationCount = 1; // 串行队列
        queue.maxConcurrentOperationCount = 2; // 并发队列,一次只能执行两个并发队列
        [queue addOperationWithBlock:^{
            sleep(1);
            NSLog(@"当前线程1:%@",[NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            sleep(1);
            NSLog(@"当前线程2:%@",[NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"当前线程3:%@",[NSThread currentThread]);
        }];
    

    设置最大并发量,1,2执行完了才会到3

    NSOperation 操作依赖

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

    • (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
    • (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
      @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组
    - (void)Operation6{
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        //2、创建操作
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:1];
            NSLog(@"当前线程1:%@",[NSThread currentThread]);
        }];
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"当前线程2:%@",[NSThread currentThread]);
        }];
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"当前线程3:%@",[NSThread currentThread]);
        }];
        
        //3、添加依赖
        [op1 addDependency:op2];
        [op1 addDependency:op3];
        //4、添加操作
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
    }
    
    2018-12-03 17:22:02.254933+0800 001--Block[5286:333230] 当前线程3:<NSThread: 0x604000460600>{number = 3, name = (null)}
    2018-12-03 17:22:04.259007+0800 001--Block[5286:333238] 当前线程2:<NSThread: 0x604000462440>{number = 4, name = (null)}
    2018-12-03 17:22:05.264331+0800 001--Block[5286:333230] 当前线程1:<NSThread: 0x604000460600>{number = 3, name = (null)}
    

    相关文章

      网友评论

          本文标题:iOS 多线程-基础篇

          本文链接:https://www.haomeiwen.com/subject/gcfrcqtx.html