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-02-23 11:28:11 GCD队列[2300:245747] current task
    2018-02-23 11:28:11 GCD队列[2300:245747] 先加入队列
    2018-02-23 11:28:11 GCD队列[2300:245747] 次加入队列
    2018-02-23 11:28:11 GCD队列[2300:245747] next task
    
    • 自定义并发队列嵌套执行同步任务(不会产生死锁,程序正常运行)
    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] current task
    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] next task
    
    • 自定义并发队列执行异步任务
    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--进程/多线程/同步任务/异步任务/串行队列/并行队列(对比分析)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容