美文网首页iOS-开发
IOS 多线程的四种创建方案及比较

IOS 多线程的四种创建方案及比较

作者: 天遥love | 来源:发表于2018-10-22 11:44 被阅读0次
    简单的整理了一下,多线程的创建方式,它们之间的优缺点,以及在项目中我们在什么样的场景下选择哪一种方式。水平有限,写得比较浅显易懂,太深入的可以自己去学习、去钻研,没有什么是学不会的,前提只要你肯学。废话不多说,let's go!!!

    创建线程的的方案有pthread,NSThread,GCD,NSOperation,那么我就依次说一下每种方案有什么优缺点,及它们是怎样创建线程的。

    1.0 pthread

    a. 简介

    pthread(POSIX thread)表示跨平台的的线程接口,适用于Unix\Linux\Windows等系统,是跨平台和可移植的,使用的语言是C语言,线程的生命周期是由程序员管理的,适用难度很大,所以几乎是不用的。

    b. 创建子线程

    所用函数:
    int pthread_create(pthread_t * __restrict, const pthread_attr_t * __restrict,
            void *(*)(void *), void * __restrict);
    

    参数:
    参数1:线程的编号(地址)
    参数2:线程属性,传入指向线程属性的指针地址。
    参数3:新线程要执行的函数(任务),传入函数地址,即函数名。
    参数4: 给调用用的函数传递的参数。
    返回值:
    返回int类型的值,0表示创建新线程成功,反之,创建新线程失败,返回失败的编号。
    很多C语言框架里面并不是非零即真原则;因为他们认为成功的结果只有一个,但是失败的原因有很多。

    demo
    - (void)pthreadDemo {
        
        pthread_t ID;
        int returnValue = pthread_create( &ID, NULL, pthreadAction, NULL);
        if (returnValue == 0) {
            NSLog(@"线程创建成功");
        } else {
            NSLog(@"线程创建失败");
        }
    }
    void *pthreadAction(void *param) {
        NSLog(@"%@",[NSThread currentThread]);
        return NULL;
    }
    

    打印结果

    2018-10-18 11:09:41.738469+0800 多线程Demo[8228:2385086] 线程创建成功
    2018-10-18 11:09:41.738770+0800 多线程Demo[8228:2385731] <NSThread: 0x600000268840>{number = 3, name = (null)}
    2018-10-18 11:09:41.903999+0800 多线程Demo[8228:2385086] 线程创建成功
    2018-10-18 11:09:41.904245+0800 多线程Demo[8228:2385735] <NSThread: 0x600000260bc0>{number = 4, name = (null)}
    

    从上述结果看出,创建新线程成功。

    c.还有一点我们需要注意的是__bridge使用,例:

    NSString *ocString = @"tianyao";
    int returnValue = pthread_create( &ID, NULL, pthreadAction, (__bridge void *)(ocString));
    

    在混合开发的时候,在C和OC或者swift之间传递数据,需要使用__bridge进行桥接,它的目的就是告诉编译器怎样管理内存。因为在ARC开发模式下,OC和swift编译器在编译的时候,根据代码的结构,自动的添加retain/release/autorelease。但是,ARC 只负责管理 OC 部分的内存管理,而不负责C语言代码的内存管理。因此,如果使用的 C 语言框架出现 retain/create/copy/new 等字样的函数,大多都需要 release,否则会出现内存泄漏。
    像上述代码,在c的函数里面传递OC的字符串,就需要用到__bridge,因为c的内存需要手动释放,而OC的出了作用域就会自动释放,这样就会出问题,所以使用__bridge就是告诉编译器C函数中的OC代码的内存管理交给C处理了,你不用管了。

    2.0 NSThread

    a. 简介

    NSThread的使用更加面向对象,简单易用,可以直接操作线程对象,使用的语言是OC,但是线程的生命周期是由程序员自行管理的,所以偶尔会使用。

    b. 创建线程的三种方式

    • 对象方法创建
      手动开启线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"tianyao"]
    // 手动开启线程
    [thread start]
    
    • 类方法创建
      自动开启线程,这样的话就无法获取线程对象
    [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"tianyao"];
    
    • NSObject(NSThreadPerformAdditions) 的分类创建
      方便任何继承自NSObject的对象,都可以很容易的调用线程方法,也是无法获取线程对象
    [self performSelectorInBackground:@selector(demo:) withObject:@"tianyao"];
    

    c. 线程属性

    • name: 线程名称
      当线程执行的方法的内部出现异常时,可以记录当前和异常的线程。
    • stackSize:栈区大小
      可以通过[NSThread currentThread].stackSize = 512 * 1024设置栈区的大小(必须是4KB的倍数)。
    • isMainThread: 是否主线程
    • threadPriority: 线程优先级
      它是一个浮点数,范围是0~1.0,1.0表示最高优先级,0.0表示最低优先级,系统默认的优先级是0.5,线程的优先级并不能绝对的保证优先级高的线程执行完所有的代码,它只是提高了优先级高的代码执行代码的概率。
    • qualityOfService:服务质量(IOS8推出)
      NSQualityOfServiceUserInteractive - 用户交互,例如绘图或者处理用户事件
      NSQualityOfServiceUserInitiated - 用户需要
      NSQualityOfServiceUtility - 实用工具,用户不需要立即得到结果
      NSQualityOfServiceBackground - 后台
      NSQualityOfServiceDefault - 默认,介于用户需要和实用工具之间
    注意

    开发中最好不好修改优先级,不要相信用户交互服务质量

    3.0 GCD

    a. 简介(IOS4.0后推出)

    GCD(Grand Central Dispatch)是苹果公司为多核的并行运算提出的解决方案,会自动利用更多的CPU内核(比如双核、四核),是纯C语言的,提供了很多强大的函数,能够自动的管理线程的生命周期(创建线程、调度任务、销毁线程),在平时的工作中经常使用。

    b. GCD核心

    GCD的核心就是将任务添加到队列中。
    • 队列
      串行队列:任务按顺序有序的执行。就像单行车道,车只能一辆一辆的按照顺序行驶
      并发队列:可以让多个任务并发执行。那么并发队列就可类比于多行车道。
    • 任务
      任务的执行可分为两种方式
      同步执行:在当前线程中依次执行任务。
      异步执行:创建一个新线程来执行任务。

    c. 串行队列

    串行同步:
    - (void)serialSyncDemo {
        // 串行队列
        /*
         参数1:队列名称
         参数2:队列类型
         */
        dispatch_queue_t serialQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_SERIAL);
        for(int i = 0; i < 5; i++){
            dispatch_sync(serialQueue, ^{
                NSLog(@"%d %@", i, [NSThread currentThread]);
            });
        }
    }
    

    打印结果:

    2018-10-18 15:58:53.085081+0800 多线程Demo[8994:2663401] 0 <NSThread: 0x60400006cd80>{number = 1, name = main}
    2018-10-18 15:58:53.085400+0800 多线程Demo[8994:2663401] 1 <NSThread: 0x60400006cd80>{number = 1, name = main}
    2018-10-18 15:58:53.086193+0800 多线程Demo[8994:2663401] 2 <NSThread: 0x60400006cd80>{number = 1, name = main}
    2018-10-18 15:58:53.086402+0800 多线程Demo[8994:2663401] 3 <NSThread: 0x60400006cd80>{number = 1, name = main}
    2018-10-18 15:58:53.086700+0800 多线程Demo[8994:2663401] 4 <NSThread: 0x60400006cd80>{number = 1, name = main}
    
    得出结论:串行同步不具备开启新线程的能力,任务按照顺序执行。
    串行异步:
    - (void)serialAsyncDemo {
        dispatch_queue_t serialQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
        for(int i = 0; i < 5; i++){
            dispatch_async(serialQueue, ^{
                NSLog(@"%d %@", i, [NSThread currentThread]);
            });
        }
    }
    

    打印结果:

    2018-10-18 16:13:58.676331+0800 多线程Demo[9064:2685387] 0 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
    2018-10-18 16:13:58.678881+0800 多线程Demo[9064:2685387] 1 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
    2018-10-18 16:13:58.679375+0800 多线程Demo[9064:2685387] 2 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
    2018-10-18 16:13:58.679847+0800 多线程Demo[9064:2685387] 3 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
    2018-10-18 16:13:58.680204+0800 多线程Demo[9064:2685387] 4 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
    
    得出结论:串行异步会开启一条子线程,任务一次执行。

    d. 并行队列

    并行同步:
    - (void)concurrentSyncDemo {
        dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
        for(int i = 0; i < 5; i++){
            dispatch_sync(concurrentQueue, ^{
                NSLog(@"%d %@", i, [NSThread currentThread]);
            });
        }
    }
    

    打印结果:

    2018-10-18 16:43:00.811969+0800 多线程Demo[9161:2713449] 0 <NSThread: 0x60000007fac0>{number = 1, name = main}
    2018-10-18 16:43:00.812157+0800 多线程Demo[9161:2713449] 1 <NSThread: 0x60000007fac0>{number = 1, name = main}
    2018-10-18 16:43:00.812328+0800 多线程Demo[9161:2713449] 2 <NSThread: 0x60000007fac0>{number = 1, name = main}
    2018-10-18 16:43:00.812867+0800 多线程Demo[9161:2713449] 3 <NSThread: 0x60000007fac0>{number = 1, name = main}
    2018-10-18 16:43:00.813012+0800 多线程Demo[9161:2713449] 4 <NSThread: 0x60000007fac0>{number = 1, name = main}
    
    得出结论:并行同步不会开启新线程,任务在当前线程依次执行。
    并行异步:
    - (void)concurrentAsyncDemo {
        dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
        for(int i = 0; i < 5; i++){
            dispatch_async(concurrentQueue, ^{
                NSLog(@"%d %@", i, [NSThread currentThread]);
            });
        }
    }
    

    打印结果:

    2018-10-18 17:02:30.044475+0800 多线程Demo[9208:2730824] 2 <NSThread: 0x604000462380>{number = 4, name = (null)}
    2018-10-18 17:02:30.044706+0800 多线程Demo[9208:2730823] 1 <NSThread: 0x604000462000>{number = 5, name = (null)}
    2018-10-18 17:02:30.044829+0800 多线程Demo[9208:2730825] 0 <NSThread: 0x60000027c440>{number = 3, name = (null)}
    2018-10-18 17:02:30.045005+0800 多线程Demo[9208:2730822] 3 <NSThread: 0x60000027f140>{number = 6, name = (null)}
    2018-10-18 17:02:30.045009+0800 多线程Demo[9208:2730834] 4 <NSThread: 0x60000027f400>{number = 7, name = (null)}
    
    得出结论:并行异步会开启多条线程,任务不按顺序执行。

    e. 主队列

    主队列是专门在主线程上面调度任务的队列,会随着程序启动一起创建,不会开启新的线程,以先进先出的方式执行任务

    主队列只有当主线程空闲的时候才会调度任务,也就是说当主线程有任务正在执行时,无论主队列被添加了任何任务都不会被执行。
    主队列同步
    - (void)mainSyncDemo {
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        NSLog(@"start");
        dispatch_sync(mainQueue, ^{
            NSLog(@"%@", [NSThread currentThread]);
        });
        NSLog(@"end");
    }
    

    执行上面的代码会造成死锁,程序崩溃。因为主队列上面的代码只有当主线程空闲的时候才会执行,又因为是同步,代码按顺序执行,当主线程执行到主队列的代码的时候,主线程此时不是处于空闲的状态,所以没法执行主队列的代码,这就造成了主队列和主线程之间的相互等待,造成死锁。

    解决死锁的办法: 就是将主队列的代码放到子线程中,不让其阻碍主线程的执行,这样等主线程空闲下来的时候,就可以去执行主队列上面的代码。
    - (void)mainSyncDemo {
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        NSLog(@"start");
        dispatch_async(dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT), ^{
            dispatch_sync(mainQueue, ^{
                NSLog(@"%@", [NSThread currentThread]);
            });
        });
        NSLog(@"end");
    } 
    

    打印结果:

    2018-10-18 18:03:12.375942+0800 多线程Demo[9377:2796585] start
    2018-10-18 18:03:12.376192+0800 多线程Demo[9377:2796585] end
    2018-10-18 18:03:12.376542+0800 多线程Demo[9377:2796585] <NSThread: 0x60400006acc0>{number = 1, name = main}
    
    主队列异步
    - (void)mainAsyncDemo {
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        NSLog(@"start");
        dispatch_async(mainQueue, ^{
            NSLog(@"%@", [NSThread currentThread]);
        });
        NSLog(@"end");
    }
    

    打印结果:

    2018-10-18 18:10:35.030586+0800 多线程Demo[9435:2808199] start
    2018-10-18 18:10:35.030802+0800 多线程Demo[9435:2808199] end
    2018-10-18 18:10:35.031140+0800 多线程Demo[9435:2808199] <NSThread: 0x60000006ac40>{number = 1, name = main}
    
    得出结论:主线程异步不会创建新的线程,任务在主线程上面依次执行。

    f. 全局队列

    全局队列又叫全局并发队列,是系统为了方便程序员开发提供的,其工作状态与并发队列一致,无论 MRC & ARC 都不需要考虑释放。

    全局队列同步:
    - (void)globalSync {
        /*
        
            参数1:A quality of service 服务质量
                iOS 8.0及以后
                QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
                QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
                QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)
                QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!)
                QOS_CLASS_BACKGROUND 0x09, 后台
                QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配
                iOS 7.0及以前
                DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
                DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
                DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
                DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
            参数2:Reserved for future use 未来使用:为未来保留使用的,应该永远传入0
         
         */
        dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
        for(int i = 0; i < 5; i++){
            dispatch_sync(globalQueue, ^{
                NSLog(@"%d %@", i, [NSThread currentThread]);
            });
        }
    }
    

    打印结果:

    2018-10-19 10:56:41.119513+0800 多线程Demo[10930:3237013] 0 <NSThread: 0x600000064440>{number = 1, name = main}
    2018-10-19 10:56:41.119773+0800 多线程Demo[10930:3237013] 1 <NSThread: 0x600000064440>{number = 1, name = main}
    2018-10-19 10:56:41.120436+0800 多线程Demo[10930:3237013] 2 <NSThread: 0x600000064440>{number = 1, name = main}
    2018-10-19 10:56:41.120701+0800 多线程Demo[10930:3237013] 3 <NSThread: 0x600000064440>{number = 1, name = main}
    2018-10-19 10:56:41.120832+0800 多线程Demo[10930:3237013] 4 <NSThread: 0x600000064440>{number = 1, name = main}
    
    得出结论:全局同步不会创建新的现场,程序在当前现场按顺序执行。
    全局队列异步:
    - (void)globalAsync {
        dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
        for(int i = 0; i < 5; i++){
            dispatch_async(globalQueue, ^{
                NSLog(@"%d %@", i, [NSThread currentThread]);
            });
        }
    }
    

    打印结果:

    2018-10-19 10:58:54.635833+0800 多线程Demo[10964:3242388] 1 <NSThread: 0x604000460340>{number = 4, name = (null)}
    2018-10-19 10:58:54.635987+0800 多线程Demo[10964:3242022] 0 <NSThread: 0x604000274f80>{number = 3, name = (null)}
    2018-10-19 10:58:54.636380+0800 多线程Demo[10964:3242393] 2 <NSThread: 0x600000464b40>{number = 5, name = (null)}
    2018-10-19 10:58:54.636981+0800 多线程Demo[10964:3242394] 3 <NSThread: 0x600000464bc0>{number = 7, name = (null)}
    2018-10-19 10:58:54.637505+0800 多线程Demo[10964:3242395] 4 <NSThread: 0x60400027dc00>{number = 6, name = (null)}
    

    得出结论:全局异步会创建多条线程,任务不按顺序执行。

    g. GCD阻塞(Barrier)

    应用场景:主要用于多个异步操作完成之后,统一对非线程安全的对象(例如:NSMutableArray,NSMutableDictionary等)做处理。适用于大规模数据的I/O操作。举例说明:

    NSMutableArray *_imageArr = [NSMutableArray array];
    - (void)barrierDemo {
        dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
        for(int i = 0; i < 2500; i++){
            dispatch_async(concurrentQueue, ^{
                NSString *name = [NSString stringWithFormat:@"%02d.jpg",i%10 + 1];
                NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
                NSData *data = [NSData dataWithContentsOfURL:url];
                UIImage *img = [UIImage imageWithData:data];
                NSLog(@"%@ %@",name,[NSThread currentThread]);
                [self.imageArr addObject:img];
            });
        }
    

    执行上述代码程序会崩溃,原因是NSMutableArray是非线程安全的,如果出现两个线程同时向数组中添加对象,程序就会崩溃。解决的方案如下:

    - (void)barrierDemo {
        dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
        for(int i = 0; i < 2500; i++){
            dispatch_async(concurrentQueue, ^{
                NSString *name = [NSString stringWithFormat:@"%02d.jpg",i%10 + 1];
                NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
                NSData *data = [NSData dataWithContentsOfURL:url];
                UIImage *img = [UIImage imageWithData:data];
                NSLog(@"%@ %@",name,[NSThread currentThread]);
                dispatch_barrier_async(concurrentQueue, ^{
                    [self.imageArr addObject:img];
                });
            });
        }
    }
    

    dispatch_barrier_async可以保证同一时间内只有一条线程执行block内的代码,也就是说在其之前添加的block全部执行完毕之后,才在同一个线程顺序执行,从而保证了非线程安全的对象的正确操作。

    f. GCD延迟操作

    dispatch_after这个函数默认是异步执行的。
    - (void)afterDemo {
        NSLog(@"start");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // 执行的任务
        });
        NSLog(@"end");
        // 拆分
        /*
         参数1 : dispatch_time_t when,表示延迟的时间
         参数2 : dispatch_queue_t queue,表示任务执行的队列
         参数3 : dispatch_block_t block,表示线程要执行的任务
         */
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_block_t block = ^ {
           // 执行的任务
        };
        dispatch_after(time, queue, block);
    }
    

    上述代码dispatch_after里面的代码延迟一秒执行。

    g. GCD一次执行

    GCD的一次执行运用最多的场景就是创建单例,核心就是dispatch_once_t,它的里面有一把锁,能够保证线程的安全。

    苹果公司推荐使用
    单例:

    一个应用程序里面有且只有一个这样的实例对象,例如:网络请求,音乐播放器等。单例一旦创建就会一直存在,直到app退出,单例存在静态区,所以不能滥用。
    向我们应用程序中经常使用的好多,也都是单例。例如:

     [NSNotificationCenter defaultCenter];
     [NSUserDefaults standardUserDefaults];
     [UIApplication sharedApplication];
     [NSFileManager defaultManager];
    

    如果不使用GCD的话,也可以这样创建单例,使用互斥锁

    + (instancetype)sharedTool {
        // 添加互斥锁
        @synchronized (self) {
            if (instance == nil) {
                instance = [[self alloc] init];
            }
        }
        return instance;
    }
    

    开发中一般不使用这种方式,因为互斥锁使用的是线程同步的原理,线程之间需要等待,相比dispatch_once效率不高。

    懒汉式和饿汉式单例
    static id instance;
    // 懒汉式单例:使用时才会创建
    + (instancetype)sharedTool {
        static id instance;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[self alloc] init];
        });
        return instance;
    }
    
    // 饿汉式单例:重写initialize这个类方法,在类第一次使用的时候就会创建
    // initialize会在类第一次被使用时调用, 且调用是线程安全的
    + (void)initialize {
        instance = [[self alloc] init];
    }
    + (instancetype)sharedTool {
        return instance;
    }
    

    有时候我们会看到有些代码会重写allocWithZone和copyWithZone。例如:

    + (instancetype)allocWithZone:(struct _NSZone *)zone {
            static id instance;
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                instance = [[self alloc] init];
            });
            return instance;
    }
    

    因为是担心合作开发中,别人会使用TYOnce *tools = [[TYOnce alloc] init]; [tools copy]这种方式来创建单例,但是一般正常的程序员都知道我们创建单例的方式都是使用TYOnce *tools = [TYOnce sharedTool];这种方式,所以这种顾虑基本上可以不用考虑。

    h. 调度组

    监听一组异步任务是否执行结束,在其执行结束后得到统一的通知。例如:监听几部电影同时下载:

    - (void)groupDemo {
        //调度组
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        dispatch_group_async(group, queue, ^{
            NSLog(@"下载第1部电影 %@",[NSThread currentThread]);
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"下载第2部电影 %@",[NSThread currentThread]);
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"下载第3部电影 %@",[NSThread currentThread]);
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"电影全部下载完成");
        });
    }
    

    打印结果:

    2018-10-19 15:20:07.473239+0800 05-调度组[11616:3513382] 下载第1首歌曲 <NSThread: 0x604000278b80>{number = 3, name = (null)}
    2018-10-19 15:20:07.473239+0800 05-调度组[11616:3513384] 下载第3首歌曲 <NSThread: 0x60400027a980>{number = 4, name = (null)}
    2018-10-19 15:20:07.474253+0800 05-调度组[11616:3513381] 下载第2首歌曲 <NSThread: 0x60400027be00>{number = 5, name = (null)}
    2018-10-19 15:20:07.474631+0800 05-调度组[11616:3513290] 歌曲下载完成了
    

    由打印的结果可以看出,任务是异步执行的,在三部电影全部下载完成后,可以得到统一的通知。

    调度组的执行原理:实现监听一组异步任务是否执行结束
    /*
        enter等于eave : 监测成功
        enter多于leave : 监测失效
        enter小于leave : 程序崩溃
     */
    - (void)groupDemo {
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_group_enter(group);
        dispatch_group_async(group, queue, ^{
            NSLog(@"下载第1部电影 %@",[NSThread currentThread]);
            [NSThread sleepForTimeInterval:2.0];
            dispatch_group_leave(group);
        });
        dispatch_group_enter(group);
        dispatch_group_async(group, queue, ^{
            NSLog(@"下载第2部电影 %@",[NSThread currentThread]);
            dispatch_group_leave(group);
        });
        dispatch_group_enter(group);
        dispatch_group_async(group, queue, ^{
            NSLog(@"下载第3部电影 %@",[NSThread currentThread]);
            dispatch_group_leave(group);
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"电影全部下载完成");
        });
    }
    

    4.0 NSOperation

    a. 简介(IOS2.0后推出)

    NSOperation是OC语言中基于GCD的面向对象的封装,使用起来比GCD更加简单(面向对象),提供了一些用GCD不好实现的功能(例如:添加依赖),能够自动的管理线程的生命周期,在平时的工作中经常使用。

    苹果推荐使用

    NSOperation是一个抽象类,所以无法直接使用,因为它的方法只有声明没有实现。核心就是将操作添加到队列当中。
    使用时其实我们是对NSOperation子类的使用,它的子类:

    NSInvocationOperation;
    NSBlockOperation;
    // 自定义operation
    NSOperation;
    

    b. NSInvocationOperation的使用

    • 试例一
    - (void)operationDemo {
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
        [op start];
    }
    - (void)demo {
        NSLog(@"%@", [NSThread currentThread]);
    }
    

    打印结果:

    2018-10-19 16:25:58.252280+0800 多线程Demo[11856:3583208] <NSThread: 0x604000078680>{number = 1, name = main}
    
    得出结论: [op start]方法,会在当前线程执行selector方法。
    • 试例二
    - (void)operationDemo {
        NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo) object:nil];
        // 队列: 并发队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        // 把操作添加到队列
        [queue addOperation:op];
    }
    - (void)demo {
        NSLog(@"%@", [NSThread currentThread]);
    }
    

    打印结果:

    2018-10-19 16:32:03.564602+0800 多线程Demo[11895:3592479] <NSThread: 0x60400026dc00>{number = 3, name = (null)}
    
    得出结论:将操作添加到队列,默认是异步执行。
    • 试例三:验证队列的并发性
    - (void)operationDemo {
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        for(int i = 0;i< 5;i++){
            NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo) object:nil];
            [queue addOperation:op];
        }
    }
    - (void)demo {
        NSLog(@"%@", [NSThread currentThread]);
    }
    

    打印结果:

    2018-10-19 16:36:15.437686+0800 多线程Demo[11936:3600229] <NSThread: 0x60000026c7c0>{number = 6, name = (null)}
    2018-10-19 16:36:15.437641+0800 多线程Demo[11936:3600228] <NSThread: 0x604000276c80>{number = 4, name = (null)}
    2018-10-19 16:36:15.437744+0800 多线程Demo[11936:3600234] <NSThread: 0x60000026c400>{number = 3, name = (null)}
    2018-10-19 16:36:15.437808+0800 多线程Demo[11936:3600232] <NSThread: 0x60000026c780>{number = 5, name = (null)}
    2018-10-19 16:36:15.438371+0800 多线程Demo[11936:3600308] <NSThread: 0x604000279840>{number = 7, name = (null)}
    
    得出结论:会开启多条线程,不是顺序执行的。与GCD中并发异步执行效果一样。

    c. NSBlockOperation的使用

    • 试例一
    - (void)blockDemo {
        // 封装NSOperation对象
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@",[NSThread currentThread]);
        }];
        [op start];
    }
    

    得出结论:

    2018-10-19 17:12:50.353660+0800 多线程Demo[12060:3633014] <NSThread: 0x600000067e80>{number = 1, name = main}
    
    得出结论: [op start]方法,操作只在当前线程执行。
    • 试例二
    - (void)blockDemo {
        // 队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        // 封装NSOperation对象
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@",[NSThread currentThread]);
        }];
        // 把操作添加到队列
        [queue addOperation:op];
    }
    

    打印结果:

    2018-10-19 17:15:00.513886+0800 多线程Demo[12079:3637107] <NSThread: 0x60400027e5c0>{number = 3, name = (null)}
    
    得出结论:将操作添加到队列,操作默认是异步的。
    • 试例三
    - (void)blockDemo {
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        for(int i = 0;i< 5;i++){
            NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
                NSLog(@"%@",[NSThread currentThread]);
            }];
            [queue addOperation:op];
        }
    }
    

    打印结果:

    2018-10-19 17:19:32.740483+0800 多线程Demo[12120:3644204] <NSThread: 0x6000002698c0>{number = 5, name = (null)}
    2018-10-19 17:19:32.740487+0800 多线程Demo[12120:3644205] <NSThread: 0x604000269fc0>{number = 6, name = (null)}
    2018-10-19 17:19:32.740483+0800 多线程Demo[12120:3644203] <NSThread: 0x60400026b800>{number = 4, name = (null)}
    2018-10-19 17:19:32.740524+0800 多线程Demo[12120:3644201] <NSThread: 0x600000269d00>{number = 3, name = (null)}
    2018-10-19 17:19:32.740646+0800 多线程Demo[12120:3644202] <NSThread: 0x604000269f80>{number = 7, name = (null)}
    
    得出结论:队列默认是并发性的。
    • 试例四
      在实际开发时,如果要使用到NSOperationQueue,可以直接定义成全局的队列
    // 全局队列
    @property (nonatomic, strong) NSOperationQueue *queue;
    - (NSOperationQueue *)queue {
        if (_queue == nil) {
            _queue = [[NSOperationQueue alloc]init];
        }
        return _queue;
    }
    - (void) blockDemo {
        [self.queue addOperationWithBlock:^{
            NSLog(@"%@",[NSThread currentThread]);
        }];
    }
    
    • 试例五
      添加操作服务质量和监听操作执行结束
      操作服务质量:解决队列里面的操作有更多的机会被队列调度执行,类似于线程优先级。
      监听操作执行结束:这个监听的回调是异步的。
    - (void)OperationDemo {
        // 操作1
        NSBlockOperation *firstOp = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 10; i++) {
                NSLog(@"firstOp == %d %@",i,[NSThread currentThread]);
            }
        }];
        // 监听操作1什么时候执行结束 : 这个监听是异步监听
        [firstOp setCompletionBlock:^{
            NSLog(@"操作1执行结束 == %@",[NSThread currentThread]);
        }];
        // 设置操作优先级 : 设置为最高
        firstOp.qualityOfService = NSQualityOfServiceUserInteractive;
        [self.queue addOperation: firstOp];
        // 操作2
        NSBlockOperation *secondOp = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 10; i++) {
                NSLog(@"secondOp == %d %@",i,[NSThread currentThread]);
            }
        }];
        // 设置操作优先级 : 设置为最低
        secondOp.qualityOfService = NSQualityOfServiceBackground;
        [self.queue addOperation: secondOp];
    }
    
    • 试例六
      添加操作执行块
    - (void)OperationDemo {
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@",[NSThread currentThread]);
        }];
        // 添加操作的执行块 : 这个执行块也是属于op对象的
        // 当添加操作执行块后, 这个执行块里面的任务就新开子线程执行
        [op addExecutionBlock:^{
            NSLog(@"ExecutionBlock %@",[NSThread currentThread]);
        }];
        // 在没有添加执行块之前,这个操作是在当前线程执行
        [op start];
    }
    

    打印结果:

    2018-10-22 10:18:26.121270+0800 多线程Demo[16173:4731928] ExecutionBlock <NSThread: 0x6000002745c0>{number = 3, name = (null)}
    2018-10-22 10:18:26.121270+0800 多线程Demo[16173:4730187] <NSThread: 0x600000261dc0>{number = 1, name = main}
    
    得出结论:由打印的结果可以看出执行块里面的任务开启了新的线程执行,在没有添加执行块之前,这个操作是在当前线程执行的。

    d. NSOperation的高级应用

    • 队列的最大并发数
    self.queue.maxConcurrentOperationCount = 3;
    

    设置最大并发数为三,每次只能调度三个操作。

    • 队列的暂停、继续和取消全部
    // 队列暂停
    self.queue.suspended = YES;
    

    只能够暂停还没有执行的操作,正在执行的操作没有办法暂停。如果先暂停队列,再添加操作到队列,队列不会调度操作执行。

    // 队列继续
    self.queue.suspended = NO;
    
    // 队列全部取消
    [self.queue cancelAllOperations];
    

    这个方法只能够取消还没有执行的操作,正在执行的操作没有办法取消。如果要取消,需要自定义NSOperation。队列取消全部操作时,会有一定的时间延迟。

    • 操作依赖
    • (void)dependencyDemo {
      // 登陆->付费->下载
      NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"登陆");
      }];
      NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"付费");
      }];
      NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"下载");
      }];
      // 设置操作依赖
      [op3 addDependency:op2];
      [op2 addDependency:op1];
      // [op1 addDependency:op3]; 注意1:不要搞成循环依赖
      // 不同的队列之中的操作也可以设置操作依赖
      [[NSOperationQueue mainQueue]addOperation:op1];
      // 把操作添加到队列
      [self.queue addOperations:@[op2,op3] waitUntilFinished:NO];
      }
      很多时候我们希望自己所执行的事情能按照顺序执行,比如我们我们下载一首付费的歌曲,需要我们按顺序执行登录-付费-下载的操作,由于这些操作我们放在后台去执行,所以它们执行的顺序是不确定的,看CPU怎么调度,所以我们为了达到我们的要求。就需要添加操作依赖来实现。
    注意:

    不能循环建立操作间依赖关系。否则,队列不调度操作执行。
    操作间可以跨队列建立依赖关系。
    要将操作间的依赖建立好了之后,再添加到队列中。

    相关文章

      网友评论

        本文标题:IOS 多线程的四种创建方案及比较

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