前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >多线程-GCD

多线程-GCD

作者头像
進无尽
发布2018-09-12 18:23:45
9490
发布2018-09-12 18:23:45
举报
文章被收录于专栏:進无尽的文章進无尽的文章

前言

GCD是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时GCD抽象层次最高,当然是用起来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的。


GCD是一种轻量的基于block的线程模型,底层实现主要有Dispatch Queue和Dispatch Source Dispatch Queue :管理block(操作) Dispatch Source :处理事件 Dispatch Queue&Dispatch Source更多相关知识

GCD推出来以后,开发者可以不直接操纵线程,而是将所要执行的任务封装成一个unit丢给线程池去处理,线程池会有效管理线程的并发,控制线程的生死。因此,现在如果考虑到并发场景,基本上是围绕着GCD和NSOperationQueue来展开讨论。

任务和队列

这里就不得不提到两个概念:任务和队列

  • 任务:即操作,就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。调度队列执行任务有两种方式: 同步执行 和 异步执行.

同步派发(sync) 和 异步派发(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!

【异步并不一定会开启多线程,当在主线程中派发任务到主队列后,会等待主线程空闲时才会调度该任务并没有开启新的线程;添加到其他线程时,会开启新的线程调度任务。】 如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。 如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。

  • 队列:调度队列是一个类似对象的结构体,它管理您提交给它的任务。所有的调度队列都是先进先出的数据结构。队列和线程的区别,他们之间并没有“拥有关系(ownership)”。队列用于存放任务。一共有两种队列, 串行队列 和 并行队列

放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。

放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

GCD中不同队列中不同任务的执行情况如下表:

同步执行的任务

异步执行的任务

串行队列中

当前线程,一个一个执行

其他线程,一个一个执行

并行队列中

当前线程,一个一个执行

同时开很多线程,一起执行

队列的创建和执行 #开辟队列的方法: dispatch_queue_t myQueue = dispatch_queue_create("MyQueue", NULL);

代码语言:javascript
复制
  参数1:标签,用于区分队列
  参数2:队列的类型,表示这个队列是串行队列还是并发队列NUll表示串行队列,
  DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。       
  DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。   

 #执行队列的方法
  异步执行
  dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)

   同步执行
  dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)

几个特别的队列

  • 主队列(Main Queue):专门负责调度主线程(Main Thread)的任务,是一个串行队列,没有办法开辟新的线程。任何需要刷新 UI 的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。

这里需要特别说一下:主队列和主线程的关系。 (1)主队列是专门负责调度主线程的任务的。iOS编程中,需要在主线程(主线程,这个线程是其他线程的父线程)中进行操作时,我们经常会用到以下代码: dispatch_async(dispatch_get_main_queue(), ^{ // dispatch_get_main_queue() 实际获取的是 主队列 }); 而且在主队列下的任务不管是异步任务还是同步任务都不会开辟线程,任务只会在主线程顺序执行。比如block内的任务是异步执行,主线程在将当前方法执行完毕之后,才会去继续执行主队列里的任务。 (2)主队列的任务一定在主线程中执行,主线程是可以执行主队列之外(dispatch_get_global_queue)其他队列的任务的。 另外关于主线程中更新UI操作也不是绝对安全的,详细请看这篇文章:主线程中也不绝对安全的 UI 操作

  • 全局队列:本质是一个并发队列,由系统提供,是所有应用程序共享的。方便编程,可以不用创建就直接使用。 #获取全局队列的方法: dispatch_get_global_queue(long indentifier.unsigned long flags) 第一个参数:线程优先级,默认写0就行,不要使用系统提供的枚举类型,因为ios7和ios8的枚举数值不一样,使用数字可以通用。 第二个参数:标记参数,目前没有用,一般传入0. #使用全局队列多线程执行任务 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); #创建多个线程用于填充图片 for (int i=0; i<count; ++i) { #异步执行队列任务 dispatch_async(globalQueue, ^{ [self loadImage:[NSNumber numberWithInt:i]]; }); }

队列组

队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。这是一个很实用的功能。

代码语言:javascript
复制
# 创建队列组
dispatch_group_t group = dispatch_group_create();
# 获取到全局队列
dispatch_queue_t queue =   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

# 多次使用队列组的方法执行任务, 目前API中只有异步方法
//1.执行3次循环
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"group-01 - %@", [NSThread currentThread]);
    }
});

//2.主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue(), ^{
    for (NSInteger i = 0; i < 8; i++) {
        NSLog(@"group-02 - %@", [NSThread currentThread]);
    }
});

//3.执行5次循环
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"group-03 - %@", [NSThread currentThread]);
    }
});

#.都完成后会自动通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"完成 - %@", [NSThread currentThread]);
});

队列组其他实用方法 这也是使用GCD信号量实现多线程同步加锁的实现方式。

dispatch_group_enter

用于添加对应任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞和dispatch_group_notify的block执行

代码语言:javascript
复制
void dispatch_group_enter(dispatch_group_t group);

dispatch_group_leave

用于减少任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enter和dispatch_group_leave要匹配,不然系统会认为group任务没有执行完毕

代码语言:javascript
复制
void dispatch_group_leave(dispatch_group_t group);

dispatch_group_wait

等待组任务完成,会阻塞当前线程,当任务组执行完毕时,才会解除阻塞当前线程

代码语言:javascript
复制
long dispatch_group_wait(dispatch_group_t group, 
                     dispatch_time_t timeout);
***********************************
group   ——需要等待的任务组
timeout ——等待的超时时间(即等多久),单位为dispatch_time_t。
如果设置为DISPATCH_TIME_FOREVER,则会一直等待(阻塞当前线程),直到任务组执行完毕。

信号量

当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,但如何在GCD中快速的控制并发呢?答案就是 dispatch_semaphore.

信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。

在GCD中有三个函数是semaphore的操作,分别是:

dispatch_semaphore_create   创建一个semaphore dispatch_semaphore_signal   发送一个信号 dispatch_semaphore_wait    等待信号 第一个函数有一个整形的参数,我们可以理解为信号的总量; dispatch_semaphore_signal是发送一个信号,自然会让信号总量+1, dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1, 特别说明下:信号总量为0时dispatch_semaphore_wait会阻塞当前的线程(主线程、其他线程),被阻塞的线程中无法执行任何代码。 根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。

使用GCD的信号量实现并发的控制

创建了一个初使值为10的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了10个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为10的一个线程队列。

GCD执行任务的其他一些常用方法

代码语言:javascript
复制
#重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。 
dispatch_apply():
#单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。 
dispatch_once():
#延迟一定的时间后执行。 
dispatch_time():
#使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)
dispatch_barrier_async(): 
#实现对任务分组管理,如果一组任务全部完成可以通过
dispatch_group_async(): 
#方法获得完成通知(需要定义dispatch_group_t作为分组标识)。       
dispatch_group_notify()

一个栗子:多个并发网络请求完成后执行下一步

  • 1.使用GCD的dispatch_group_t -(void)Btn2{ NSString *str = @"http://www.jianshu.com/p/6930f335adba"; NSURL *url = [NSURL URLWithString:str]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; dispatch_group_t downloadGroup = dispatch_group_create(); for (int i=0; i<10; i++) { dispatch_group_enter(downloadGroup); NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%d---%d",i,i); dispatch_group_leave(downloadGroup); }]; [task resume]; } dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ NSLog(@"end"); }); } 创建一个dispatch_group_t, 每次网络请求前先dispatch_group_enter,请求回调后再dispatch_group_leave,对于enter和leave必须配合使用,有几次enter就要有几次leave,否则group会一直存在。当所有enter的block都leave后,会执行dispatch_group_notify的block。
  • 2.使用GCD的信号量dispatch_semaphore_t -(void)Btn3{ NSString *str = @"http://www.jianshu.com/p/6930f335adba"; NSURL *url = [NSURL URLWithString:str]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; dispatch_semaphore_t sem = dispatch_semaphore_create(0); for (int i=0; i<10; i++) { NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%@",[NSThread currentThread]) NSLog(@"%d---%d",i,i); count++; if (count==10) { dispatch_semaphore_signal(sem); count = 0; } }]; [task resume]; } dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"end"); }); } 开始信号量为0,等待,等10个网络请求都完成了,dispatch_semaphore_signal(semaphore)为计数+1,然后计数-1返回,程序继续执行。 这里特别说一下,例子中的NSURLSessionDataTask 的block 回调中不是主线程,而是多线程环境。此时的主线程已经被阻塞了,是不会执行任何代码的,只有在子线程中把信号量加1,才能结束主线程的阻塞。 10个网络请求顺序回调。 - (void)toCrashing { NSString *str = @"http://www.jianshu.com/p/6930f335adba"; NSURL *url = [NSURL URLWithString:str]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0]; dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT); for (int i=0; i<10; i++) { NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { dispatch_async(queue, ^{ [lock lockWhenCondition:i]; NSLog(@"%d---%d",i,i); [lock unlockWithCondition:i+1]; }); }]; [task resume]; } } 使用NSConditionLock 可以解决。

小结

GCD的知识很多,本文就不一一罗列了,后续如有新的收获,会持续更新的。


本文参考文章: iOS编程中throttle那些事 关于iOS多线程,你看我就够了 GCD入门(二): 多核心的性能

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.02.14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 任务和队列
  • 队列组
  • 信号量
    • 使用GCD的信号量实现并发的控制
    • GCD执行任务的其他一些常用方法
    • 一个栗子:多个并发网络请求完成后执行下一步
    • 小结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档