美文网首页
iOS 多线程之GCD

iOS 多线程之GCD

作者: 淡定的笨鸟 | 来源:发表于2019-05-21 23:11 被阅读0次

    GCD 简介
    1、什么是GCD?
    全称是 Grand Central Dispatch,纯 C 语言编写,提供了非常多强大的函数
    2、GCD的优势是什么?
    GCD 是苹果公司为多核的并行运算提出的解决方案
    GCD 会自动利用更多的CPU内核(比如双核、四核)
    GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

    串行并行、同步异步

    一、概念

    • 同步执行(sync):在当前线程中执行任务,任务未执行完时,会阻塞线程,不会开辟线程

    • 异步执行(async):另开辟线程执行任务,不会阻塞当前线程

    • 串行队列:按顺序一个一个执行,FIFO先进先出原则

    • 并行队列:可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

    一般使用中会有如下四种情况

    • 串行队列,同步执行:在当前线程中,一个个执行
    • 串行队列,异步执行:另开辟一个线程,一个个执行
    • 并行队列,同步执行:在当前线程中,一个个执行
    • 并行队列,异步执行:另开辟多个线程,同时执行

    也就是说串行同步和并行同步作用是一样的。

    二、实例效果

    结合代码看一下效果
    串行同步

    /**
     串行队列,同步执行
     */
    - (void)createSerialQueueWithSync {
        dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
        dispatch_sync(serialQueue, ^{
            NSLog(@"A==%@", [NSThread currentThread]);
        dispatch_sync(serialQueue, ^{
            NSLog(@"B==%@", [NSThread currentThread]);
        });
        dispatch_sync(serialQueue, ^{
            NSLog(@"C==%@", [NSThread currentThread]);
        });
        NSLog(@"D==%@", [NSThread currentThread]);
    }
    

    串行同步打印结果如下:


    串行同步运行结果

    从结果中我们能看到,符合串行队列的特征(一个一个执行),符合同步执行特征(在当前线程执行,并且阻塞了当前线程)。

    串行异步

    /**
     串行队列,异步执行
     */
    - (void)createSerialQueueWithAsync {
        dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
        dispatch_async(serialQueue, ^{
            NSLog(@"A==%@", [NSThread currentThread]);
        });
        dispatch_async(serialQueue, ^{
            NSLog(@"B==%@", [NSThread currentThread]);
        });
        dispatch_async(serialQueue, ^{
            NSLog(@"C==%@", [NSThread currentThread]);
        });
        NSLog(@"D==%@", [NSThread currentThread]);
    }
    

    串行异步打印结果如下


    串行异步运行结果

    从结果中可以看到,符合串行队列的特征(一个一个执行),符合异步执行特征(另开辟线程,且不阻塞当前线程)。

    并行同步

    /**
     并行队列,同步执行
     */
    - (void)createConcurrentQueueWithSync {
        dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
        //    dispatch_queue_t conCurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//这个是全局并行队列,一般并行任务都会加到这里面去
        
        dispatch_sync(conCurrentQueue, ^{
            NSLog(@"A==%@", [NSThread currentThread]);
        });
        dispatch_sync(conCurrentQueue, ^{
            NSLog(@"B==%@", [NSThread currentThread]);
        });
        dispatch_sync(conCurrentQueue, ^{
            NSLog(@"C==%@", [NSThread currentThread]);
        });
        NSLog(@"D==%@", [NSThread currentThread]);
    }
    

    并行同步打印结果如下


    并行同步运行结果

    从结果可以看出,符合同步特征(在当前线程,且阻塞当前线程),但是并不清楚是否符合并行队列的特征,并行队列的效果需要异步执行才能看出来,并行同步整体的效果和串行同步相同。

    并行异步

    /**
     并行队列,异步执行
     */
    - (void)createConcurrentQueueWithAsync {
        dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(conCurrentQueue, ^{
            NSLog(@"A==%@", [NSThread currentThread]);
        });
        dispatch_async(conCurrentQueue, ^{
            NSLog(@"B==%@", [NSThread currentThread]);
        });
        dispatch_async(conCurrentQueue, ^{
            NSLog(@"C==%@", [NSThread currentThread]);
        });
        NSLog(@"D==%@", [NSThread currentThread]);
    }
    

    并行异步打印结果如下


    并行异步运行结果

    从结果中可以看出,符合并行队列特征(可以并发执行线程任务),符合异步执行特征(另开辟线程,且不阻塞当前线程)。

    //全局并发队列
    dispatch_get_global_queue
    //主线程--串行队列
    dispatch_get_main_queue()
    

    这两个队列在系统刚启动时就创建好的队列,一般不建议使用全局并发队列,因为在全局并发队列中也会执行系统的任务,对于调试和业务剥离等造成影响。

    死锁

    多线程如果使用的太乱,往往会出现死锁的现象。
    什么是死锁?
    线程A等待线程B执行完再执行,且线程B也等待线程A执行完再执行,互相等待即为死锁。
    举例如下:

    - (void)test1 {
        //1,5,2,造成死锁,
        //1,5,2,async是异步,所以会开辟线程,当然开辟线程需要耗时,所以5在先,2在后
        //为什么会造成死锁?因为队列中加入任务的顺序是2、4、3,按照队列的FIFO原则,理应4执行完再执行3,但执行完2遇到了sync同步函数,需要阻塞线程,4需要等待3执行完再执行,造成了互相等待即死锁。
        NSLog(@"1==%@", [NSThread currentThread]);
        dispatch_queue_t queue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            NSLog(@"2==%@", [NSThread currentThread]);
            dispatch_sync(queue, ^{
                NSLog(@"3==%@", [NSThread currentThread]);
            });
            NSLog(@"4==%@", [NSThread currentThread]);
        });
        NSLog(@"5==%@", [NSThread currentThread]);
    }
    

    输出结果是1、5、2,然后就死锁崩溃了。1,5,2,async是异步,所以会开辟线程,当然开辟线程需要耗时,所以5在先,2在后。
    那么为什么会造成死锁?
    因为队列中加入任务的顺序是2、4、3,按照队列的FIFO原则,理应4执行完再执行3,但执行完2遇到了sync同步函数,需要阻塞线程,4需要等待3执行完再执行,造成了互相等待即死锁。

    死锁的总结
    记住两点就好理解为什么会发生死锁了
    1、线程总会在队列中,遵循FIFO(先进先执行)的原则。
    2、sync不论在什么情况下,都会阻塞当前线程,来执行自己的任务。

    栅栏函数barrier

    一、概念
    dispatch_barrier_async加入到自定义的并行队列(不能是全局global队列)时,程序会先执行队列中barrier之前的任务,再执行barrier的任务,最后再执行队列中barrier之后的任务。

    二、实例
    直接上代码
    实例一、应用了dispatch_barrier_async的队列中,线程的执行顺序。

    - (void)createBarrierAsync {
        dispatch_queue_t queue = dispatch_queue_create("liufeng", DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"start==%@", [NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"A==%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"B==%@", [NSThread currentThread]);
        });
        dispatch_barrier_async(queue, ^{
            NSLog(@"barrier_async==%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"C==%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"D==%@", [NSThread currentThread]);
        });
        NSLog(@"end==%@", [NSThread currentThread]);
    }
    

    控制台打印效果如下


    栅栏函数的打印结果

    从打印结果我们可以看到,程序会先执行队列中barrier之前的任务,再执行barrier的任务,最后再执行队列中barrier之后的任务。

    实例二、从网络加载图片,并给图片加上水印后显示到屏幕上

    - (void)testPic {
        dispatch_queue_t concurrentQueue = dispatch_queue_create("com.waterImage", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(concurrentQueue, ^{
            NSString *logoStr = @"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3351002169,4211425181&fm=27&gp=0.jpg";
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
            UIImage *image = [UIImage imageWithData:data];
            [self.mArray addObject:image];
        });
        
        dispatch_async(concurrentQueue, ^{
            NSString *logoStr = @"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3033952616,135704646&fm=27&gp=0.jpg";
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
            UIImage *image = [UIImage imageWithData:data];
            [self.mArray addObject:image];
        });
        
        __block UIImage *newImage = nil;
        dispatch_barrier_async(concurrentQueue, ^{
            
            for (int i = 0; i<self.mArray.count; i++) {
                UIImage *waterImage = self.mArray[i];
                newImage = [LFPrintImageTool printText:@"小姑娘还不睡呀?" onImage:waterImage];
            }
        });
        
        
        dispatch_async(concurrentQueue, ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                self.imageView.image = newImage;
            });
        });
    }
    

    模拟器显示如图所示


    栅栏函数应用在图片加水印

    这个从结果看不出来什么,主要看代码,我们程序的步骤是这样的,先将图片从网络获取下来,才能给它加水印,最后显示到屏幕上,栅栏函数相当于阻塞了当前线程。

    注意:栅栏函数必须是自定义的并发队列才有效,且必须是同一队列中的线程才有效。

    调度组

    一、概念
    在调度组内的线程都执行完毕后,dispatch_notify函数会触发回调。

    二、实例
    应用场景:
    一个业务需要开启N个异步线程,但是后续操作,需要依赖N个线程返回的数据完成操作。
    直接上代码
    实例一

    - (void)createGroupQueue {
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_async(group, queue, ^{
            NSLog(@"A---%@", [NSThread currentThread]);
        });
        
        dispatch_group_async(group, queue, ^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"B---%@", [NSThread currentThread]);
            }
        });
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"C---%@", [NSThread currentThread]);
        });
        
        dispatch_notify(group, queue, ^{
            NSLog(@"队列组任务执行完毕");
        });
    }
    

    调度组执行结果如下

    调度组执行结果
    从结果中可以看出,符合预期,调度组内的线程都执行完毕后,dispatch_notify函数会触发回调。

    实例二

    - (void)test2 {
        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, ^{
            NSLog(@"A---%@", [NSThread currentThread]);
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"B---%@", [NSThread currentThread]);
            }
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"C---%@", [NSThread currentThread]);
            dispatch_group_leave(group);
        });
        
        dispatch_notify(group, queue, ^{
            NSLog(@"队列组任务执行完毕");
        });
    }
    

    上面这个实例是以进组出组的方式实现调度。
    dispatch_group_enter(group);
    dispatch_group_leave(group);
    进组出组的方式类似于信号量,内部有一个signal,enter加1,leave减1,它们总是成对出现,当signal为0时,表示调度组里面的任务都执行完了。

    信号量

    一、作用
    可以控制并发队列的最大并发数,当创建的信号量限制为1时,可以达到锁的效果。

    二、概念
    信号量,一般用来线程并发数量,信号量为几,线程最大并发数就是几
    //创建信号量,参数:信号量的初值,当信号量小于0时阻塞当前线程
    dispatch_semaphore_create(信号量值)

    //等待降低信号量
    dispatch_semaphore_wait(信号量,等待时间)

    //提高信号量
    dispatch_semaphore_signal(信号量)

    三、实例

    - (void)createSemaphoreQueue {
        //create的value表示,最多几个资源可访问
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
        dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        //任务1
        dispatch_async(quene, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"run task 1");
            sleep(1);
            NSLog(@"complete task 1");
            dispatch_semaphore_signal(semaphore);
        });
        //任务2
        dispatch_async(quene, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"run task 2");
            sleep(1);
            NSLog(@"complete task 2");
            dispatch_semaphore_signal(semaphore);
        });
        //任务3
        dispatch_async(quene, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"run task 3");
            sleep(1);
            NSLog(@"complete task 3");
            dispatch_semaphore_signal(semaphore);
        });
    }
    

    控制台打印效果如下


    信号量测试.png

    再次执行效果如下


    信号量测试

    可以看到,始终都是task1和task2先执行完,信号量经过dispatch_semaphore_signal增加以后,task3才能执行,结论就是不论并发队列中有多少任务等待执行,同一时间只允许两个任务执行。

    总结

    以上就是GCD的基本使用,在很多大型的底层框架中,很多使用的都是NSOperation,因为NSOperation更加灵活,开发者可以更好的监控线程的生命周期以及做处理。

    相关文章

      网友评论

          本文标题:iOS 多线程之GCD

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