iOS多线程——GCD

在iOS中,实现多线程的方式有很多,比如GCD,NSOperation,NSThread等等,但是一直对线程的概念模糊,今天就根据代码例子来了解iOS中GCD的用法和原理。

GCD是和block紧密相连的,所以最好先了解下block。
GCD是C level的函数,这意味着它也提供了C的函数指针作为参数,方便了C程序员。

下面首先来看GCD的使用:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async表明异步运行,block代表的是你要做的事情,queue则是你把任务交给谁来处理了。(除了async,还有sync,delay,本文以async为例)。

dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾。

之所以程序中会用到多线程是因为程序往往会需要读取数据,然后更新UI。为了良好的用户体验,读取数据的操作会倾向于在后台运行,这样以避免阻塞主线程。GCD里就有三种queue来处理。

GCD Queue分为三种:

  • The main queue:主队列,主线程就是在个队列中,系统默认就有一个串行队列
  • Global queues: 全局并发队列
  • 用户队列:是用函数dispatch_queue_create创建的自定义队列

而用户队列又分为下面两种:
(1)Serial Dispatch Queue
线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。(DISPATCH_QUEUE_SERIAL)

(2)Concurrent Dispatch Queue
线程池提供多个线程来执行任务,所以可以按序启动多个任务并发执行。(DISPATCH_QUEUE_CONCURRENT)

关于同步和异步

  • dispatch_sync
    则调用用 dispatch_sync的线程会等dispatch_sync的对内容执行完再继续执行。dispatch_sync函数不会立即返回,即阻塞当前线程,等待block同步执行完成。

  • dispatch_async
    调用dispatch_async的线程不会的等dispatch_async的内容,自己继续执行。dispatch_async函数会立即返回, block会在后台异步执行。

关于并发和并行

  • 并行:是多个任务在同一个时间片段内同时进行,比如说你一边吃饭一边打电话一遍看电视

  • 并发:是多个任务在同一个时间的点上可以切换进行,比如说你先吃着饭,然后电话来了,停下吃饭立马打电话,打完后立马切换到看电视,这个你在同一个线条内只有一个任务在执行

图示关于并发和并行

并行
并发

下面就以代码实例来分析GCD用法

实例一:DISPATCH_QUEUE_SERIAL串行队列

/**
 DISPATCH_QUEUE_SERIAL是每次运行一个任务,可以添加多个,执行次序FIFO。
 **/
- (void)test1
{
    NSDate *date = [NSDate date];
    
    NSString *daStr = [date description];
    
    const char *queueName = [daStr UTF8String];
    
    //DISPATCH_QUEUE_SERIAL等同于NULL
    dispatch_queue_t myQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
    
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(addTime) userInfo:nil repeats:YES];
    
    //在main_queue中异步将任务放到myQueue队列里
    //放入myQueue的顺序是按代码的执行顺序:任务一,任务二,任务三(因为main_queue是顺序执行)
    //在myQueue取出的顺序是按照FIFO的顺序
    //因为这是一个串行的队列,所以取出后的任务是一个接着一个执行的
    
    
    //执行结果
    //[NSThread sleepForTimeInterval:6]~~~~~~~~6
    //[NSThread sleepForTimeInterval:3]~~~~~~~~9
    //[NSThread sleepForTimeInterval:12]~~~~~~~~21

    
    //任务一:
    dispatch_async(myQueue, ^{
        
        [NSThread sleepForTimeInterval:6];
        
        NSLog(@"[NSThread sleepForTimeInterval:6]~~~~~~~~%d", t);
        
    });
    
    //任务二:
    dispatch_async(myQueue, ^{
        
        [NSThread sleepForTimeInterval:3];
        
        NSLog(@"[NSThread sleepForTimeInterval:3]~~~~~~~~%d", t);
        
    });
    
    //任务三:
    dispatch_async(myQueue, ^{
        
        [NSThread sleepForTimeInterval:12];
        
        NSLog(@"[NSThread sleepForTimeInterval:12]~~~~~~~~%d", t);
        
    });
}

-(void)addTime {
    
    ++t;
}

实例二:全局的并发队列dispatch_get_global_queue

/**
 可以同时运行多个任务,每个任务的启动时间是按照加入queue的顺序,结束的顺序依赖各自的任务.
 **/
- (void)test2
{
    dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
    //在main_queue中异步将任务放到myQueue
    //放入myQueue的顺序是:任务一,任务二,任务三
    //在myQueue中取出的顺序是FIFO的原则:任务一,任务二,任务三
    //取出的任务放到线程里执行,因为这是一个并行的队列,所以任务可以同时运行
    
    //执行的结果
    //执行的结果可以是 1 2 3 的随机组合
    
    //任务一:
    dispatch_async(myQueue, ^{
        
        NSLog(@"~~~~~~~~~1");
        
    });
    
    //任务二:
    dispatch_async(myQueue, ^{
        
        NSLog(@"~~~~~~~~~2");
        
    });
    
    //任务三:
    dispatch_async(myQueue, ^{
        
        NSLog(@"~~~~~~~~~3");
        
    });
}

实例三:DISPATCH_QUEUE_SERIAL串行队列

/**
 串行队列的异步任务:使用一个子线程依次执行。
  对比一下dispatch_async和dispatch_sync输出的i的顺序和线程的地址
 **/
- (void)test3
{
    dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    
    for (int i = 0; i < 3; i++) {
        
        //在main_queue中将i=0,1,2的任务异步加入queue队列中
        //加入queue的顺序是:i=0, i=1, i=2
        //所以在queue中取出的顺序是:i=0,i=1,i=2
        //因为queue是同步队列,所以三个任务放到同一个线程中依次执行
        
        //运行结果
        //~~~i=0~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
        //<NSThread: 0x600000068d00>{number = 1, name = (null)}
        //~~~i=1~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
        //<NSThread: 0x600000068d00>{number = 1, name = (null)}
        // ~~~i=2~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
        //<NSThread: 0x600000068d00>{number = 1, name = (null)}

        //当是使用DISPATCH_QUEUE_SERIAL和dispatch_sync同步的时候,三个任务执行的线程就是当前的mainThread中执行的

        dispatch_async(queue, ^{
            
            NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
            
            NSLog(@"%@",[NSThread mainThread]);
            
        });
    }
}

实例四:DISPATCH_QUEUE_CONCURRENT并发队列

/**
 并行队列的异步任务:使用多个子线程无序执行,一般任务较少时几个任务就开几个线程,较多时则开部分线程。
 应用:一系列的异步任务没有先后顺序,结束无序
 对比一下dispatch_async和dispatch_sync输出的线程的地址
 **/
- (void)test4
{
    
    dispatch_queue_t queue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    
    //在main_queue中异步的将i=0,i=1,i=2的任务加入到queue中
    //加入queue的顺序是i=0,i=1,i=2
    //在queue中取出的顺序是i=0,i=1,i=2
    //因为queue是并发队列,所以有多条的线程来执行三个任务,thread的执行的顺序不定
    
    //当使用dispatch_sync时,执行的顺序又成了i=0,i=1,i=2
    
    for (int i = 0; i < 3; i++) {
        
        dispatch_async(queue, ^{
            
            NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
            
            NSLog(@"%@",[NSThread mainThread]);
            
        });
    }
}

实例五:dispatch_group_t组队列

/**
 dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。
 dispatch_group_async会监听最终的任务完成后,并通知一个线程
 这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成了。
注意这里不是监听queue里所有的任务完成,而是添加到组里的任务,这个任务是在这个queue里,同时也在这个组里,组里所有的任务的完成并不代表queue里所有的任务的完成。
 下面是一段例子代码:
 注意:当queue是global(或者DISPATCH_QUEUE_CONCURRENT)队列和DISPATCH_QUEUE_SERIAL队列时线程的区别
 **/
- (void)test5
{
    
    //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_queue_t queue = dispatch_queue_create("queue3", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"task1~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
        
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"task2~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
        
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"task3~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
        
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        
        NSLog(@"回到主线程");
        
    });
}

实例六:dispatch_apply重复

/**
 dispatch_apply:执行某个代码片段N次
 
 重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。
 多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。
 
 dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
 运行结果:
 array[0]~~~~~~~~~~~~~~0
 array[3]~~~~~~~~~~~~~~3
 array[2]~~~~~~~~~~~~~~2
 array[1]~~~~~~~~~~~~~~1
 array[4]~~~~~~~~~~~~~~4
 array[5]~~~~~~~~~~~~~~5
 
 dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
 运行结果:
 array[0]~~~~~~~~~~~~~~0
 array[1]~~~~~~~~~~~~~~1
 array[2]~~~~~~~~~~~~~~2
 array[3]~~~~~~~~~~~~~~3
 array[4]~~~~~~~~~~~~~~4
 array[5]~~~~~~~~~~~~~~5
 **/
- (void)test6
{
    
    NSArray *array = @[@"0",@"1",@"2",@"3",@"4",@"5"];
    
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
    
    //打印done是在dispatch_apply里所有的任务执行完毕之后才会执行
    dispatch_async(queue2, ^(){
        
        //放在dispatch_apply里面的任务的执行顺序完全依赖于queue1的队列串发还是并发
        dispatch_apply([array count], queue1, ^(size_t index) {
            
            NSLog(@"array[%ld]~~~~~~~~~~~~~~%@", index, array[index]);
            
        });
        
        NSLog(@"done");
    });
}

实例七:dispatch_barrier_async

/**
 dispatch_barrier_async的使用
 
 dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
 
 只有当这个队列为自己创建的并发队列(DISPATCH_QUEUE_CONCURRENT)时才会有这种效果
 执行结果(一)为:
 dispatch_async2
 dispatch_async1
 dispatch_barrier_async
 dispatch_async3
 
 如果使用dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 根据官方文档指出,这个时候的dispatch_barrier_async完全等同于dispatch_async
 执行结果(二)为:
 dispatch_barrier_async
 dispatch_async3
 dispatch_async2
 dispatch_async1
 
 在使用DISPATCH_QUEUE_SERIAL串行队列时,完全就按照FIFO的顺序执行了
 执行结果(三)为:
 2015-12-23 16:53:04.574 NSURLDemo[67716:3661098] dispatch_async1
 2015-12-23 16:53:05.577 NSURLDemo[67716:3661098] dispatch_async2
 2015-12-23 16:53:05.578 NSURLDemo[67716:3661098] dispatch_barrier_async
 2015-12-23 16:53:07.085 NSURLDemo[67716:3661098] dispatch_async3
 
 不仅有dispatch_barrier_async方法,还有dispatch_barrier_sync方法,两个方法的区别是:
 dispatch_barrier_async当他把这个barrier添加到队列后,当前队列不用等待block的执行返回
 而dispatch_barrier_sync需要等待block的内容执行完毕之后再继续下面的执行
 **/
- (void)test7
{
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:3];
        
        NSLog(@"dispatch_async1~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
        
    });
    
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:1];
        
        NSLog(@"dispatch_async2~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);

        
    });
    
    dispatch_barrier_async(queue, ^{
        
        [NSThread sleepForTimeInterval:0.5];
        
        NSLog(@"dispatch_barrier_async~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);

        
    });
    
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:1];
        
        NSLog(@"dispatch_async3~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);

    });
}

实例八:dispatch_once一次使用函数

/**
 dispatch_once
 dispatch_once这个函数,它可以保证整个应用程序生命周期中某段代码只被执行一次!
 **/
- (void)test8
{
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        
        // code to be executed once
    });
}

实例九:dispatch_after延迟函数

/**
 dispatch_after
 有时候我们需要等个几秒钟然后做个动画或者给个提示,这时候可以用dispatch_after这个函数:
 **/
-(void)test9
{
    double delayInSeconds = 2.0;
    
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        
        // code to be executed on the main queue after delay
    });
}

实例十:dispatch_set_target_queue转换队列

/**
  dispatch_set_target_queue
  通过dispatch_set_target_queue函数可以设置一个dispatch queue的优先级,或者指定一个dispatch source相应的事件处理提交到哪个queue上。
  它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:
  dispatch_set_target_queue(dispatchA, dispatchB);
  那么dispatchA上还未运行的block会放到dispatchB上,然后由dispatchB来进行管理运行。
 **/

  dispatch_set_target_queue(serialQ, globalQ);

实例十一:dispatch_group_wait等待

//dispatch_group_wait来等待这些任务完成。若任务已经全部完成或为空,则直接返回,否则等待所有任务完成后返回。

//dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

-(void)test10
{
    
    dispatch_group_t group = dispatch_group_create();
    
    //注意queue为DISPATCH_QUEUE_CONCURRENT 和 DISPATCH_QUEUE_SERIAL时当前的线程
    
    dispatch_queue_t queue = dispatch_queue_create([@"com.queue" UTF8String], DISPATCH_QUEUE_SERIAL);
    
    //每个dispatch_group_enter对应一个dispatch_group_leave完成group内所有的任务则发送通知
    
    //进入group
    
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
        
        NSLog(@"task1~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
        
        //离开group
        
        dispatch_group_leave(group);
        
    });
    
    
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
        
        NSLog(@"task2~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
        
        dispatch_group_leave(group);
        
    });
    
    NSLog(@"~~~~~~~~~~~~~~~~~~before group wait");
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"~~~~~~~~~~~~~~~~~~after group wait");
    
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"~~~~~~~~~~~~~allGroupFinish");
        
    });
}

补充:dispatch_sync(dispatch_get_main_queue(),...)造成死锁的原因

当这段代码放在主线程里,也即dispatch_get_main_queue()中,执行到sync时向dispatch_get_main_queue()插入同步thread,sync会等到里面的block执行完成才返回。sync又在主队列里面,是个串行队列,sync是后面才加入的,前面一个是主线程,所以sync想执行block必须等待前一个主线程执行完成,而主线程却在等待sync返回,才能执行后续工作,从而造成死锁。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容

  • 多线程学习笔记-GCD 我把这篇文章所用到的代码总结到这里->GCD项目总结下载地址-GCD-wxk可以下载参考 ...
    wxkkkkk阅读 532评论 0 2
  • 一、简介在iOS所有实现多线程的方案中,GCD应该是最有魅力的,因为GCD本身是苹果公司为多核的并行运算提出的解决...
    MYS_iOS_8801阅读 571评论 0 0
  • 前言 嘿嘿嘿,精品。 概述 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”。纯C语...
    Ostkaka丶阅读 1,100评论 0 12
  • 一、基本概念 线程是用来执行任务的,线程彻底执行完任务A才能执行任务B,为了同时执行两个任务,产生了多线程 1、进...
    空白Null阅读 671评论 0 3
  • 你将老去,我将老去,无人幸免。 这个世界上,有些人有多冷漠,有些人就有多温暖,希望你总是遇到,那些温暖对你...
    一颗半糖阅读 223评论 0 5