iOS多线程篇-剖析GCD

这篇介绍GCD

什么是GCD
1- 全称Grand Central Dispatch
2- 它是纯C语言开发的一套多线程机制,提供了非常多强大的函数,但它是面向过程的

优点
1- 对于多核运算更有效,会自动利用更多的CPU内核
2- 自动管理线程的生命周期(创建,调度,销毁),你只需要告诉他要执行什么任务即可

特点
GCD有一个队列,队列里存放着任务,GCD统一管理整个队列中的任务,队列分为串行和并行两种
任务 : 你要执行的操作
队列 : 存放任务
队列执行任务方式 : 你若将任务添加至队列后,GCD会自动将队列中的任务取出(先进先出,后进后出),放到对应的线程去执行
1- 同步执行任务 : 在当前线程执行,不开启新线程
2- 异步执行任务 : 在其他线程执行,开启新线程
队列分类
串行队列 : 只有一个线程,加入队列的操作按添加顺序依次完成
并发队列 : 自动开启多个线程,多个任务同时执行,但也是先进来的任务优先处理
特殊队列主队列 : 用来执行主线程上的操作任务

备注
同步和异步决定了是否开启新的线程
串行和并发决定了任务的执行方式

执行任务方式.png
队列类型.png

下面做个测试:
分别同步和异步添加三个任务,查看它们在主队列,手动创建串行队列,全局并发队列中开启的线程数,以及执行任务方式.

总结

** 其他**
GCD存在于libdispatch库中,任何iOS程序,在运行时,默认会动态加载这个库,不需要我们手动导入.

举个在并发队列中异步执行的例子

    //获得全局的并发队列
    dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //添加任务到队列中,就可以执行任务
    //异步函数:具备开启新线程的能力
    dispatch_async(queue, ^
                   {
                       NSLog(@"任务1----%@",[NSThread currentThread]);
                   });
    dispatch_async(queue, ^
                   {
                       NSLog(@"任务2----%@",[NSThread currentThread]);
                   });
    dispatch_async(queue, ^
                   {
                       NSLog(@"任务3----%@",[NSThread currentThread]);
                   });
    //打印主线程
    NSLog(@"主线程----%@",[NSThread mainThread]);

同步函数 + 主队列 = 死锁

原因 :
因为同步任务是,一添加到队列上就要马上执行,而主队列只有一条线程(主线程),此时主线程在等待主队列调度同步任务,而主队列发现主线程上还有任务未执行完,就不会让同步任务添加到主线程上,所以主线程和主队列互相等待.
1- 此时主线程在等待主队列调度同步任务
2- 而主队列在等待主线程执行完已有的任务

    NSLog(@"主线程----%@",[NSThread mainThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^
                  {
                      NSLog(@"任务1----%@",[NSThread currentThread]);
                  });
    dispatch_sync(queue, ^
                  {
                      NSLog(@"任务2----%@",[NSThread currentThread]);
                  });
    dispatch_sync(queue, ^
                  {
                      NSLog(@"任务3----%@",[NSThread currentThread]);
                  });

线程之间的通信

    dispatch_async(queue, ^
    {
        //其他操作,例如下载数据等
        //子线程回到主线程
        dispatch_async(dispatch_get_main_queue(), ^
        {
             //UI刷新
        });
    });

示例

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    dispatch_queue_t queue = dispatch_queue_create("DaQianQian", NULL);
    dispatch_async(queue, ^
    {
        NSLog(@"当前线程%@",[NSThread currentThread]);
        //从网络上下载图片
        NSURL *urlstr=[NSURL URLWithString:@"http://h.hiphotos.baidu.com/baike/w%3D268/sign=30b3fb747b310a55c424d9f28f444387/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg"];
        NSData *data=[NSData dataWithContentsOfURL:urlstr];
        UIImage *image=[UIImage imageWithData:data];
        NSLog(@"图片加载完毕");
        //子线程回到主线程
        dispatch_async(dispatch_get_main_queue(), ^
        {
            self.imageView.image=image;
            //打印当前线程
            NSLog(@"当前线程%@",[NSThread currentThread]);
        });
    });
}

线程延迟

调用NSObject方法,不会卡住当前线程
例如 : 延迟2S后,在哪个线程调的就自动回到哪个线程,调用run方法,可以传递参数withObject:nil

[self performSelector:@selector(run) withObject:nil afterDelay:2.0];

注意 : 如果队列是主队列,就在主线程执行,如果是并发队列,就会新开子线程执行,但是!同步函数执行,异步函数并不执行!

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
        dispatch_queue_t queue = dispatch_queue_create("QianQian", 0);
        dispatch_sync(queue, ^
               {
                 [self performSelector:@selector(test1) withObject:nil afterDelay:5.0];
              });
        NSLog(@"同步函数执行");
      //如果使用的是dispatch_async,则不执行
}
-(void)test1
{
        NSLog(@"异步函数中延迟执行----%@",[NSThread currentThread]);
}


原因是:
performSelector withObject afterDelay 方法在子线程中,并不会调用SEL方法,而performSelector withObject 方法会直接调用.原因在于一下两点:

  1. afterDelay方法是使用当前线程的定时器在一定时间后调用SEL,而无afterDelay的方法是直接调用SEL.
  2. 子线程中默认是没有定时器的.

解决办法有两种:
解决办法一 : 开启线程的定时器

[[NSRunLoop currentRunLoop] run]; 

结果如下

        dispatch_queue_t queue = dispatch_queue_create("QianQian", 0);
        dispatch_async(queue, ^
               {
                 [self performSelector:@selector(test1) withObject:nil afterDelay:5.0];
                   [[NSRunLoop currentRunLoop] run];
              });
        NSLog(@"同步异步函数都执行");

解决办法二 : 使用GCD函数dispatch_after来执行定时任务,请往下看:

使用GCD函数,不会卡住当前线程

//参数:什么时间/执行哪个队列/执行什么任务
dispatch_after(dispatch_time_t when,
    dispatch_queue_t queue,
    dispatch_block_t block);
//例如 : 延迟5S后去主线程执行block中的打印
 dispatch_queue_t queue= dispatch_get_main_queue();
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^
         {
                 NSLog(@"主队列--延迟执行------%@",[NSThread currentThread]);
         });

3- 上一篇提到过的sleepForTimeInterval方法,会卡主线程

[NSThread sleepForTimeInterval: 5];

只执行一次

使用dispatch_once函数, 能保证某段代码, 在整个APP的生命周期, 只被执行1次!

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
        {
           // 只执行1次的代码(这里面默认是线程安全的)
        });

//例子
@property(nonatomic,assign) BOOL log;

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        {
            NSLog(@"该行代码只执行一次");
        });


    //你也可以用BOOL值判断来控制,但下次进入此控制器,还是会执行
    if (_log==NO)
        {
            NSLog(@"该行代码只执行一次");
            _log=YES;
        }
}

队列组

假如我们现在有一个需求,要求开两个线程,线程1循环打印1000遍A,线程2循环打印1000遍B,两个线程同时打印,打印完成后,回主线程输出"完成".
按照上面整理的知识,我们会认为,开启一个并发的队列,异步执行操作,然后回主线程即可,代码如下:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
           {
                for(int A = 0 ; A < 1000; A++)
                    {
                        NSLog(@"A = %d,线程是%@",A,[NSThread currentThread]);
                    }
                for(int B = 0 ; B < 1000; B++)
                    {
                       NSLog(@"B = %d,线程是%@",B,[NSThread currentThread]);
                    }
               
         
                 dispatch_async(dispatch_get_main_queue(), ^
                  {
                       NSLog(@"回到主线程---%@",[NSThread currentThread]);
                 });
             });
}


你会发现,A和B是在一个线程中执行的,并且是循环完A才开始循环B,等B循环结束后,再回到主线程.
所以,碰到这种需求的时候,我们应该用队列组来解决

队列组使用场景
1- 你需要异步执行两个耗时的操作
2- 等2个异步操作都执行完毕后,再回到主线程执行操作

队列组使用方法
1- 创建一个组
2- 开启一个任务执行任务1
3- 开启一个任务执行任务2
4- 同时执行任务1任务2操作
5- 等组中的所有任务都执行完毕, 再回到主线程执行其他操作

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
    {
        for(int A = 0 ; A < 1000; A++)
          {
              NSLog(@"A = %d,线程是%@",A,[NSThread currentThread]);
          }
    });
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
    {
        for(int B = 0 ; B < 1000; B++)
          {
             NSLog(@"B = %d,线程是%@",B,[NSThread currentThread]);
          }
    });
dispatch_group_notify(group,dispatch_get_main_queue(), ^
    {
        NSLog(@"回到主线程---%@",[NSThread currentThread]);
    });
    
}

这样就能完美实现啦 !

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

推荐阅读更多精彩内容