多线程GCD

什么是GCD

全称是Grand Central Dispatch
纯C语言,提供了非常多强大的函数
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

GCD有三种队列:

  • main queue:是一个串行队列, 队列中的任务按FIFO(first input first output,先进先出)的顺序执行。一般用来更新UI
//获取主队列
 dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • global queue: 全局队列, 是一个并行队列,即队列中的任务执行顺序和进入队列的顺序无关
 //开启异步线程, 在全局队列中执行     
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          [self dosomething]; 
  });
  • custom queue:
    并发队列只有在异步(dispatch_async)函数下才有效!!!
    GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
//(1)使用dispatch_get_global_queue函数获得全局的并发队列
    //参数1: 优先级
 //参数2: 暂时无用参数 (传0)
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 1.GCD有两种任务派发方式: 同步(`dispatch_sync`)和异步(`dispatch_async`)
// 这小段代码有问题, 出现了线程死锁
// 提示: 下面的代码在主线程`main_thread`中执行
   - (void)viewDidLoad{
         dispatch_queue_t queue = dispatch_get_main_queue();
         dispatch_sync(queue, ^{});
     }

dispatch_async(queue, block):直接回到调用线程(不阻塞调用线程)。
dispatch_sync(queue, block): 阻塞调用线程,等待 block() 执行结束,回到调用线程。
两者的区别:就是看是否阻塞调用线程。

同步和异步主要影响:能不能开启新的线程
同步:只是在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力
并发和串行主要影响:任务的执行方式
并发:允许多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务

主队列的特点是发现当前主线程中有任务在执行, 那么主队列会暂停调用队列中的任务, 直到队列中空闲为止。所以在主队列(main_thread)中调用dispatch_sync时会出现线程死锁
2.串行队列(Serial Dispatch Queue)和并行队列(Concurrent Dispatch Queue)区别是: 串行队列使用一个线程执行
并发功能只有在异步(dispatch_async)函数下才有效
创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);

  -(void)serialDisPatchQueue{
        dispatch_queue_t queue1 =  dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue1, ^{
             NSLog(@"download1---%@---", [NSThread currentThread]);
        });
            
        dispatch_queue_t queue2 =  dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue2, ^{
             NSLog(@"download2---%@---", [NSThread currentThread]);
        });

        dispatch_queue_t queue3 =  dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue3, ^{
             NSLog(@"download3---%@---", [NSThread currentThread]);
        });

        dispatch_queue_t queue4 =  dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue4, ^{
             NSLog(@"download4---%@---", [NSThread currentThread]);
        });
}
 download3---<NSThread: 0x60000007ad40>{number = 5, name = (null)}---
 download2---<NSThread: 0x608000266200>{number = 4, name = (null)}--- 
 download1---<NSThread: 0x608000260e40>{number = 3, name = (null)}---
 download4---<NSThread: 0x60800007b580>{number = 6, name = (null)}---

多个线程更新相同资源导致数据竞争时使用串行队列Serial Dispatch Queue
* 异步函数+并发队列
- (void)asyncConcurrent{
// 1.创建队列
/*
第一个参数: C语言的字符串, 标签
第二个参数: 队列的类型
DISPATCH_QUEUE_CONCURRENT: 并发
DISPATCH_QUEUE_SERIAL: 串行
*/
dispatch_queue_t concurrent_queue = dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_CONCURRENT);

            // 2.封装任务, 添加任务到队列中
            dispatch_async(concurrent_queue, ^{
                NSLog(@"download1---%@", [NSThread currentThread]);
            });

            dispatch_async(concurrent_queue, ^{
                NSLog(@"download2---%@", [NSThread currentThread]);
            });

            dispatch_async(concurrent_queue, ^{
                NSLog(@"download3---%@", [NSThread currentThread]);
            });
             NSLog(@"Running on main Thread");
        }

    打印结果
        NSLog(@"Running on main Thread");
        download2---<NSThread: 0x600000272700>{number = 4, name = (null)}
        download1---<NSThread: 0x608000263680>{number = 3, name = (null)}
        download3---<NSThread: 0x608000267ec0>{number = 5, name = (null)}
    1.在dispatch_async使用的Thread是不同线程, 因为他们的指针地址完全不同, number也可以作为线程的标识, 说明开启了多条线程
     2.`Running on main Thread`这句话并没有在最后打印, 说明开启了新的线程
     3.打印结果并不是顺序的, 说明是并发执行的

-ps: 但是工作中并行的情况不是一整块需要使用的话,也不会选择这种方式,所以它在工作中的应用相比如下的方式会更少一些
* 异步函数+串行队列
- (void)asyncSerial
{
dispatch_queue_t seaial_queue = dispatch_queue_create("download", DISPATCH_QUEUE_SERIAL);

            dispatch_async(seaial_queue, ^{
                NSLog(@"download1---%@", [NSThread currentThread]);
            });

            dispatch_async(seaial_queue, ^{
                NSLog(@"download2---%@", [NSThread currentThread]);
            });

            dispatch_async(seaial_queue, ^{
                NSLog(@"download3---%@", [NSThread currentThread]);
            });

            NSLog(@"Running on main Thread");

        }            

打印结果
Running on main Thread
download1---<NSThread: 0x608000261900>{number = 4, name = (null)}
download2---<NSThread: 0x608000261900>{number = 4, name = (null)}
download3---<NSThread: 0x608000261900>{number = 4, name = (null)}
1.在dispatch_async使用的是同一个Thread, 因为number和地址完全相同
2.打印顺序和代码相同, 符合FIFO原则
3.Running on main Thread这句话并没有在最后打印, 说明开启了新的线程
-串行队列可以保障消息的顺序,在直播间聊天/顺序操作数组等事件时都非常有用

 * 同步函数+并发队列

(不会开启新线程,并发执行任务失效!)
- (void)syncConcurrent
{
dispatch_queue_t concurrent_queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

            dispatch_sync(concurrent_queue, ^{
                NSLog(@"download1------%@", [NSThread currentThread]);
            });

            dispatch_sync(concurrent_queue, ^{
                NSLog(@"download2------%@", [NSThread currentThread]);
            });

            dispatch_sync(concurrent_queue, ^{
                NSLog(@"download3------%@", [NSThread currentThread]);
            });
            NSLog(@"Running on main Thread");

        }

打印结果
download1------<NSThread: 0x608000066a40>{number = 1, name = main}
download2------<NSThread: 0x608000066a40>{number = 1, name = main}
download3------<NSThread: 0x608000066a40>{number = 1, name = main}
2017-03-10 18:28:16.331 Import[67018:21512467] Running on main Thread
在同步函数+并发队列时不会开启线程, 在主线程中执行, 任务是串行执行的

 * 同步函数+串行队列

       - (void)syncSerial
       {
           dispatch_queue_t concurrent_queue = dispatch_queue_create("download", DISPATCH_QUEUE_SERIAL);

           dispatch_sync(concurrent_queue, ^{
               NSLog(@"download1------%@", [NSThread currentThread]);
           });

           dispatch_sync(concurrent_queue, ^{
               NSLog(@"download2------%@", [NSThread currentThread]);
           });

           dispatch_sync(concurrent_queue, ^{
               NSLog(@"download3------%@", [NSThread currentThread]);
           });
           NSLog(@"Running on main Thread");

       }

打印结果

download1------<NSThread: 0x600000079740>{number = 1, name = main}
download2------<NSThread: 0x600000079740>{number = 1, name = main}
download3------<NSThread: 0x600000079740>{number = 1, name = main}
Running on main Thread

说明并没有开启新的线程, 任务是串行执行

IvAv6fy.png!web.png
  1. 队列组
- (void)group{
// 创建队列
dispatch_queue_t queue = dispatch_queue_create(0, 0);
// 创建队列组
dispatch_group_t group = dispatch_group_create();

// 1) 封装任务
// 2) 把任务添加到队列中
// 3) 会监听任务的执行情况, 通知group
dispatch_group_async(group, queue, ^{
    NSLog(@"1-----%@", [NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"2-----%@", [NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"3-----%@", [NSThread currentThread]);
});

// 拦截通知, 当队列组中所有的任务都执行完毕的时候进入会进入到下面的方法
dispatch_group_notify(group, queue, ^{
    NSLog(@"-------dispatch_group_notify--------");
});

NSLog(@"---end---");
}

打印的结果
1-----<NSThread: 0x600000270c80>{number = 3, name = (null)}
---end---
2-----<NSThread: 0x600000270c80>{number = 3, name = (null)}
3-----<NSThread: 0x600000270c80>{number = 3, name = (null)}
-------dispatch_group_notify--------
根据end打印的位置可以看出 dispatch_group_notify 这个函数本身是异步的

应用场景
  1. 下载图片1
  1. 下载图片2
  2. 合成图片并显示图片
    因为3需要等待1和2执行完才能执行, 那么可以把1和2两个线程放在队列组中, 等到队列组完成再执行3
- (void)group1{
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 下载图片1, 开启子线程
    dispatch_group_async(group, queue, ^{
        
        NSURL *url = [NSURL URLWithString:@""];
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        self.image1 = [UIImage imageWithData:imageData];
        
    });
    
    // 下载图片2, 开启子线程
    dispatch_group_async(group, queue, ^{
        
        NSURL *url = [NSURL URLWithString:@""];
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        self.image2 = [UIImage imageWithData:imageData];
        
        
    });
    
    // 合并图片
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        
        [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
        
        [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
        
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        UIGraphicsEndImageContext();
        
        // 更新UI
        NSLog(@"UI---%@", [NSThread currentThread]);
        self.imageView.image = image;
    });

}

如果合成图片显示在界面上的操作并不耗时也可以写在主线程中

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    
    [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
    
    [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    // 更新UI
    NSLog(@"UI---%@", [NSThread currentThread]);
    self.imageView.image = image;
});

这两种的区别是如果是合成图片比较耗时可以使用上面的方法在子线程中执行。下面dispatch_get_main_queue()是全局并发队列, dispatch_group_t group = dispatch_group_create();是创建的并发队列, 两种的区别

  • 全局并发队队列在整个应用程序中本身是默认存在的, 并且对应有高优先级、默认优先级、低优先级和后台优先级一种四个并发队列, 我们只是拿来其中的一个直接拿来用, 而Create函数是真实的从头开始去创建一个队列。
  • 在使用栅栏函数的时候, 苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效
  • 其他区别涉及到XNU内核的系统级线程编程, 不一一列举

队列组还可以解决这样一些问题, 比如因为一些服务器之类的原因, 一个界面可能有会两个接口, 在两个接口请求之前需要显示转圈, 只有等待两个接口都请求结束的时候才能停止转圈刷新这个界面, 这个时候就可以使用队列组, 在dispatch_group_notify方法中刷新界面

GCD 的其他用法

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

推荐阅读更多精彩内容