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

iOS多线程之三:GCD的使用

作者头像
s_在路上
发布2018-09-11 17:16:41
2.8K0
发布2018-09-11 17:16:41
举报
文章被收录于专栏:iOS 开发杂谈iOS 开发杂谈

一、什么是GCD GCD是Grand Central Dispatch的简称,它是基于C语言的。如果使用GCD,完全由系统管理线程,不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。 二、GCD的使用 首先看下这段代码:

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

上面的这段代码是一个简单的异步任务,通过这段代码,引出了下面的几个名词: 1、async(异步)与sync(同步): 具体概念:iOS多线程(一) 当然,我们也可以使用同步任务,使用dispatch_sync函数添加到相应的队列中,而这个函数会阻塞当前调用线程,直到相应任务完成执行。

但是,也正因为这样的同步特性,在实际项目中,当有同步任务添加到正在执行同步任务的队列时,串行的队列会出现死锁。而且由于同步任务会阻塞主线程的运行,可能会导致某个事件无法响应。 2、队列(queue): 队列的基本原理:先进先出(FIFO),先进队列的元素先出队列。 在GCD中,可以给开发者调用的常见公共队列有以下两种:

dispatch_get_global_queue:用于获取应用全局共享的并发队列 (提供多个线程来执行任务,所以可以按序启动多个任务并发执行。可用于后台执行任务)
dispatch_get_main_queue:用于获取应用主线程关联的串行调度队列(只提供一个线程执行任务。运行的main主线程,一般用于UI的搭建)

这两种公共队列的调用便可以解决关于后台执行任务、主线程用于更新UI界面的问题,代码如下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 把逻辑计算等需要消耗长时间的任务,放在此处的全局共享的并发队列执行;
    dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主线程更新UI界面;
    });
});

异步下载图片

// 异步下载图片  
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
    NSURL *url = [NSURL URLWithString:@"http://img.51fanxing.com/spec/9579/u_20120110174805627264.jpg"];  
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];  

    // 回到主线程显示图片  
    dispatch_async(dispatch_get_main_queue(), ^{  
        self.imageView.image = image;  
    });  
});

2.1、获取主线程队列

dispatch_queue_t queue = dispatch_get_main_queue();

Paste_Image.png

2.2、获取全局队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

Paste_Image.png

通过dispatch_get_global_queue方法获取的全局队列都是并行队列,并且队列不能被修改。 ps: identifier:用以标识队列优先级 flags:苹果预留的,传入任何非0的值都可能导致返回NULL

Paste_Image.png

2.3、创建队列

dispatch_queue_t queue = dispatch_queue_create("com.51fanxing.queue", NULL);

Paste_Image.png

ps: label: 队列的名称,调试的时候可以区分其他的队列 attr: 队列的属性,dispatch_queue_attr_t类型。用以标识队列串行,并行,以及优先级等信息

Paste_Image.png

DISPATCH_QUEUE_SERIAL或者NULL,表示创建串行队列,优先级为目标队列优先级。 DISPATCH_QUEUE_CONCURRENT表示创建并行队列,优先级也为目标队列优先级。 dispatch_queue_attr_make_with_qos_class函数可以创建带有优先级的dispatch_queue_attr_t对象。通过这个对象可以自定义queue的优先级。

Paste_Image.png

attr: 传入DISPATCH_QUEUE_SERIAL、NULL或者DISPATCH_QUEUE_CONCURRENT,表示串行或者并行 qos_class: 传入qos_class枚举,表示优先级级别 relative_priority: 相对于qos_class的相对优先级,qos_class用于区分大的优先级级别,relative_priority表示大级别下的小级别。relative_priority必须大于QOS_MIN_RELATIVE_PRIORITY小于0,否则将返回NULL。从GCD源码中可以查到QOS_MIN_RELATIVE_PRIORITY等于-15。

2.4、串行队列和并行队列 串行队列指同一时间每次只能执行一个任务。线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。可以添加多个任务到串行队列中,执行顺序按照先进先出(FIFO),如果需要并发地执行大量任务,应该把任务提交到全局并发queue来完成才能更好地发挥系统性能。

利用dispatch_queue_create函数创建串行queue,两个参数分别是queue名和一组queue属性:

dispatch_queue_t queue = dispatch_queue_create("com.51fanxing.queue", NULL);  

Paste_Image.png

从以上代码中可以看出,后面所添加的任务也必须等待前面的任务完成后才能执行,类似我们前面所讲”饭堂”排队的例子,队列完全按照”先进先出”的顺序,也即是所执行的顺序取决于:开发者将工作任务添加进队列的顺序。

并行队列可以同时执行多个任务,系统会维护一个线程池来保证并行队列的执行。线程池会根据当前任务量自行安排线程的数量,以确保任务尽快执行。 并发dispatch queue可以同时并行地执行多个任务,不过并发queue仍然按先进先出(FIFO)的顺序来启动任务。但是任务结束的顺序则依赖各自的任务所需要消耗的时间。并发queue同时执行的任务数量会根据应用和系统动态变化,各种因素包括:可用核数量、其它进程正在执行的工作数量、其它串行dispatch queue中优先任务的数量等。与串行队列的不同,虽然启动时间一致,但是这是“并发执行”,因此不需要等到上一个任务完成后才进行下一个任务。并发queue会在之前的任务完成之前就出列并开始执行下一个任务。

Paste_Image.png

从以上代码中可以看出,与串行不同的是,不需要等到A任务调用完,就已经在调用B、C,显著地提高了线程的执行速度,凸显了并行队列所执行的异步操作的并行特性; 另外,从这段代码中,不同的是串行队列需要创建一个新的队列,而并行队列中,只需要调用iOS系统中为我们提供的全局共享dispatch_get_global_queue就可以了:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3、dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

});

需要注意的是,使用dispatch_after实现延迟执行某动作,时间并不是很精确,因为main dishpatch queue在主线程的runLoop中执行,所以比如在每隔1/60秒执行的RunLoop中,block最快在三秒后执行,最慢在3秒+1/60秒后执行,并且在main dishpatch queue有大量追加处理货主线程本身的任务处理有延迟时,这个时间会增加。 如果对时间的精确度没有高要求,只是为了推迟执行,那么使用dispatch_after还是很不错的。 ps: 1、NSObject中提供的线程延迟方法

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

2、通过NSTimer来延迟线程执行

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];

4、dispatch_once 一般我们会利用dispatch_once创建单例

Paste_Image.png

Paste_Image.png

从上面代码中可以看出 第一个参数predicate,该参数是检查后面第二个参数所代表的代码块是否被调用的谓词, 第二个参数则是在整个应用程序中只会被调用一次的代码块。dispach_once函数中的代码块只会被执行一次,而且还是线程安全的。

5、dispatch_apply

Paste_Image.png

从上面代码中可以看出,这些迭代是并发执行的 和普通for循环一样,dispatch_applydispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程,主线程中调用这两个函数必须小心,可能会阻止事件处理循环并无法响应用户事件。所以如果循环代码需要一定的时间执行,可以考虑在另一个线程中调用这两个函数。如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。

6、dispatch_group_t,dispatch_group_notify 可以使用dispatch_group_async函数将多个任务关联到一个dispatch group和相应的queue中,group会并发地同时执行这些任务。而且dispatch group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。

Paste_Image.png

7、dispatch_barrier_async 在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用dispatch_barrier_async函数将任务加入到并行队列之后,任务会在前面任务全部执行完成之后执行,任务执行过程中,其他任务无法执行,直到barrier任务执行完成。

有时候我们会需要这样的一个场景,A任务和B任务执行完毕之后,在执行C任务,需要借助dispatch_barrier_async这个函数。

Paste_Image.png

Paste_Image.png

从代码中可以看出确实只有在前面A、B任务完成后,barrier任务才能执行,最后才能执行C任务。 注意: 使用dispatch_barrier_async,该函数只能搭配自定义并行队列dispatch_queue_t使用。不能使用:dispatch_get_global_queue,否则dispatch_barrier_async的作用会和dispatch_async的作用一模一样。

8、信号量 个人理解,在多线程下使用信号量可以控制多线程的并发数目。

创建信号量,可以设置信号量的资源数。0表示没有资源,调用dispatch_semaphore_wait会立即等待。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

等待信号,可以设置超时参数。该函数返回0表示得到通知,非0表示超时。

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

通知信号,如果等待线程被唤醒则返回非0,否则返回0。

dispatch_semaphore_signal(semaphore);

比如,执行10个任务,然后等待2秒,然后继续执行。

Paste_Image.png

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档