iOS中的常见多线程方案
GCD的队列
GCD的队列可以分为2大类型
并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)- 并发功能只有在
异步(dispatch_async)函数下才有效
串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
各种队列的执行效果
队列组的使用
- 思考:如何用gcd实现以下功能
- 异步并发执行任务1、任务2
- 等任务1、任务2都执行完毕后,再回到主线程执行任务3
有哪些锁?
- OSSpinLock
- os_unfair_lock
- pthread_mutex
- dispatch_semaphore
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
dispatch_semaphore
semaphore叫做”信号量”- 信号量的初始值,可以用来控制线程并发访问的最大数量
- 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
自旋锁、互斥锁比较
- 什么情况使用自旋锁比较划算?
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
- CPU资源不紧张
- 多核处理器
- 什么情况使用互斥锁比较划算?
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
多读单写实现方案
- pthread_rwlock: 读写锁
- dispatch_barrier_async:异步栅栏调用
是否可以在子线程刷新UI?
不可以。因为子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈,这中间的时间非常的短,就让大家误以为线程可以更新UI。如果子线程一直在运行,则子线程中的UI更新的函数栈,主线程无法获知,即无法更新。
viewDidLoad dispatch_sync 主队列同步执行问题
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
结果只会打印1,然后线程卡死,这是为什么呢?
首先执行任务1,这是肯定没问题的,只是接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3。但这是队列,有任务来,当然会将任务加到队尾,然后遵循FIFO原则执行任务。那么,现在任务2就会被追加到最后,任务3排在了任务2前面,问题来了:任务3要任务2执行完才能执行,任务2又排在任务3后面,意味着任务2要在任务3执行完才能执行,所以他们进入互相等待的局面。这就是死锁。
优化方案:
NSLog(@"1");
///获取一个全局队列
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2");
});
NSLog(@"3");
GCD实现多个接口请求完成后刷新UI
///GCD多个接口请求完之后刷新UI
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("zz.zg", DISPATCH_QUEUE_SERIAL);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
///网络请求1
///成功失败,离开组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
///网络请求2
///成功失败,离开组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
///网络请求3
///成功失败,离开组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
///网络请求4
///成功失败,离开组
dispatch_group_leave(group);
});
///4个网络请求结束,更新UI
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
///更新UI
});
});
});
app运行过程中,同时最多有几个线程,怎么实现的高并发?
同时最多有几个线程: 根据cpu的能力,目测:50个
生活中遇到的很多场景,多是IO密集型。解决这类问题的核心思想就是减少cpu空转的时间,增加CPU的利用率。具体有下面两种方法:
1. 限制活动线程的个数不超过硬件线程的个数
- 活动线程指Runnable状态的线程。
- Blocked状态的线程个数不在限制内。Blocked状态的线程都在等待外部事件触发,比如鼠标点击、磁盘IO操作事件,操作系统会将他们移除到调度队列外,所以它们不会消耗cpu时间片。程序可以有很多的线程,但是只要
保证活动的线程个数小于硬件线程的个数,运行效率是可以保证的。 - 计算密集型和IO密集型线程是要分开看待的。计算密集型线程应该永远不被block,大部分时间都要保证runnable状态。要有效的利用处理器资源,可以让计算密集型的线程个数跟处理器个数匹配。而IO密集型线程大部分时间都在等待IO事件,不需要太多的线程。
2. 基于任务的编程(协程)
线程个数跟硬件线程一致。任务调度器把对应的任务放入跟线程做一个映射,放入到相应的线程执行。有几个明显的优势:
按需调度。线程调度器的时间片是公平的分配给各个线程的,因为它不理解程序的业务逻辑。这就跟计划经济样的,极度的公平就是不公平,市场经济这种按需分配才能提高效率。任务调度器是理解任务信息的,可以更有效的调度任务。负载均衡。将程序分成一个个小的任务,让调度器来调度,不让所有的线程空跑,保证线程随时有活干。有效的利用计算资源、平衡计算资源。更易编程。以线程为基础的编程,要提高效率,经常要考虑到底层的硬件线程,考虑线程调度受到的影响。但是如果基于任务来编程,只要集中注意力在任务之间的逻辑关系上,处理好任务之间的关系。调度效率可以交给调度器来管控。