[iOS]多线程和GCD
新博客wossoneri.com
进程和线程
进程
是指在系统中正在运行的一个应用程序。
每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。
比如同时打开QQ、Xcode,系统就会分别启动两个进程。
线程
一个进程要想执行任务,必须得有线程(每一个进程至少要有一条线程)
线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行
比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行
线程的串行
一个线程中任务的执行是串行的
如果要在一个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
也就是说,在同一时间内,一个线程只能执行一个任务
比如在一个线程中下载三个文件(分别是文件A、文件B、文件C),下载顺序就是ABC
多线程
一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
如果把进程比作车间,那么线程就相当于车间工人
多线程技术可以提高程序的执行效率,比如同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C)
同一时间,CPU只能处理一条线程,只有一条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
所以如果线程非常非常多,CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低)。
优点:
- 能适当提高程序的执行效率
- 能适当提高资源利用率(CPU、内存利用率)
缺点:
- 开启线程需要占用一定的内存空间,如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU在调度线程上的开销就越大
- 程序设计更加复杂:比如线程之间的通信、多线程的数据共享
移动APP经常使用多线程,因为对APP来说,界面要保持响应用户操作并给以反馈,也就是要保持流畅。所以很多比较耗时的运算就应该放在其他线程中,保证主线程能够及时处理用户操作。
对于iOS程序,使用多线程有几类:
- c语言的
pthread_t - NSThread
- GCD
- NSOperation
使用的比较多的应该就是GCD和NSOperation了,对于这两者的讨论可以看看这个
NSOperation vs Grand Central Dispatch
这里主要介绍GCD
GCD
GCD全称是Grand Central Dispatch,纯c语言提供。
GCD是苹果公司为多核的并行运算提出的解决方案,会自动利用更多的CPU内核(比如双核、四核),会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
GCD中有两个核心概念:
- 任务:执行什么操作
- 队列:用来存放任务
将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行。任务的取出遵循队列的FIFO原则:First in first out
GCD路径iOS usr/include/dispatch/下查看头文件说明
GCD常用方法
执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
用同步的方式执行任务(当前线程中执行)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
用异步的方式执行任务(另起一条线程中执行)
队列
从上面方法第一个参数dispatch_queue_t就是GCD的队列类型。一般分为两大类型:并发队列和串行队列。并发功能只有在异步函数下才有用。
- 同步:在当前线程中执行任务,不具备开启新线程的能力
- 异步:在新的线程中执行任务,具备开启新线程的能力
- 并发:多个任务并发(同时)执行
- 串行:一个任务执行完毕后,再执行下一个任务
获取串行队列:
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
参数为队列名和属性,属性一般用
NULLdispatch_get_main_queue()
获得主队列,主队列是
GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
获取并发队列:
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags);
第一个参数是优先级,第二个暂用0即可
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
关于不同队列的执行任务的效果:

示例代码
一. 异步函数往并发队列添加任务
//获得全局并发队列 执行顺序每次都不一样
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(@"Main Thread: %@", [NSThread mainThread]);
//输出
Main Thread: <NSThread: 0x12de0c090>{number = 1, name = main}
下载图片1---<NSThread: 0x12ddc1140>{number = 6, name = (null)}
下载图片2---<NSThread: 0x12dd80ea0>{number = 7, name = (null)}
下载图片3---<NSThread: 0x12dee45f0>{number = 8, name = (null)}
看到开启了三个子线程执行任务
二. 异步函数往串行队列中添加任务
//创建串行队列 按顺序执行 而且只开启一个线程
dispatch_queue_t queue = dispatch_queue_create("wossoneri", NULL);
//添加任务到队列执行任务
//异步函数 具备开启新线程能力
dispatch_async(queue, ^{
NSLog(@"下载图片1---%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片2---%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片3---%@", [NSThread currentThread]);
});
NSLog(@"Main Thread: %@", [NSThread mainThread]);
//输出
Main Thread: <NSThread: 0x12c60c0d0>{number = 1, name = main}
下载图片1---<NSThread: 0x12c6574f0>{number = 6, name = (null)}
下载图片2---<NSThread: 0x12c6574f0>{number = 6, name = (null)}
下载图片3---<NSThread: 0x12c6574f0>{number = 6, name = (null)}
看到只开启了一条线程,串行执行任务
三. 用同步函数往并发队列中添加任务
//获得全局并发队列 执行顺序每次都不一样
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"Main Thread: %@", [NSThread mainThread]);
//同步
dispatch_sync(queue, ^{
NSLog(@"下载图片1---%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下载图片2---%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下载图片3---%@", [NSThread currentThread]);
});
//输出
Main Thread: <NSThread: 0x136d04ae0>{number = 1, name = main}
下载图片1---<NSThread: 0x136d04ae0>{number = 1, name = main}
下载图片2---<NSThread: 0x136d04ae0>{number = 1, name = main}
下载图片3---<NSThread: 0x136d04ae0>{number = 1, name = main}
发现根本没有开启新线程,直接在主线程顺序执行,并发队列失去了并发功能。
四. 用同步函数往串行队列中添加任务
//创建串行队列 按顺序执行 而且只开启一个线程
dispatch_queue_t queue = dispatch_queue_create("wossoneri", NULL);
NSLog(@"Main Thread: %@", [NSThread mainThread]);
//同步
dispatch_sync(queue, ^{
NSLog(@"下载图片1---%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下载图片2---%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下载图片3---%@", [NSThread currentThread]);
});
//输出
Main Thread: <NSThread: 0x135e0c0b0>{number = 1, name = main}
下载图片1---<NSThread: 0x135e0c0b0>{number = 1, name = main}
下载图片2---<NSThread: 0x135e0c0b0>{number = 1, name = main}
下载图片3---<NSThread: 0x135e0c0b0>{number = 1, name = main}
没有开启新线程,依旧在主线程顺序执行任务。
小结:
- 同步函数(永远)不会开启新线程,不具备开线程的能力
- 异步函数具备开启新线程的能力(但不一定总会开线程?)
- 在串行队列只开启一条线程
- 在并发队列开启多条线程
主队列
主队列是和主线程相关联的队列,主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行。
dispatch_queue_t queue = dispatch_get_main_queue();
使用异步函数执行主队列任务:
//获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//把任务添加到主队列中执行
dispatch_async(queue, ^{
NSLog(@"使用异步函数执行主队列中的任务1--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"使用异步函数执行主队列中的任务2--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"使用异步函数执行主队列中的任务3--%@",[NSThread currentThread]);
});
//输出
Main Thread: <NSThread: 0x13de0c030>{number = 1, name = main}
使用异步函数执行主队列中的任务1--<NSThread: 0x13de0c030>{number = 1, name = main}
使用异步函数执行主队列中的任务2--<NSThread: 0x13de0c030>{number = 1, name = main}
使用异步函数执行主队列中的任务3--<NSThread: 0x13de0c030>{number = 1, name = main}
看到任务都在主线程中执行。
使用同步函数执行主队列任务:
此时会发生死锁。
死锁的原因是:主线程本身是串行队列,串行队列的任务是顺序执行的。
比如下面代码段
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
在主线程中,任务执行的顺序是:
NSLog(@"1");
dispatch_sync()
NSLog(@"3");
NSLog(@"2");
其中
NSLog(@"2");
是作为Block的内容放在队列最后执行。
但dispatch_sync()方法必须返回才能往下执行,其返回的条件是Block的内容执行完毕才行。
也就是说死锁的条件是因为dispatch_sync()方法在等待Block执行完毕,而Block在等待dispatch_sync()方法往下执行才能轮到它。
所以,如果把任务放到主队列中进行处理,那么不论处理函数是异步的还是同步的都不会开启新的线程。
延时执行
可以使用NSObject方法,该方法通常在哪个线程调用,就在哪个线程执行,一般是主线程
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
示例
- (void)viewDidLoad {
NSLog(@"打印线程----%@",[NSThread currentThread]);
//延迟执行
[self performSelector:@selector(runA) withObject:nil afterDelay:2.0];
}
- (void)onBtnClicked {
//在异步函数中执行
dispatch_queue_t queue = dispatch_queue_create("wOw", 0);
dispatch_async(queue, ^{
[self performSelector:@selector(runB) withObject:nil afterDelay:1.0];
});
NSLog(@"异步函数");
}
- (void)runA {
NSLog(@"延迟执行----%@", [NSThread currentThread]);
}
- (void)runB {
NSLog(@"异步函数中延迟执行----%@", [NSThread currentThread]);
}
//输出
2016-05-29 23:31:49.194 FunctionTest[4199:1337673] 打印线程----<NSThread: 0x154e04b60>{number = 1, name = main}
2016-05-29 23:31:51.197 FunctionTest[4199:1337673] 延迟执行----<NSThread: 0x154e04b60>{number = 1, name = main}
2016-05-29 23:31:58.198 FunctionTest[4199:1337673] 异步函数
这里发现,异步下的runB方法似乎并没有执行。换成同步则会执行。
出现这个问题的原因是async开的新线程中的runLoop没有启动,在后面加上
[[NSRunLoop currentRunLoop] run];
即可。好吧..后面再研究一下RunLoop原理...
dispatch_async(queue, ^{
[self performSelector:@selector(runB) withObject:nil afterDelay:1.0];
[[NSRunLoop currentRunLoop] run];
});
//再看输出
2016-05-29 23:37:10.877 FunctionTest[4214:1339152] 打印线程----<NSThread: 0x12660a2a0>{number = 1, name = main}
2016-05-29 23:37:12.879 FunctionTest[4214:1339152] 延迟执行----<NSThread: 0x12660a2a0>{number = 1, name = main}
2016-05-29 23:37:15.150 FunctionTest[4214:1339152] 异步函数
2016-05-29 23:37:16.157 FunctionTest[4214:1339199] 异步函数中延迟执行----<NSThread: 0x1266b3f40>{number = 6, name = (null)}
延时都是正确的。使用异步函数后执行的线程也变为的新线程。
使用GCD方法
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
示例:
NSLog(@"打印线程----%@",[NSThread currentThread]);
//GCD delay
//1 主队列
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]);
});
//2 并发队列
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(when, queue1, ^{
NSLog(@"并发队列-延迟执行------%@",[NSThread currentThread]);
});
//输出
2016-05-30 22:14:43.734 FunctionTest[4507:1470176] 打印线程----<NSThread: 0x15ee0bd40>{number = 1, name = main}
2016-05-30 22:14:49.234 FunctionTest[4507:1470176] 主队列--延迟执行------<NSThread: 0x15ee0bd40>{number = 1, name = main}
2016-05-30 22:14:49.235 FunctionTest[4507:1470304] 并发队列-延迟执行------<NSThread: 0x15ed90390>{number = 6, name = (null)}
看到并发队列开启一个新线程,在新线程执行。主队列直接在主线程执行。
线程切换
之前说过,程序中遇到耗时操作就要把操作放在另外一个线程中执行。当执行过之后,就需要把耗时操作得到的数据带回到主线程对UI进行刷新操作,这时就可以用如下代码。
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执⾏耗时的异步操作
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执⾏UI刷新操作
});
});
一次性代码
这个概念在之前的单例模式中提到过,就是保证一段代码段在程序执行过程中只执行一次。使用的方法就是dispatch_once。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
Block中的代码在整个程序运行期间只执行一次!
队列组
队列组的使用情形是这样:
现在有多个耗时的操作要做,我当然要考虑开启异步的线程去,把任务放到并发队列去做。但此时我需要这几个操作都完成的时候回到主线程来。
此时有一个办法,就是把操作统一放在一个线程里做,这样我能知道线程执行结束的时间,但缺点是这些耗时的操作是串行的。
如果让这些操作并行执行,那效率就更高了,但我该怎么知道全部操作都执行完的时间呢?
这时就用上队列组了。
创建一个队列组,把所有异步操作都放在队列组中,这样队列组执行完会发出一个通知回来。
//创建队列组
dispatch_group_t group = dispatch_group_create();
//异步方法
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行一个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行另一个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程
});
暂时整理这么多,掌握这些可以应对大多数使用到GCD的相关问题了。
Reference
整理多线程篇
GCD中在主线程中用同步函数分派任务到串行队列中会产生死锁是什么原因?
[iOS]多线程和GCD的更多相关文章
- iOS多线程 NSThread/GCD/NSOperationQueue
无论是GCD,NSOperationQueue或是NSThread, 都没有线程安全 在需要同步的时候需要使用NSLock或者它的子类进行加锁同步 "] UTF8String], DISPA ...
- iOS 多线程 之 GCD(大中枢派发)(一)
导语: 本文个人原创,转载请注明出处(http://www.cnblogs.com/pretty-guy/p/8126981.html) 在iOS开发中多线程操作通常是一下3种,本文着重介绍Dispa ...
- IOS多线程(GCD)
简介 Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统.这建立在任务并行执行的线程池模式的基础上的.它首次 ...
- ios 多线程小结----- GCD篇
//3 GCD(充分利用设备的多盒)-------------屏蔽了线程,只能看见任务 队列步骤两步,定制任务,将任务添加到队列.GCD将添加的任务,放到线程中去执行,自动执行,自动释放原则:先进先出 ...
- IOS 多线程 NSOperation GCD
1.NSInvocationOperation NSInvocationOperation * op; NSOperationQueue * que = [[NSOperationQueuealloc ...
- iOS 多线程之 GCD 的基本使用
什么是GCD 全称Grand Central Dispatch 中暑调度器 纯C语言 提供了很多强大的函数 GCD 的优势 GCD是苹果公司为多核的并行运算提出的解决方案 GCD会自动利用更多的CPU ...
- iOS 多线程 之 GCD(大中枢派发)(二)
本文接着上一篇讲.主要讲:dispatch_source. dispatch_source主要用户监听事件,可以监听如下事件 DISPATCH_SOURCE_TYPE_DATA_ADD DISPATC ...
- iOS 多线程:『GCD』详尽总结
本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法.这大概是史上最详细.清晰的关于 GCD 的详细讲解+总结的文章了.通过本文,您将了解到: 1. GCD 简介 2. GCD 任务和队列 ...
- iOS 多线程 GCD part3:API
https://www.jianshu.com/p/072111f5889d 2017.03.05 22:54* 字数 1667 阅读 88评论 0喜欢 1 0. 预备知识 GCD对时间的描述有些新奇 ...
随机推荐
- interface中定义default方法和static方法
interface的default方法和static方法 接口中可以定义static方法,可通过接口名称.方法名()调用,实现类不能继承static方法: 接口中可以定义default方法,defau ...
- MySQL mysqlbinlog 访问mysql-bin日志出错
问题 mysqlbinlog -v -v --base64-output=DECODE-ROWS mysql-bin.000166 | less ERROR: Error in Log_event:: ...
- PHP字母数字验证码和中文验证码
1:字母数字组合的验证码 HTML代码: 验证码:<input type="text" name="code"> <img onclick=& ...
- windows phone开发-windows azure mobile service使用入门
在使用azure之前,我一直只能做本地app,或者使用第三方提供的api,尽管大多数情况下够用,但是仍不能随心所欲操纵数据,这种感觉不是特别好.于是在azure发布后,我就尝试使用azure来做为个人 ...
- SpringBoot 遇到 com.google.guava » guava 组件运行异常问题修复方案
环境 Apache Maven : 3.5.4 org.springframework.boot » spring-boot-starter-parent : 2.0.3.RELEASE io.spr ...
- web自动化测试---xpath方式定位页面元素
在实际应用中,如果存在多个相同元素,包括属性相同时,一般会选用这种方式,当然如果定位属性唯一的话,也是可以使用的,不过这种方式没有像id,tag,name等容易理解,下面讲下xpath定位元素的方法 ...
- centos下如何使用sendmail发送邮件
最近在实施服务端日志监控脚本,需要对异常情况发送邮件通知相关责任人,记录下centos通过sendmail发送邮件的配置过程. 一.安装sendmail与mail 1.安装sendmail: 1) ...
- spring framework核心框架体系结构(转载)
作者:Dreawer 很多人都在用spring开发java项目,但是配置maven依赖的时候并不能明确要配置哪些spring的jar,经常是胡乱添加一堆,编译或运行报错就继续配置jar依赖,导致spr ...
- 从零开始学 Web 之 Ajax(三)Ajax 概述,快速上手
大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...
- 音频标签化1:audioset与训练模型 | 音频特征样本
随着机器学习的发展,很多"历史遗留"问题有了新的解决方案.这些遗留问题中,有一个是音频标签化,即如何智能地给一段音频打上标签的问题,标签包括"吉他"." ...