学习MJ的视频课程,整理总结知识点–队列组、自旋锁
面试题一:子线程添加timer会不执行
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
运行结果:打印“1 3”
一句话概括原因:performSelector:withObject:afterDelay是RunLoop中的代码,依靠RunLoop来执行,而子线程没有RunLoop或者子线程RunLoop没有启动
performSelector:withObject:afterDelay
是RunLoop中的代码
底层用到了定时器、定时器添加到RunLoop里的,RunLoop被唤醒时才会处理timer任务,异步线程不执行timer是因为子线程RunLoop没有启动(需要启动RunLoop)[[NSRunLoop currentRunLoop] run];
面试题二:没有RunLoop,子线程执行完就会自动退出
1 | NSThread *thread = [[NSThread alloc] initWithBlock:^{ |
performSelector:onThread:withObject:waitUntilDone:是通过RunLoop来处理的,子线程默认没有RunLoop,此时子线程内任务执行完就自动退出了,再执行test
函数会崩溃。
除非子线程内启动了RunLoop
两个函数的对比:performSelector:withObject:
调用的底层msgSendperformSelector:withObject:afterDelay
调用的底层是RunLoop中的API
两者调用的本质不太一样
RunLoop源码不开放,但是有组织用自己的方式实现了一遍Cocoa的源码,就是GNUstep
1 | GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍 |
线程安全问题
卖票问题、存取钱问题
解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
常见的线程同步技术是:加锁
线程安全问题解决
解决方案:加锁
iOS中常用的锁有
- OSSpinLock
- os_unfair_lock
- pthread_mutex
- dispatch_semaphore
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
OSSpinLock
1 | static OSSpinLock moneyLock = OS_SPINLOCK_INIT; |
要用同一把锁(锁被加了的标记,有标记就不进去执行了,等待这个锁被解标记后才能进去执行)
等待锁的线程会处于忙等,一致占用cpu资源,while等待
thread1
thread2
thread3
线程的调度
时间片轮训
线程优先级高的话,会分配的时间片更多
导致OSSpinLock的问题问题:优先级反转
如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
(因为它是占用cpu忙等,所以执行效率是比较高的)
底层实现
通过汇编查看stepinstruction、stepi、si
os_unfair_lock
iOS10 API_AVAILABLE
和OSSpinLock的使用方式非常像
Low-level lock 等待过程中会休眠,自旋锁不是,等待时是一直占用cpu等
通过汇编查看stepinstruction、stepi、si
pthread_mutex
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
递归锁
一个函数内加锁,内部调用另一个函数,这个函数内也加同样的锁,结果会出现死锁
递归函数也会出现同样的问题
解决:使用递归锁
递归锁:允许同一个线程对一把锁进行重复加锁
1 | // 初始化属性 |
递归锁可以对同一把锁重复加锁,但是重复加锁仅限同一个线程,重复加锁最终也都会做同样次数的释放锁。
当第二个线程进来发现已经加锁时,就等待之前的线程释放锁
条件锁
// 等待
pthread_cond_wait(&_cond, &_mutex);
// 信号
pthread_cond_signal(&_cond);
执行流程:
pthread_cond_wait睡眠等待、释放锁,等signal条件唤醒
signal条件唤醒,解锁完成,等待的线程重新加锁,继续往下执行,完成后解锁
使用场景:
线程依赖的实现
NSLock、NSRecursiveLock
NSLock是对mutex普通锁的封装
NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致
NSCondition
条件锁
NSCondition是对mutex和cond的封装
NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
可以设置只有满足条件才加锁,真正使用的场景不多
dispatch_queue(DISPATCH_QUEUE_SERIAL)
串行队列,让队列中的任务串行执行,防止并发访问的安全问题。也就是多线程中的任务串行执行
1 | - (instancetype)init { |
semaphore
semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
1 | static dispatch_semaphore_t semaphore; |
@synchronized
@synchronized是对mutex递归锁的封装
源码查看:objc4中的objc-sync.mm文件
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
选择对象
self、类对象、oncelock对象
iOS线程同步方案性能比较
性能从高到低排序
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
自旋锁、互斥锁比较
什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器
什么情况使用互斥锁比较划算?
预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈
总结
os_unfair_lock iOS10
dispatch_semaphore 多样性
pthread_mutex 跨平台 多样性
参考和源码
源码: