今天和大家一起来讨论一下GCD的基本使用,有疏忽的地方,还望各位不吝赐教。
一、多线程简介
1、多线程相关的概念
进程:
1、进程是指在系统中正在运行的一个应用程序;
2、每个进程是独立的,每个进程运行在专用且受保护的内存空间中。
线程:
1、一个进程执行任务,必须依赖线程(一个进程至少有一条线程);
2、进程的所有任务都在线程中执行。
线程的串行:
1、一个线程中任务的执行是串行的
2、如果要在一个线程中执行多个任务,只能一个一个按顺序执行,即同一时间一个线程只能执行一个任务;
线程和进程的比较:
1、线程是CPU调用(执行任务)的最小单位。
2、进程是CPU分配资源的最小单位。
3、一个程序可以对应多个进程,一个进程中可以有多个线程,但至少有一个。
4、同一个进程中的线程共享进程的资源。
多线程:
一个进程中可以开启多条线程的,每一条线程可以并行(同时)执行不同的任务。其实在同一时间,CPU只能处理一条线程,多线程的并发(同时)执行,其实是一种假象,只不过是快速的在多条线程之间进行调度,也就是切换罢了。但是一般不建议开很多条线程,因为开线程越多,相应的开销就越大,对于CPU来说负担就越重。
2、多线程的优缺点
优点:
1、能适当的提高程序的执行效率;
2、能适当的提高资源的利用率(包括CPU和内存)。
缺点:
1、创建线程是有开销的,iOS 9下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB,主线程1MB,也可以使用-setStackSize:进行设置,但是必须是4KB的倍数,最小16KB),创建线程大约需要90毫秒的时间进行创建。
2、如果开启大量的线程,会降低程序的性能。
3、线程越多,CPU在线程上的开销就越大。
4、程序设计更加复杂,包括在线程之间的通信和多线程之间的数据共享。
3、多线程在iOS中的应用
主线程
一个iOS程序运行后,默认会开启一条线程,称为主线程或者UI线程。
主线程的主要作用
1、显示\刷新UI界面;
2、处理UI事件(比如点击事件,滚动事件和拖拽事件等);
3、注意不要将耗时操作放到主线程汇总;
4、耗时操作会卡住主线程,严重影响UI的流畅度,造成不好的用户体验;
5、在开发过程中,注意将耗时操作放到子线程中进行执行。
二、多线程安全
当多个线程同时访问一块资源的时候,会发生数据错乱和数据安全的问题。
解决方案:互斥锁
当线程A访问资源的时候加锁,保证其他线程无法在线程A访问期间进行对本资源的操作,在线程A操作完成后,会打开添加的互斥锁,之后其他线程可以按照同样的方式进行访问资源。
格式:
@synchronized(锁对象) {需要锁定的代码}
使用方式:
@synchronized(self) {}
注意点:
1、锁必须是全局唯一的,所以一般使用控制器作为标识。
2、注意加锁的前提条件,在多线程访问同一块资源的时候再添加。
3、加锁是需要耗费性能的。
4、加锁的结果:线程同步(多条线程在同一条线上执行,按照顺序进行执行)
关于原子属性和非原子属性的解释:
*atomic:
原子属性,会为setter方法加锁,默认为atomic。
因为会为setter方法加锁,所以在使用的时候是线程安全的,但是会消耗大量的资源。
*nonatomic:
非原子属性,不会为setter方法加锁。
虽然nonatomic是不安全的,但是出现多个线程同时访问一块资源的情况很少,为了保证效率,所以一般使用nonatomic。
建议:
尽量把这些争夺资源的任务交给服务器端处理,减小移动端的压力。
三、iOS多线程实现方案
技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 一套通用的多线程API 可移植,适用于多个平台 使用难度大 |
C | 程序员管理 | 几乎不用 |
NSThread | 使用更加面向对象 简单易用,可以直接操作线程对象 |
OC | 程序员管理 | 偶尔使用 |
GCD | 为了替代NSThread等的线程技术 充分利用设备的多核 |
C | 自动管理 | 经常使用 |
NSOperation | 基于GCD 相比GCD多了一些更简单实用的功能 更加面向对象 |
OC | 自动管理 | 经常使用 |
四、GCD多线程实现方案
1、GCD简介
全称是Grand Central Dispatch,是一个纯C语言的库,是苹果公司为多核的并行运算提出的解决方案。
2、GCD优势
1、GCD会自动利用更多的CPU内核(比如双核和四核)
2、GCD会自动管理线程的生命周期(创建线程,调度线程和销毁线程)
3、程序员不需要手动编写管理线程的任何代码,只需要告诉GCD想要执行什么任务
3、任务和队列
任务:执行的操作统称为任务
队列:用来存放任务,并且调度任务在那些线程中执行
4、使用步骤
1、定制任务
确定想做的任务
2、将任务添加到队列中
GCD会自动将队列中的任务取出来,放到对应的线程中执行
任务的取出遵循队列的FIFO的原则:先进先出,后进后出
5、执行任务的方式
GCD中有两个用来执行任务的常用函数
- 同步函数
同步:只能在当前线程中执行任务,不具备开启新线程的能力 - 异步函数
异步:可以在新的线程中执行任务,具备开启新线程的能力
6、队列的类型
GCD中队列分为两大类型
- 并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务) - 串行队列 (Serial Dispatch Queue)
可以让任务一个接着一个的执行(一个执行完之后,再执行下一个任务)
7、术语的相关解释
- 同步和异步主要影响的是能不能开启新的线程 这里指的是函数
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力 - 并发和串行主要影响:任务的执行方式 这里指的是队列
并发:可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
串行:可以让任务一个接着一个的执行(一个执行完之后,再执行下一个任务)
五、pthread简单使用
因为pthread用的不多,所以简单提一下,关于其创建线程的使用如下:
// 1、创建线程对象
pthread_t thread;
// 2、创建线程
/*
* 第一个参数:线程对象,传递地址
* 第二个参数:线程的属性 NULL
* 第三个参数:指向函数的指针
* 第四个参数:函数需要接受的参数
*/
pthread_create(&thread, NULL, task, NULL);
// 3、task实现
void *task(void *param){
NSLog(@"%@",[NSThread currentThread]);
return NULL;
}
六、GCD简单使用
创建并发队列的方式
1、create创建并发队列(主动创建)
/*
* 第一个参数:c语言的字符串,标签
* 第二个参数:队列的类型
* DISPATCH_QUEUE_CONCURRENT 并发
* DISPATCH_QUEUE_SERIAL 串行
*/
dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd",DISPATCH_QUEUE_CONCURRENT);
2、获取全局并发队列(系统中本身存在,拿过来用就行了)
/* 和DISPATCH_QUEUE_CONCURRENT创建的并发队列 是等价的
* 第一个参数:优先级 四种 一般用默认的即可
#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 最低的优先级
* 第二个参数:系统说明是给未来使用的,使用的时候传参为0即可。
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
创建串行队列的方式
1、create创建串行队列(主动创建)
/*
* 第一个参数:c语言的字符串,标签
* 第二个参数:队列的类型
* DISPATCH_QUEUE_CONCURRENT 并发
* DISPATCH_QUEUE_SERIAL 串行 或者传递NULL
*/
dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd",DISPATCH_QUEUE_SERIAL);
2、使用主队列(跟主线程相关联的队列)
/*
* 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务都会放在主线程中进行执行。
* 第一个参数:c语言的字符串,标签
* 第二个参数:队列的类型
* DISPATCH_QUEUE_CONCURRENT 并发
* DISPATCH_QUEUE_SERIAL 串行 或者传递NULL
*/
函数和队列的组合情况
1、异步函数 + 并发队列: 会开启多条线程,队列中的任务是异步(并发)执行
// 1.创建队列
/*
* 第一个参数:c语言的字符串,标签
* 第二个参数:队列的类型
* DISPATCH_QUEUE_CONCURRENT 并发
* DISPATCH_QUEUE_SERIAL 串行
*/
dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd", DISPATCH_QUEUE_CONCURRENT);
// 2、封装任务 > 将任务添加到队列中
/*
* 第一个参数:要执行的队列
* 第二个参数:要执行的任务
* 注意:
并不是有几个任务就开几条线程,具体开几条线程由系统进行衡量!
*/
dispatch_async(queue, ^{
NSLog(@"1---------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2---------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3---------%@",[NSThread currentThread]);
});
// 打印结果:
3---------<NSThread: 0x60800007e880>{number = 5, name = (null)}
1---------<NSThread: 0x600000078700>{number = 3, name = (null)}
2---------<NSThread: 0x60800007e2c0>{number = 4, name = (null)}
2、异步函数 + 串行队列: 会开启一条线程,队列中的任务是同步(串行)执行的
// 1.创建队列
/*
* 第一个参数:c语言的字符串,标签
* 第二个参数:队列的类型
* DISPATCH_QUEUE_CONCURRENT 并发
* DISPATCH_QUEUE_SERIAL 串行
*/
dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd", DISPATCH_QUEUE_SERIAL);
// 2、封装任务 > 将任务添加到队列中
/*
* 第一个参数:要执行的队列
* 第二个参数:要执行的任务
*/
dispatch_async(queue, ^{
NSLog(@"1---------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2---------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3---------%@",[NSThread currentThread]);
});
// 打印结果:
1---------<NSThread: 0x60000007d180>{number = 3, name = (null)}
2---------<NSThread: 0x60000007d180>{number = 3, name = (null)}
3---------<NSThread: 0x60000007d180>{number = 3, name = (null)}
3、同步函数 + 并发队列:不会开线程,任务是串行执行的
// 1.创建队列
/*
* 第一个参数:c语言的字符串,标签
* 第二个参数:队列的类型
* DISPATCH_QUEUE_CONCURRENT 并发
* DISPATCH_QUEUE_SERIAL 串行
*/
dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd", DISPATCH_QUEUE_CONCURRENT);
// 2、封装任务 > 将任务添加到队列中
/*
* 第一个参数:要执行的队列
* 第二个参数:要执行的任务
*/
dispatch_sync(queue, ^{
NSLog(@"1---------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2---------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3---------%@",[NSThread currentThread]);
});
// 打印结果:
1---------<NSThread: 0x60000007e800>{number = 1, name = main}
2---------<NSThread: 0x60000007e800>{number = 1, name = main}
3---------<NSThread: 0x60000007e800>{number = 1, name = main}
4、同步函数 + 串行队列:不会开线程,任务是串行执行的(效果和3同)
// 1.创建队列
/*
* 第一个参数:c语言的字符串,标签
* 第二个参数:队列的类型
* DISPATCH_QUEUE_CONCURRENT 并发
* DISPATCH_QUEUE_SERIAL 串行
*/
dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd",DISPATCH_QUEUE_SERIAL);
// 2、封装任务 > 将任务添加到队列中
/*
* 第一个参数:要执行的队列
* 第二个参数:要执行的任务
*/
dispatch_sync(queue, ^{
NSLog(@"1---------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2---------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3---------%@",[NSThread currentThread]);
});
// 打印结果:
1---------<NSThread: 0x60000007e800>{number = 1, name = main}
2---------<NSThread: 0x60000007e800>{number = 1, name = main}
3---------<NSThread: 0x60000007e800>{number = 1, name = main}
5、异步函数 + 主队列:不会开线程,任务是串行执行的
// 1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2、封装任务 > 将任务添加到队列中
/*
* 第一个参数:要执行的队列
* 第二个参数:要执行的任务
*/
dispatch_async(queue, ^{
NSLog(@"1---------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2---------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3---------%@",[NSThread currentThread]);
});
// 打印结果:
1---------<NSThread: 0x60000007e800>{number = 1, name = main}
2---------<NSThread: 0x60000007e800>{number = 1, name = main}
3---------<NSThread: 0x60000007e800>{number = 1, name = main}
6、同步函数(立刻马上执行) + 主队列:死锁
// 1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2、封装任务 > 将任务添加到队列中
/*
* 第一个参数:要执行的队列
* 第二个参数:要执行的任务
*/
dispatch_sync(queue, ^{
NSLog(@"1---------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2---------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3---------%@",[NSThread currentThread]);
});
// 说明:
主队列的特点:如果主队列发现当前主线程正在执行任务,会等到主线程执行完当前任务后,再调度主队列中的任务,如果在子线程中使用以下方法就可以避免死锁
同步函数:立刻马上执行,我不执行完,后面的谁都别想执行
异步函数:如果我不执行完,后面的也可以执行
各种队列执行总结
并发队列 | 手动创建串行队列 | 主队列 | |
---|---|---|---|
同步 | 没有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
异步 | 开启新线程 并发执行任务 |
开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
七、GCD线程之间的通信
// 1、获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// 2、封装任务 > 将任务添加到队列中
dispatch_async(queue, ^{
// 在这里执行子线程中相关耗时的操作
/*
例如网络请求或者下载任务
*/
dispatch_async(dispatch_get_main_queue(), ^{
// 在这里进行和主线程的通信,主要是刷新UI的相关操作,这里用同步函数也是可以的,并不会造成死锁。
});
});
八、GCD常用函数
1、延迟执行
1)延迟执行的第一种方法
[self performSelector:@selector(task) withObject:nil afterDelay:2.0];
2)延迟执行的第二种方法
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
3)延迟执行的第二种方法
/* 主要可以控制延迟执行的操作在哪个队列中执行 dispatch_get_main_queue() 修改此参数即可
* 第一个参数:DISPATCH_TIME_NOW 从现在开始计算时间
* 第二个参数:延迟的时间 GCD的时间单位 纳秒
* 第三个参数:dispatch_get_main_queue 主队列
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"delay----%@",[NSThread currentThread]);
});
2、一次性执行
使用场景:单例模式(在以后的分享中会提到。。。。。。)
// static 静态全局变量,作用域是整个应用程序的生命周期
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"once-----");
});
注意:
一次性代码不能放在懒加载中 会出问题
因为此代码只执行一次,在第一次创建的时候会把对象返回,但是在第二次创建对象的时候,不会再执行一次性代码,所以对象返回值为空。
3、GCD的栅栏函数
控制并发队列中异步执行任务的执行顺序。
// 获得全局并发队列
// 栅栏函数不能使用全局并发队列
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue = dispatch_queue_create("en", DISPATCH_QUEUE_CONCURRENT);
// 异步函数
dispatch_async(queue, ^{
NSLog(@"1--------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2--------%@",[NSThread currentThread]);
});
// 栅栏函数 先执行1、2,关于1、2的先后是不能确定的,再执行3。
dispatch_barrier_async(queue, ^{
NSLog(@"+++++++++++++++++++");
});
dispatch_async(queue, ^{
NSLog(@"3--------%@",[NSThread currentThread]);
});
// 打印结果:
1--------<NSThread: 0x6000000754c0>{number = 3, name = (null)}
2--------<NSThread: 0x60000006a340>{number = 4, name = (null)}
+++++++++++++++++++
3--------<NSThread: 0x60000006a340>{number = 4, name = (null)}
4、GCD快速迭代
迭代其实就是遍历,说白了就是一个循环。
// 1)快速迭代的第一种方法
for循环,但是for循环是同步执行的。
// 2)快速迭代的第二种方法
/* 主线程和子线程一起完成任务 任务的执行是并发的
* 第一个参数:迭代的次数
* 第二个参数:并发队列 只能传并发队列,如果传主队列,死锁,如果传手动创建的串行队列跟for循环效果一样
* 第三个参数:索引
*/
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%@-----%zu",[NSThread currentThread],index);
});
5、GCD定时器
/** 定时器 */
@property (nonatomic, strong) dispatch_source_t timer;
// 创建timer
/* GCD中的定时器是绝对精准的,不会受到RunLoop的影响!
* 第一个参数:类型 DISPATCH_SOURCE_TYPE_TIMER
* 第二个参数:描述信息
* 第三个参数:更详细的描述信息
* 第四个参数:队列 决定GCD的timer在那个线程中执行
*/
// 以局部变量的形式进行定义,timer不会执行的,因为在执行之前已经被释放了
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
/*
* 第一个参数:定时器对象
* 第二个参数:DISPATCH_TIME_NOW 起始时间
* 第三个参数:间隔时间
* 第四个参数:精准度 允许误差,此处传0即可
*/
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
// 设置定时器的任务
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"执行任务");
});
// 开启定时器
dispatch_resume(self.timer);
九、GCD队列组的使用
跟栅栏函数相类似,也是用来控制并发队列中异步执行任务的执行顺序的。
// 1、获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2、创建队列组
dispatch_group_t group = dispatch_group_create();
// 3、异步函数
/* 这个函数做了什么?
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]);
});
// 4、拦截通知,当所有的队列组中的任务执行完毕以后会执行这个方法
dispatch_group_notify(group, queue, ^{
NSLog(@"end--------end");
});
// 打印结果:
2---------<NSThread: 0x60800007a8c0>{number = 7, name = (null)}
1---------<NSThread: 0x60800007a800>{number = 6, name = (null)}
3---------<NSThread: 0x608000078980>{number = 8, name = (null)}
end--------end
另外一种GCD队列组的实现方式(以前使用的)
// 获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 此方法之后执行的执行的异步任务会被纳入到队列组的监听范围内
// 配对使用
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"1---------%@",[NSThread currentThread]);
// 离开群组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"2---------%@",[NSThread currentThread]);
// 离开群组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"3---------%@",[NSThread currentThread]);
// 离开群组
dispatch_group_leave(group);
});
// 当所有的队列组中的任务执行完毕以后会执行这个方法
// 内部本身是异步的,不会阻塞
dispatch_group_notify(group, queue, ^{
NSLog(@"end--------end");
});
// 其他的监听方法 DISPATCH_TIME_FOREVER 死等 等到所有任务都执行完毕才执行这个方法
// 这里是阻塞的,如果下面方法没有执行,以后的方法不会执行的。
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
十、GCD异步函数的补充
GCD异步函数有两种实现方式,具体的异同比较如下:
1、block 实现
// dispatch_async(<dispatch_queue_t _Nonnull queue>, <^(void)block>)
2、方法调用的方式实现
/*
* 第一个参数:队列
* 第二个参数:参数
* 第三个参数:要调用的函数名称
*/
dispatch_async_f(dispatch_get_global_queue(0, 0), nil ,task);
dispatch_async_f(dispatch_get_global_queue(0, 0), nil ,task);
dispatch_async_f(dispatch_get_global_queue(0, 0), nil ,task);
// 调用函数的实现
void task(void *params){
NSLog(@"%s--------%@",__func__,[NSThread currentThread]);
}
网友评论