GCD学习之队列和任务

队列与任务

任务task

就是需要执行的操作,是GCD中放在block中,需要在线程中执行的那段代码。

执行方式,有两种:

  • 同步执行

    • 把任务同步添加到指定的队列中。在队列中,之前的任务执行结束之前会一直等待,直到队列里面的任务完成之后,再去执行其他的任务

    • 同步执行的任务只能在当前线程中执行,不具备开启新线程的能力。

    • 函数:dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);

      // 同步执行任务的创建
        dispatch_sync(queue, ^{
         // 同步执行的任务代码
      
        });
      
  • 异步执行

    • 异步添加任务到指定的队列中,不需要理会队列中其他的任务。这种任务无需做任何的等待,添加到队列就会立即执行。

    • 异步执行可以在新的线程中执行,具备开启新的线程的能力。

    • 函数:dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

       // 异步执行任务的创建
         dispatch_async(queue, ^{
          // 异步执行的任务代码
         });
      

队列

是指执行任务的等待队列,是用来存放任务的,队列分为“串行队列”和“并发队列”。队列采用“先进先出”的原则,即新任务总是被出入到队列的末尾,而读取任务的时候总是从队列的头部开始。串行队列和并发队列都遵循这个原则。

队列的分类
  • 串行队列(serial)

    • 只开启一个线程

    • 一次只有一个任务被执行

    • 一个任务执行完成后,再去执行下一个任务

    • 多个串行队列之间是按照并发队列的形式进行执行

  • 并发队列(concurrent)

    • 可以开启多个线程

    • 多个任务可以同时执行

队列的类型以及获取相应的队列

主队列(Main Dispatch Queue)

  1. 专门负责调度主线程的任务,没有办法开辟新的线程。在主队列的任务,无论是同步任务还是异步任务,都不会开启新的线程。

  2. 主线程只能过去,不能创建。获取方法:dispatch_queue_main_t dispatch_get_main_queue(void)

      //获取主队列
      dispatch_queue_t mainQueue = dispatch_get_main_queue();        
    
  3. 主队列是一种串行队列。

  4. 主队列与主线程的关系

    • 主队列的任务一定在主线程中执行

    • 主线程可以执行主队列以外的其他队列的任务

  5. 所有追加到主队列的操作都会在runloop中执行.

全局并发队列(Global Dispatch Queue

  1. 是一种并发队列,有系统提供,方便开发人员编程使用,可以不用创建直接使用。

  2. 并发的执行多个任务,但是任务的执行顺序是随机的

  3. 获取方法:dispatch_queue_global_t dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);

    • 参数1: 长整型参数intptr_t identifier,表示队列的优先级或者Qos的值

    • 参数2: 长整型参数uintptr_t flags,传0就可以。

       //创建全局并发队列
       dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      

自定义队列

  1. 如果以上两种队列不能满足要求的话,可以自定义队列。

  2. 串行队列、并发队列都可以进行自定义

  3. 自定义队列可以设置队列的优先级

  4. 自定义队列需要用到的方法是:dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

    • 参数1: 队列的标识符

    • 参数2: 一个结合了服务质量Qos级别的队列属性值,可以传以下两种类型的值

      • 表示队列类型的值

        • 直接传值NULL: 默认创建一个串行队列
        dispatch_queue_t queue = dispatch_queue_create("identifier", NULL);
        
        • DISPATCH_QUEUE_SERIAL: 创建一个串行队列
        dispatch_queue_t serialQueue = dispatch_queue_create("identifier", DISPATCH_QUEUE_SERIAL);
        
        • DISPATCH_QUEUE_CONCURRENT: 创建一个并发队列
        dispatch_queue_t conQueue = dispatch_queue_create("identifier", DISPATCH_QUEUE_CONCURRENT);
        
      • 表示执行任务的服务质量Qos级别

        • 使用dispatch_queue_attr_t dispatch_queue_attr_make_with_qos_class(dispatch_queue_attr_t _Nullable attr,dispatch_qos_class_t qos_class, int relative_priority);
         //设置队列的类型,设置队列的优先级
          /*
          第二个参数
          __QOS_ENUM(qos_class, unsigned int,
          QOS_CLASS_USER_INTERACTIVE  __QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x21,
          QOS_CLASS_USER_INITIATED    __QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x19,
          QOS_CLASS_DEFAULT           __QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x15,
          QOS_CLASS_UTILITY           __QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x11,
          QOS_CLASS_BACKGROUND        __QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x09,
          QOS_CLASS_UNSPECIFIED       __QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x00,
          );
          */
          dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1);
          //创建队列
          dispatch_queue_t queue = dispatch_queue_create("identifier", attr);
        

任务和队列的组合,对线程的影响

任务---队列---线程.png

任何和队列需要搭配使用,算上主线程的特殊情况,总共是有六种组合。

同步任务 + 并发队列

同步任务不会开启新的线程,虽然任务是在并发队列中,但是系统只会使用同步任务所在的线程,所以这种组合没有开启线程,并且任务按照串行的方式执行。

需要说明的是,这种组合是在当前线程执行任务,不一定是在主线程。

//同步任务 + 并发队列 
- (void)concurrentQueueAndSync{
   //创建并发队列
   dispatch_queue_t queue = dispatch_queue_create("concurrentQueueAndSync", DISPATCH_QUEUE_CONCURRENT);
   //创建同步执行的任务1
   dispatch_sync(queue, ^{
     sleep(1);
      NSLog(@"任务1--同步任务 + 并发队列  -: %@",[NSThread currentThread]);
   });
     //任务2
   dispatch_sync(queue, ^{
     sleep(2);
     NSLog(@"任务2--同步任务 + 并发队列  -: %@",[NSThread currentThread]);
   });
   //任务3
   dispatch_sync(queue, ^{
     sleep(1);
     NSLog(@"任务3--同步任务 + 并发队列  -: %@",[NSThread currentThread]);
  });
   //任务4
   dispatch_sync(queue, ^{
     sleep(2);
     NSLog(@"任务4--同步任务 + 并发队列  -: %@",[NSThread currentThread]);
   });
}

打印结果为:

2022-04-01 16:28:56.369531+0800 suanfaProject[5147:205112] 任务1--同步任务 + 并发队列 -: <_NSMainThread: 0x60000012c000>{number = 1, name = main}
2022-04-01 16:28:58.370188+0800 suanfaProject[5147:205112] 任务2--同步任务 + 并发队列 -: <_NSMainThread: 0x60000012c000>{number = 1, name = main}
2022-04-01 16:28:59.371567+0800 suanfaProject[5147:205112] 任务3--同步任务 + 并发队列 -: <_NSMainThread: 0x60000012c000>{number = 1, name = main}
2022-04-01 16:29:01.373276+0800 suanfaProject[5147:205112] 任务4--同步任务 + 并发队列 -: <_NSMainThread: 0x60000012c000>{number = 1, name = main}

同步任务 + 串行队列

同步任务不会开启新的线程,而且串行队列也不会开启新的线程。所以这种组合是不会开启线程,并且所有的任务按照串行的方式,按顺序逐个完成。

需要说明的是,这种组合是在当前线程执行任务,不一定是在主线程。

 //同步任务 + 串行队列
- (void)serialQueueAndSync{
   //创建串行队列
   dispatch_queue_t serialQueue = dispatch_queue_create("serialQueueAndSync", DISPATCH_QUEUE_SERIAL);
   //开启同步执行任务1
   dispatch_sync(serialQueue, ^{
     sleep(1);
     NSLog(@"任务1--同步任务 + 串行队列 -: %@",[NSThread currentThread]);
   });
   //任务2
   dispatch_sync(serialQueue, ^{
     sleep(2);
     NSLog(@"任务2--同步任务 + 串行队列 -: %@",[NSThread currentThread]);
   });
   //任务3
   dispatch_sync(serialQueue, ^{
     sleep(1);
     NSLog(@"任务3--同步任务 + 串行队列 -: %@",[NSThread currentThread]);
   });
   //任务4
   dispatch_sync(serialQueue, ^{
     sleep(2);
     NSLog(@"任务4--同步任务 + 串行队列 -: %@",[NSThread currentThread]);
   });
}

打印结果为:

2022-04-01 16:37:14.093280+0800 suanfaProject[5295:212370] 任务1--同步任务 + 串行队列 -: <_NSMainThread: 0x600001194940>{number = 1, name = main}
2022-04-01 16:37:16.094911+0800 suanfaProject[5295:212370] 任务2--同步任务 + 串行队列 -: <_NSMainThread: 0x600001194940>{number = 1, name = main}
2022-04-01 16:37:17.095654+0800 suanfaProject[5295:212370] 任务3--同步任务 + 串行队列 -: <_NSMainThread: 0x600001194940>{number = 1, name = main}
2022-04-01 16:37:19.098231+0800 suanfaProject[5295:212370] 任务4--同步任务 + 串行队列 -: <_NSMainThread: 0x600001194940>{number = 1, name = main}

同步任务 + 主队列

介绍主队列的时候说过,主队列是没有办法开辟新的线程,无论任务是要同步执行还是异步执行,都不会开启新的线程。所以涉及到主线程的组合,是比较特殊的。

这种情况的组合,会阻塞主线程,导致程序的崩溃。究其原因是因为主线程需要等到主线程的任务完成之后,才会去执行主队列的任务。而同步任务(dispatch_sync)函数中的代码需要等到执行完成才会返回。这种情况下,双方互相等待,也就是主队列中的任务等待主线程执行完成,主队列在等待dispatch_sync函数中的任务执行完成,最终结果就是导致主线程卡住。

//同步任务 + 主线程主队列
-(void)mainQueueAndSync{
   //获取主队列
   dispatch_queue_t mainQueue = dispatch_get_main_queue();
   //创建异步任务
   dispatch_sync(mainQueue, ^{
     NSLog(@"同步任务 + 主线程主队列 -: %@",[NSThread currentThread]);
   });
}

异步任务 + 并发队列

异步任务会开启新的线程,并且并发任务也会开启新的线程。所以这种组合是多个线程并发执行任务。

//异步任务 + 并发队列
- (void)concurrentQueueAndAsync{
   NSLog(@"当前线程 -: %@",[NSThread currentThread]);
   //创建并发队列
   dispatch_queue_t queue = dispatch_queue_create("concurrentQueueAndAsync", DISPATCH_QUEUE_CONCURRENT);
   //创建同步执行的任务1
   dispatch_async(queue, ^{
     sleep(1);
     NSLog(@"任务1--异步任务 + 并发队列 -: %@",[NSThread currentThread]);
   });
   //任务2
   dispatch_async(queue, ^{
      sleep(1);
     NSLog(@"任务2--异步任务 + 并发队列-: %@",[NSThread currentThread]);
   });
   //任务2
   dispatch_async(queue, ^{
     sleep(2);
     NSLog(@"任务3--异步任务 + 并发队列 -: %@",[NSThread currentThread]);
   });
   //任务2
   dispatch_async(queue, ^{
     sleep(1);
     NSLog(@"任务4--异步任务 + 并发队列 -: %@",[NSThread currentThread]);
   });
}

打印结果为:

2022-04-01 17:00:01.138537+0800 suanfaProject[5589:228584] 当前线程 -: <_NSMainThread: 0x6000033dc340>{number = 1, name = main}
2022-04-01 17:00:02.139603+0800 suanfaProject[5589:228706] 任务2--异步任务 + 并发队列-: <NSThread: 0x6000033d1a00>{number = 7, name = (null)}
2022-04-01 17:00:02.143847+0800 suanfaProject[5589:228709] 任务1--异步任务 + 并发队列 -: <NSThread: 0x600003394140>{number = 2, name = (null)}
2022-04-01 17:00:02.143849+0800 suanfaProject[5589:228710] 任务4--异步任务 + 并发队列 -: <NSThread: 0x6000033dd9c0>{number = 4, name = (null)}
2022-04-01 17:00:03.140795+0800 suanfaProject[5589:228708] 任务3--异步任务 + 并发队列 -: <NSThread: 0x600003393100>{number = 6, name = (null)}

异步任务 + 串行队列

异步任务会开启线程,串行队列只在新开启的线程中按照顺序执行。

//异步任务 + 串行队列
- (void)serialQueueAndAsync{
   NSLog(@"当前线程 -: %@",[NSThread currentThread]);
   //创建串行队列
   dispatch_queue_t queue = dispatch_queue_create("serialQueueAndAsync", DISPATCH_QUEUE_SERIAL);
   //创建异步执行任务1
   dispatch_async(queue, ^{
     sleep(1);
     NSLog(@"任务1--异步任务 + 串行队列 -: %@",[NSThread currentThread]);
   });
   //任务2
   dispatch_async(queue, ^{
     sleep(2);
     NSLog(@"任务2--异步任务 + 串行队列 -: %@",[NSThread currentThread]);
   });
   //任务3
   dispatch_async(queue, ^{
     sleep(1);
     NSLog(@"任务3--异步任务 + 串行队列 -: %@",[NSThread currentThread]);
   });
   //任务4
    dispatch_async(queue, ^{
     sleep(2);
     NSLog(@"任务4--异步任务 + 串行队列 -: %@",[NSThread currentThread]);
   });
}

打印结果为:

2022-04-01 17:03:10.569084+0800 suanfaProject[5627:230899] 当前线程 -: <_NSMainThread: 0x600002058900>{number = 1, name = main}
2022-04-01 17:03:11.571632+0800 suanfaProject[5627:231008] 任务1--异步任务 + 串行队列 -: <NSThread: 0x600002000640>{number = 7, name = (null)}
2022-04-01 17:03:13.576875+0800 suanfaProject[5627:231008] 任务2--异步任务 + 串行队列 -: <NSThread: 0x600002000640>{number = 7, name = (null)}
2022-04-01 17:03:14.578503+0800 suanfaProject[5627:231008] 任务3--异步任务 + 串行队列 -: <NSThread: 0x600002000640>{number = 7, name = (null)}
2022-04-01 17:03:16.584066+0800 suanfaProject[5627:231008] 任务4--异步任务 + 串行队列 -: <NSThread: 0x600002000640>{number = 7, name = (null)}

异步任务 + 主队列

介绍主队列的时候说过,主队列是没有办法开辟新的线程,无论任务是要同步执行还是异步执行,都不会开启新的线程。所以涉及到主线程的组合,是比较特殊的。

所以这种组合也是不开启线程,并且在主线程中按照顺序串行执行。

//异步任务 + 主队列
-(void)mainQueueAndAsync{
   //获取主队列
   dispatch_queue_t mainQueue = dispatch_get_main_queue();
     //创建异步任务
     dispatch_async(mainQueue, ^{
     sleep(1);
     NSLog(@"1--异步任务 + 主队列 -: %@",[NSThread currentThread]);
   });

   dispatch_async(mainQueue, ^{
     sleep(2);
     NSLog(@"2--异步任务 + 主队列 -: %@",[NSThread currentThread]);
   });

   dispatch_async(mainQueue, ^{
     sleep(1);
     NSLog(@"3--异步任务 + 主队列 -: %@",[NSThread currentThread]);
   });

    dispatch_async(mainQueue, ^{
     sleep(2);
     NSLog(@"4--异步任务 + 主队列 -: %@",[NSThread currentThread]);
   });

}

打印结果为:

2022-04-01 17:07:02.329970+0800 suanfaProject[5680:233053] 1--异步任务 + 主队列 -: <_NSMainThread: 0x600002ef0340>{number = 1, name = main}
2022-04-01 17:07:04.331448+0800 suanfaProject[5680:233053] 2--异步任务 + 主队列 -: <_NSMainThread: 0x600002ef0340>{number = 1, name = main}
2022-04-01 17:07:05.332954+0800 suanfaProject[5680:233053] 3--异步任务 + 主队列 -: <_NSMainThread: 0x600002ef0340>{number = 1, name = main}
2022-04-01 17:07:07.333557+0800 suanfaProject[5680:233053] 4--异步任务 + 主队列 -: <_NSMainThread: 0x600002ef0340>{number = 1, name = main}

线程间通信

在开发过程中,主线程进行UI刷新,把图片下载、大量计算、网络请求等一些耗时的操作放在其他的线程,当这些耗时的操作完成后需要将数据同步给UI,主线程刷新UI,那么就要用到线程之间的通讯。

在多个线程之前进行通信时,一定要注意线程相互等待导致的死锁、死循环等问题。

//主要是子线程中通信到主线程
-(void)commucationBetweenThreads{
   __block NSInteger value = 0;
   //创建一个并发队列或者使用global队列
   dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
  //    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  //创建异步任务
   dispatch_async(queue, ^{
     NSLog(@"任务1----thread: %@",[NSThread currentThread]);
     for (int i=0; i<5; i++) {
       value += 1;
       sleep(1);
     }
     //创建主队列
     dispatch_queue_t mainQueue = dispatch_get_main_queue();
     //创建异步任务
     dispatch_async(mainQueue, ^{
       NSLog(@"任务2----thread: %@,and value is %li",[NSThread currentThread],value);
     });
   });
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容