iOS课程观看笔记(六)---多线程
本节学习内容目录:
问:iOS提供了哪些多线程技术方案?
pthread、NSThread、GCD、NSOperation
GCD
同步\异步 和 串行\并行
dispatch_barrier_async(barrier栅栏)
异步栅栏调用,可以很好的解决异步多读单写问题
dispatch_group
同步\异步 和 串行\并行
sync:同步
async:异步
serial_queue:串行队列
concurrent_queue:并行队列
两两组合,可以由四种不同的组合:
同步串行
异步串行
同步并行
异步并行
同步串行
问:以下代码能执行吗?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
[self test];
});
}
- (void)test
{
NSLog(@"test-[NSThread currentThread] - %@", [NSThread currentThread]);
}
结果:程序崩溃
为什么呢?
上段代码会产生死锁
那么,死锁产生的原因是什么呢?
可以概括为一句话:队列引起的循环等待
知识点:
主队列的任务要在主线程中执行
上图中涉及到了:
同步:sync
队列:主队列(串行)
线程:主线程
任务:viewDidLoad、Block
执行过程是:
主队列中有一个ViewDidLoad任务
通过
dispatch_sync(dispatch_get_main_queue(), ^{
[self test];
});
在主队列中添加新的任务Block
主队列是串行队列,因此按照队列先进先出的特性,先将主队列中的ViewDidLoad任务取出来放进主线程,执行任务。
由于ViewDidLoad任务中,同步调用了Block任务,会先将Block任务执行完毕后,继续执行ViewDidLoad任务。
换句话说,ViewDidLoad任务想要执行完,需要依赖Block任务执行完。也就是图中主队列左边的黑色箭头。
而Block任务想执行完,根据队列先进先出的特性,需要依赖ViewDidLoad任务执行完。也就是图中主队列右边的黑色箭头。
从而,两个任务形成相互等待,产生死锁。
问:异步分派到主队列,其任务执行在主线程还是子线程?
答:主线程
主队列的任务在主线程中执行
问:以下代码分别执行在哪个线程?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1-[NSThread currentThread] - %@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2-[NSThread currentThread] - %@", [NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3-[NSThread currentThread] - %@", [NSThread currentThread]);
});
});
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"4-[NSThread currentThread] - %@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"5-[NSThread currentThread] - %@", [NSThread currentThread]);
});
});
}
运行结果:
4-[NSThread currentThread] - <NSThread: 0x600002355040>{number = 1, name = main}
1-[NSThread currentThread] - <NSThread: 0x6000023286c0>{number = 6, name = (null)}
5-[NSThread currentThread] - <NSThread: 0x600002355040>{number = 1, name = main}
2-[NSThread currentThread] - <NSThread: 0x600002355040>{number = 1, name = main}
3-[NSThread currentThread] - <NSThread: 0x600002355040>{number = 1, name = main}
1是在子线程,原因是异步并发执行任务
2是主线程,虽然2整体执行在线程6中,是子线程,且是async异步,具有开启子线程的能力,但是由于是主队列,所以任务执行在主线程
3是主线程,虽然3整体执行在线程6中,是子线程,且是sync同步,没有开启线程的能力,但是由于是主队列,所以任务执行在主线程
4是主线程,sync是同步,没有开启线程的能力,任务执行在当前线程,当前线程是主线程,因此任务执行在主线程
5是主线程,5整体执行在主线程,async是异步,有开启线程的能力,但是由于是主队列,所以任务执行在主线程
知识点:
Blocks submitted to the main queue MUST be run on the main thread
主队列的任务要在主线程中执行
同步异步主要影响:能不能开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力
创建队列的方式:
//创建队列(方法一):串行队列
dispatch_queue_t queue = dispatch_queue_create(“myQueue”, NULL);//第一个参数是字符串,是队列的名称。第二个是队列属性,NULL代表串行
//创建队列(方法二):串行队列
dispatch_queue_t queue = dispatch_queue_create(“myQueue”, DISPATCH_QUEUE_SERIAL);
//创建队列(方法三):并发队列
dispatch_queue_t queue = dispatch_queue_create(“myQueue”, DISPATCH_QUEUE_CONCURRENT);
//创建队列(方法四):主队列(特殊的串行队列)
dispatch_queue_t queue = dispatch_get_main_queue();
//创建队列(方法五):全局队列(并发队列)
dispatch_queue_t queue = dispatch_get_global_queue(0, 0)
问:下列代码执行有什么问题?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"1-[NSThread currentThread] - %@", [NSThread currentThread]);
});
}
结果:
1-[NSThread currentThread] - <NSThread: 0x600001ead040>{number = 1, name = main}
正常执行,没有问题
dispatch_sync是同步执行,没有开启线程的能力,也就是Block任务是在主线程执行。
问:以下代码执行结果是什么?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1-[NSThread currentThread] - %@", [NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2-[NSThread currentThread] - %@", [NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3-[NSThread currentThread] - %@", [NSThread currentThread]);
});
NSLog(@"4-[NSThread currentThread] - %@", [NSThread currentThread]);
});
NSLog(@"5-[NSThread currentThread] - %@", [NSThread currentThread]);
}
打印结果:
1-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}
2-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}
3-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}
4-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}
5-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}
结果分析:
1没问题
2,由于是同步,是立即在当前线程执行,当前线程是主线程,且是全局并发队列,因此,可以执行
其余结果一样。
问:以下代码执行结果是什么?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1-[NSThread currentThread] - %@", [NSThread currentThread]);
[self performSelector:@selector(test) withObject:nil afterDelay:0.0];
NSLog(@"2-[NSThread currentThread] - %@", [NSThread currentThread]);
});
}
- (void)test
{
NSLog(@"test-[NSThread currentThread] - %@", [NSThread currentThread]);
}
打印结果:
1-[NSThread currentThread] - <NSThread: 0x600002baa780>{number = 3, name = (null)}
2-[NSThread currentThread] - <NSThread: 0x600002baa780>{number = 3, name = (null)}
可以发现,1和2的结果可以正常打印,而test不能够执行打印,为什么呢?
asyn异步,dispatch_get_global_queue(0, 0)全局并发,所以,block会在子线程执行,通过打印结果3也可以看出,确实是在子线程执行的任务。
Block会在GCD底层所维护的线程池当中的某一线程去执行,而GCD底层分派的线程,默认情况下是没有开启对应的RunLoop的。即使是0.0秒,也需要创建任务提交到RunLoop上面。由于RunLoop没有开启,因此,test方法不能够执行。
dispatch_barrier_async()
异步栅栏调用
问:如何利用GCD实现多读单写?
或者,如何实现一个多读单写?
使用dispatch_barrier_async()
dispatch_group_async()
问:如何实现这个需求:A、B、C三个任务并发,完成后执行任务D?
使用dispatch_group_async()
NSOperation
需要和NSOperationQueue配合使用来实现多线程方案
问:NSOperation相比其他多线程技术方案有哪些优势和特点?
1 添加任务依赖
2 任务执行状态的控制
3 最大并发量的控制
任务执行状态的控制
问:可以控制NSOperation哪些状态?或者,关于NSOperation的任务状态都有哪些?
(需加强学习)
isReady
isExecuting
isFinished
isCancelled
NSThread(需加强)
问:如何通过NSThread结合RunLoop实现一个常驻线程?
问:NSThread的内部实现机制?
可通过GunStep查看和分享源码
内部创建了一个pthread线程
多线程和锁
问:iOS当中都有哪些锁?
或者,你在项目中,都使用过哪些锁?
这个问题第一种问法,你可以说你知道的,还好回答
第二种问法,就有点坑了,基本上都没用过。。。以后被问到用过哪些锁等同于介绍你知道的哪些锁
具体有:
@synchronized:同步的
atomic
OSSpinLock:Spin自旋
NSLock
NSRecursiveLock:(Recursive:循环)
dispatch_semaphore_t:semaphore:信号量
@synchronized
一般在创建单例对象的时候使用
atomic
修饰属性的关键字
对被修饰对象进行原子操作(不负责使用)
赋值 != 使用
OSSpinLock自旋锁
循环等待询问,不释放当前资源
用于轻量级数据访问,简单的int值+1,-1操作
例如:sidetable表中,有spinLock的使用
NSLock
问:下面代码会产生什么问题?为什么?
对同一把锁,重复加锁
解决方法:NSRecursiveLock
NSRecursiveLock
dispatch_semaphore_t信号量
Decrement the counting semaphore. If the resulting value is less than zero, this function waits for a signal to occur before returning.
计数信号量递减。如果结果值小于零,则该函数在返回之前等待出现信号。
Increment the counting semaphore. If the previous value was less than zero, this function wakes a waiting thread before returning.
增加计数信号量。如果前一个值小于零,该函数将在返回前唤醒一个等待的线程。
这个翻译,明明是 less than zero,小于0,怎么老是讲课是 <= 0?
仔细看,英文是the previous value
而,上一个英文是the resulting value
我理解的是:the resulting value 是结果值,也就是value,the previous value 是指的value前的值,由于value做了 value = value + 1;操作,也就是the resulting value = the previous value + 1;
the previous value < 0;
the resulting value - 1 < 0;
the resulting value < 1;
the resulting value <= 0;
有哥们找到了源码:
value > 0,则return 0
value <= 0则唤醒等待的线程。
信号量执行过程
假如有5个任务并发执行,信号量值value = 2;
dispatch_semaphore_wait信号量值value-1(2-1 = 1),结果1 >=0则进入执行任务1
dispatch_semaphore_wait信号量值value-1(1-1 = 0),结果0 = 0则进入执行任务2
dispatch_semaphore_wait信号量值value-1(0-1 = -1),结果-1 < 0,则主动阻塞等待任务3
dispatch_semaphore_wait信号量值value-1(-1-1 = -2),结果-2 < 0,则主动阻塞等待任务4
dispatch_semaphore_wait信号量值value-1(-2-1 = -3),结果-3 < 0,则主动阻塞等待任务5
任务1执行完毕
dispatch_semaphore_signal信号量值value + 1(-3 + 1 = -2),结果-2 < 0,则唤醒一个等待的任务3
任务2执行完毕
任务3执行完毕
dispatch_semaphore_signal信号量值value + 1(-2 + 1 = -1),结果-1 < 0,则唤醒一个等待的任务4
dispatch_semaphore_signal信号量值value + 1(-1 + 1 = 0),结果0 = 0,则唤醒一个等待的任务5
任务4执行完毕
任务5执行完毕
dispatch_semaphore_signal信号量值value + 1(0 + 1 = 1),结果1 > 0,return 0;
dispatch_semaphore_signal信号量值value + 1(1 + 1 = 2),结果2 > 0,return 0;
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
- (void)viewDidLoad {
[super viewDidLoad];
self.semaphore = dispatch_semaphore_create(2);
for (int i = 0; i<5; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
}
}
- (void)test
{
NSLog(@"dispatch_semaphore_wait");
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"dispatch_semaphore_signal");
dispatch_semaphore_signal(self.semaphore);
}
执行结果:
2020-07-31 19:18:58.821671+0800 block2[37362:733466] dispatch_semaphore_wait
2020-07-31 19:18:58.821472+0800 block2[37362:733464] dispatch_semaphore_wait
2020-07-31 19:18:58.821586+0800 block2[37362:733465] dispatch_semaphore_wait
2020-07-31 19:18:58.822075+0800 block2[37362:733467] dispatch_semaphore_wait
2020-07-31 19:18:58.822167+0800 block2[37362:733468] dispatch_semaphore_wait
2020-07-31 19:19:00.826471+0800 block2[37362:733466] dispatch_semaphore_signal
2020-07-31 19:19:00.845063+0800 block2[37362:733464] dispatch_semaphore_signal
2020-07-31 19:19:02.826900+0800 block2[37362:733465] dispatch_semaphore_signal
2020-07-31 19:19:02.848695+0800 block2[37362:733467] dispatch_semaphore_signal
2020-07-31 19:19:04.827253+0800 block2[37362:733468] dispatch_semaphore_signal
从结果时间来看,dispatch_semaphore_wait是一次性执行
dispatch_semaphore_signal是两秒执行两秒执行
由于进口处是2,则,最大并发数为2
dispatch_semaphore_wait若为负数,则执行_dispatch_semaphore_wait_slow进入等待。
dispatch_semaphore_signal能够唤醒一个在dispatch_semaphore_wait中等待的线程
参考文章:
iOS源码解析: GCD的信号量semaphore
上一篇: OSGI学习笔记(六)