iOS-GCD中的队列及实现

基本概念
  • 进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,在iOS系统中,开启一个应用就打开了一个进程。
  • 线程:线程(Thread)是进程中的一个实体,程序执行的基本单元。在iOS系统中,一个进程包含一个主线程,它的主要任务是处理UI事件,显示和刷新UI。
  • 同步:在当前线程依次执行,不开启新的线程。
  • 异步:多个任务情况下,一个任务A正在执行,同时可以执行另一个任务B。任务B不用等待任务A结束才执行。存在多条线程。
  • 队列:存放任务的结构
  • 串行:线程执行只能依次逐一先后有序的执行。
  • 并发:指两个或多个事件在同一时间间隔内发生。可以在某条线程和其他线程之间反复多次进行上下文切换,看上去就好像一个CPU能够并且执行多个线程一样。其实是伪异步。
  • 并行:指两个或多个时间在同一时刻发生。多核CUP同时开启多条线程供多个任务同时执行,互不干扰。
会出现的问题
  • 临界代码段:指的是不能同时被两个线程访问的代码段,比如一个变量,被并发进程访问后可能会改变变量值,造成数据污染(数据共享问题)。
  • 死锁:两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。
  • 线程安全:一段线程安全的代码(对象),可以同时被多个线程或并发的任务调度,不会产生问题,非线程安全的只能按次序被访问。
注意:
1.所有Mutable对象都是非线程安全的,所有Immutable对象都是线程安全的,使用Mutable对象,一定要用同步锁来同步访问(@synchronized)。
2.互斥锁:能够防止多线程抢夺造成的数据安全问题,但是需要消耗大量的资源
3.原子属性(atomic)加锁
4.atomic: 原子属性,为setter方法加锁,将属性以atomic的形式来声明,该属性变量就能支持互斥锁了。
5.nonatomic: 非原子属性,不会为setter方法加锁,声明为该属性的变量,客户端应尽量避免多线程争夺同一资源。
复制代码
GCD中的队列类型
  1. The main queue(主线程串行队列): 与主线程功能相同,提交至Main queue的任务会在主线程中执行
  2. Global queue(全局并发队列): 全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别。
  3. Custom queue (自定义队列): 可以为串行,也可以为并发。
  4. Group queue (队列组):将多线程进行分组,最大的好处是可获知所有线程的完成情况。

    Group queue 可以通过调用dispatch_group_create()来获取,通过dispatch_group_notify,可以直接监听组里所有线程完成情况。

The main queue(主线程串行队列)
  1. 获取主线程串行队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    复制代码
  2. 同步执行任务,在主线程运行时,会产生死锁
    dispatch_sync(queue,^{
        NSLog("queue");  //这句代码永远不会执行,因为产生了死锁    
    });
    复制代码
    上面的代码执行之后,首先会阻塞当前线程(主线程),然后等待主线程执行完,在回到调用线程(主线程)继续执行。如果主线程被阻塞,那么NSLog将不会被执行,也就永远无法继续执行主线程了,程序一直处于等待状态,block中的代码将执行不到,造成了死锁
  3. 异步执行任务,在主线程运行,不会产生死锁。
    dispatch_async(queue,^{
        NSLog("dispatch_async main queue");  
    });
    复制代码
    打印内容
    2018-02-23 10:52:13 GCD队列[2273:235886] dispatch_async main queue
    复制代码
    代码正常执行,没有产生死锁。
  4. 加载图片常用方式,返回主线程刷新UI
    //以便在block中使用
    __block UIImage *image = [[UIImage alloc] init];
    //创建异步线程执行队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //dispatch_queue_t asynchronousQueue = dispatch_queue_create("imageDownloadQueue", NULL);
    //创建异步线程
    dispatch_async(globalQueue, ^{
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"userPhoto"];
        NSError *error;
        NSData *dataPhoto = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@",_photoPath]] options:NSDataReadingMappedIfSafe error:&error];
        if (dataPhoto) {
            image = [UIImage imageWithData:dataPhoto];
            //存头像
            [[NSUserDefaults standardUserDefaults] setObject:dataPhoto forKey:@"userPhoto"];
        }
        //回到主线程更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            [imgView setImage:image];
        });
    });
    复制代码
  5. 主线程串行队列无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
Global queue(全局并发队列)

在一些耗时相对较长的业务场景中,我们通常会另开一个线程来执行这些业务,然后再通知主线程更新界面,以免造成界面长时间的卡顿。这些场景包括网络请求,加载图片,数据库读取等。

  1. 获取全局并发队列
    //程序默认的队列级别,一般不要修改,DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
    dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //HIGH
    dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    //LOW
    dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    //BACKGROUND
    dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    复制代码
  2. 同步执行任务,在主线程执行会导致页面卡顿。
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"current task");
    dispatch_sync(globalQueue, ^{
        sleep(2.0);
        NSLog(@"sleep 2.0s");
    });
    NSLog(@"next task");
    复制代码
    打印结果
    2018-02-23 11:02:55 GCD队列[2286:238901] current task
    2018-02-23 11:02:57 GCD队列[2286:238901] sleep 2.0s
    2018-02-23 11:02:57 GCD队列[2286:238901] next task
    复制代码
    可以看出sleep 2.0s等待了2秒之后才执行的
    根据同步执行的特点(在当前线程依次执行,不开启新的线程),因此程序在等待了2秒之后,执行了sleep 2.0s打印
    复制代码
  3. 异步执行任务,在主线程运行,会开启新的线程去执行任务,页面不会卡顿。
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"current task");
    dispatch_async(globalQueue, ^{
        sleep(2.0);
        NSLog(@"sleep 2.0s");
    });
    NSLog(@"next task");
    复制代码
    打印结果
    2018-02-23 11:09:53 GCD队列[2289:240672] current task
    2018-02-23 11:09:53 GCD队列[2289:240672] next task
    2018-02-23 11:09:55 GCD队列[2289:240717] sleep 2.0s
    复制代码
    可以看出next task是先执行的
    主线程不需要等待2s,可以继续执行后面的代码
    复制代码
  4. 多个并发队列,异步执行任务。
    NSLog(@"current task");
    for (int i = 0; i < 3; i++) {
        dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(globalQueue, ^{
            NSLog(@"全局并发队列");
        });
    }
    NSLog(@"next task");
    复制代码
    打印结果
    2018-02-23 11:15:46 GCD队列[2294:242439] current task
    2018-02-23 11:15:46 GCD队列[2294:242439] next task
    2018-02-23 11:15:46 GCD队列[2294:242486] 全局并发队列
    2018-02-23 11:15:46 GCD队列[2294:242486] 全局并发队列
    2018-02-23 11:15:46 GCD队列[2294:242486] 全局并发队列
    复制代码
Custom queue (自定义队列)

  1. 自定义串行队列
    • 自定义串行队列同步执行任务
    dispatch_queue_t serialQueue = dispatch_queue_create("com.xyy.serialQueue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"当前任务");
    dispatch_sync(serialQueue, ^{
        NSLog(@"最先加入自定义串行队列");
        sleep(2);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"次加入自定义串行队列");
    });
    NSLog(@"下一个任务");
    复制代码
    打印结果
    2018-02-23 11:21:09 GCD队列[2297:243862] 当前任务
    2018-02-23 11:21:09 GCD队列[2297:243862] 最先加入自定义串行队列
    2018-02-23 11:21:11 GCD队列[2297:243862] 次加入自定义串行队列
    2018-02-23 11:21:11 GCD队列[2297:243862] 下一个任务
    复制代码
    当前线程等待串行队列中的子线程执行完成之后再执行,串行队列中先进来的子线程先执行任务,执行完成后,再执行队列中后面的任务。
    复制代码
  2. 自定义并发队列
    • 自定义并发队列执行同步任务
    dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.xyy.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"当前任务");
    dispatch_sync(conCurrentQueue, ^{
        NSLog(@"先加入队列");
    });
    dispatch_sync(conCurrentQueue, ^{
        NSLog(@"次加入队列");
    });
    NSLog(@"下一个任务");
    复制代码
    打印结果
    2018-04-08 09:09:05.555159+0800 GCD队列[2294:242439] 当前任务
    2018-04-08 09:09:05.555267+0800 GCD队列[2294:242439] 先加入队列
    2018-04-08 09:09:05.555297+0800 GCD队列[2294:242439] 次加入队列
    2018-04-08 09:09:05.555322+0800 GCD队列[2294:242439] 下一个任务
    复制代码
    • 自定义并发队列嵌套执行同步任务(不会产生死锁,程序正常运行)
    dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.xyy.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"当前任务");
    dispatch_sync(conCurrentQueue, ^{
        NSLog(@"先加入队列");
        dispatch_sync(conCurrentQueue, ^{
            NSLog(@"次加入队列");
        });
    });
    NSLog(@"下一个任务");
    复制代码
    打印结果
    2018-02-23 11:30:54 GCD队列[2303:246697] 当前任务
    2018-02-23 11:30:54 GCD队列[2303:246697] 先加入队列
    2018-02-23 11:30:54 GCD队列[2303:246697] 次加入队列
    2018-02-23 11:30:54 GCD队列[2303:246697] 下一个任务
    复制代码
    • 自定义并发队列执行异步任务
    NSLog(@"当前任务");
    for (int i = 0; i < 3; i++) {
        dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.xyy.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(conCurrentQueue, ^{
            NSLog(@"自定义队列 %d %@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"下一个任务");
    复制代码
    打印结果
    2018-02-23 11:39:07 GCD队列[2327:251270] 当前任务
    2018-02-23 11:39:07 GCD队列[2327:251270] 下一个任务
    2018-02-23 11:39:07 GCD队列[2327:251308] 自定义队列 0 <NSThread: 0x10203a210>{number = 4, name = (null)}
    2018-02-23 11:39:07 GCD队列[2327:251308] 自定义队列 2 <NSThread: 0x10203a210>{number = 4, name = (null)}
    2018-02-23 11:39:07 GCD队列[2327:251305] 自定义队列 1 <NSThread: 0x10203d9c0>{number = 5, name = (null)}
    
    复制代码
    异步执行任务,开启子线程,不影响当前线程的任务执行.
同步任务/异步任务/串行队列/并行队列(对比分析)
  1. 同步任务和异步任务
    同步任务优先级高,在线程中有执行顺序,不会开启新的线程。
    异步任务优先级低,在线程中执行没有顺序,看cpu闲不闲。在主队列中不会开启新的线程,其他队列会开启新的线程。
    复制代码
  2. 串行队列和并行队列
    串行队列:队列中的任务按顺序执行(不会同时执行)
    并行队列:队列中的任务会并发执行,可能会有一个疑问,队列不是先进先出吗,如果后面的任务执行完了,怎么出去的了。这里需要强调下,任务执行完毕了,不一定出队列。只有前面的任务执行完了,才会出队列。
    串行队列里开启异步任务,是有顺序的
    并行队列里开启同步任务是有执行顺序的,只有异步才没有顺序
    复制代码
  3. 在主队列中开启同步任务为什么会阻塞线程
    在主队列开启同步任务,因为主队列是串行队列,里面的线程是有顺序的,先执行完一个线程才执行下一个线程,
    而主队列始终就只有一个主线程,主线程是不会执行完毕的,因为他是无限循环的,除非关闭应用程序。
    因此在主线程开启一个同步任务,同步任务会想抢占执行的资源,而主线程任务一直在执行某些操作,不肯放手。两个的优先级都很高,最终导致死锁,阻塞线程了
    复制代码
  4. 主线程队列中不能开启同步
    主线程队列中不能开启同步,会阻塞主线程。只能开启异步任务,开启异步任务也不会开启新的线程,只是降低异步任务的优先级,让cpu空闲的时候才去调用。而同步任务,会抢占主线程的资源,会造成死锁。
    复制代码
  5. 为什么串行队列开启异步任务后嵌套同步任务造成死锁?
    因为串行队列中线程是有执行顺序的,需要等上面开启的异步任务执行完毕,才会执行下面开启的同步任务。而上面的异步任务还没执行完,而下面的同步任务已经在抢占资源了,就会发生死锁。
    复制代码
  6. 串行队列中开启同步任务后嵌套同步任务造成死锁
    因为串行队列中线程是有执行顺序的,需要等上面开启的同步任务执行完毕,才会执行下面开启的同步任务。而上面的同步任务还没执行完,而下面的同步任务已经在抢占资源了,就会发生死锁
    串行队列开启同步任务后嵌套异步任务就不会造成死锁,开启异步,就会开启一个新的线程,不会阻塞线程
    复制代码
  7. 主线程队列和GCD创建的队列也是有区别?
    主线程队列和GCD创建的队列是不同的。在GCD中创建的队列优先级没有主队列高,所以在GCD中的串行队列开启同步任务里面没有嵌套任务是不会阻塞主线程,只有一种可能导致死锁,就是串行队列里,嵌套开启任务,有可能会导致死锁。
    复制代码
  8. 队列
    队列我们可以把它理解为是管理任务的,它里面放着很多的任务,来管理这些任务什么时候在哪些线程里面执行.队列是先进先出的
    复制代码

参考资料

  1. iOS中GCD的使用小结
  2. ios--进程/多线程/同步任务/异步任务/串行队列/并行队列(对比分析)

转载于:https://juejin.im/post/5a8f73dc51882518c0797b8f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值