多线程(GCD)
一.实现方式
- pthread: C语言实现,不常用
- nsthred: 基本上也不用
- gcd: 常用(个人常用)
- NSOperation(对gcd的封装)
二.生命周期(五种状态)
- 新建:实例化线程对象
- 就绪:向线程对象发送
start
消息,线程对象被加入可调度线程池等待CPU调度。 - 运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
- 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。
sleepForTimeInterval
(休眠指定时长),sleepUntilDate
(休眠到指定日期),@synchronized(self):
(互斥锁)。 - 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象
- 还有线程的
exit
和cancel
-
[NSThread exit]:
一旦强行终止线程,后续的所有代码都不会被执行。 -
[thread cancel]
取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记。
三.多线程安全问题
原因: 多个线程同时访问一个资源所引起的数据错乱问题
解决方案
- 同步锁(互斥锁):
@synchronized(锁对象(一般用self)) {
// 需要锁定的代码
}
- 自旋锁(耗性能) :加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成 .
atomic
修饰的带有自旋锁,所以说是线程安全的.但是取值的时候是可以有多个线程同时取值的,nonatomic
是线程不安全的,但是效率高,一般使用nonatomic
关于GCD
1: GCD特点
- GCD会自动利用CPU的多核特性
- GCD自动管理线程的生命周期,开发人员只需告诉GCD如何执行,执行什么任务
2: GCD的基本概念
- 队列: 装载线程任务的队形结构(先进先出),有两种队列
穿行队列
和并发队列
3: 队列的创建方法
dispatch_queue_t queue = dispatch_queue_create(const char * _Nullable label,
dispatch_queue_attr_t _Nullable attr)
参数说明:第一个为标识符,可不传;第二个参数为队列类型(并发队列DISPATCH_QUEUE_CONCURRENT
,穿行队列DISPATCH_QUEUE_SERIAL
);
此外还有两种创建队列的方法
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);//获取全局并发队列
dispatch_queue_t queue = dispatch_get_main_queue();//获取主队列
4: 任务的创建方式
- 同步:dispatch_sync
- 异步:dispatch_async
dispatch_sync(dispatch_get_global_queue(0, 0), ^{// 同步执行任务
NSLog(@"我是同步执行的任务");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{// 异步执行任务
NSLog(@"我是异步执行的任务");
});
5: GCD的使用
组合方式:有六种组合方式(串行/并发/主队列 ✖️同步/异步)
- 串行同步
- 串行异步
- 并发同步
- 并发异步
- 主队列同步
- 主队列异步
同步/异步
决定是否开新线程,串行/并发/主队列
决定是否按顺序执行任务
特别说明:5.主队列同步,会发生死锁,程序奔溃
- (void)testSync {
dispatch_sync(dispatch_get_main_queue(), ^{
//需要执行的任务
});
}
原因:在主线程中使用同步对于任务是立刻执行的,即:block中的任务会立刻执行,但是此时主线程正在处理testSync()
方法,所以任务就需要等待testSync()
执行后才能执行,但是该任务就处于testSync()
方法中,所以就造成无限等待中也就是死锁
GCD实用API
-
dispatch_after
:延时一定时间后处理任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
//3秒后需要做的事情
});
-
dispatch_barrier_async
:栅栏函数,用于分割任务的执行顺序
dispatch_queue_t asyncQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(asyncQueue, ^{
for (NSUInteger i = 0; i < 10; i++) {
NSLog(@"11111--%zd--%@",i,[NSThread currentThread]);
}
});
dispatch_async(asyncQueue, ^{
for (NSUInteger i = 0; i < 10; i++) {
NSLog(@"2222--%zd--%@",i,[NSThread currentThread]);
}
});
dispatch_barrier_async(asyncQueue, ^{
NSLog(@"我在这里把他拦截下来");
});
dispatch_async(asyncQueue, ^{
for (NSUInteger i = 0; i < 10; i++) {
NSLog(@"33333--%zd--%@",i,[NSThread currentThread]);
}
});
dispatch_async(asyncQueue, ^{
for (NSUInteger i = 0; i < 10; i++) {
NSLog(@"44444--%zd--%@",i,[NSThread currentThread]);
}
});
根据log可以发现,'3','4' 都会再'1','2'执行完之后才会执行。这样就达到了栅栏的效果。
注意: 栅栏函数不能使用全局并发队列, 必须是自己通过函数创建的并发队列,否则将达不到栅栏效果
-
dispatch_apply
:快速迭代,直接看效果
CFTimeInterval startTime = CFAbsoluteTimeGetCurrent();
// for (int i = 0; i < 10000; i ++) {
// NSLog(@"");
// }//2.395429
dispatch_queue_t concurrentQ = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10000, concurrentQ, ^(size_t i) {
NSLog(@"");
});//2.40
CFTimeInterval endTime = CFAbsoluteTimeGetCurrent();
NSLog(@"%f",endTime-startTime);
可能是我在循环中没有做什么操作吧,for循环竟然更快dispatch_apply
是dispatch_sync
函数和dispatch_group
的关联API
- dispatch_group_t:等待几个耗时且无先后任务全部执行完毕回到主线程的场景可以使用
dispatch_group_t group = dispatch_group_create();//队列组创建
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"有一个任务完成!");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"有一个任务完成!");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"前面两个任务执行完毕,更新UI");
});