OC源码:https://opensource.apple.com/tarballs/objc4/
GCD源码:https://github.com/apple/swift-corelibs-libdispatch
iOS中常见多线程方案
同步,异步,串行队列,并发队列之间关系
同步
- 主队列 (没有开辟新线程 串行执行任务),主队列本来是同步串行执行任务,如果再在主队列同步执行任务,会发生死锁
- 并发队列 (没有开辟新线程 串行执行任务)
- 手动创建的串行队列 (没有开辟新线程 串行执行任务)
异步
- 主队列 (没有开辟新线程 串行执行任务)
- 并发队列 (开辟了新线程 并行执行任务)
- 手动创建的串行队列 (开辟了新线程 串行执行任务)
队列组
//任务1 任务2 并发执行 都执行完毕后在主线程执行任务3
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1-%d-%@",i,[NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2-%d-%@",i,[NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务3-%d-%@",i,[NSThread currentThread]);
}
});
});
//1 2任务完成后并发执行 4 5
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务4-%d-%@",i,[NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务5-%d-%@",i,[NSThread currentThread]);
}
});
多线程安全隐患
资源共享
- 同一块资源被多个线程访问
解决方案
常见的线程同步技术
- OSSpinLock 自旋锁
等待锁的线程处于忙等状态,一直占用CPU资源,响应快,目前不再安全,有优先级反转问题,如果等待锁的线程优先级较高,会一直占用CPU资源,优先级低的线程就无法释放锁
#import <libkern/OSAtomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT;
//加锁
OSSpinLockLock(&_lock);
//解锁
OSSpinLockUnlock(&_lock);
- os_unfair_lock 互斥锁
等待锁的线程处于休眠状态
#import <os/lock.h>
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&_lock);
//解锁
os_unfair_lock_unlock(&_lock);
*递归锁:允许同一线程多次加锁,另一线程执行到锁会等待
- pthread_mutex
pthread跨平台,可以修改属性成为互斥锁或者递归锁
pthread_cond 条件用于线程间通讯
- NSLock是对pthread_mutex 普通锁的封装
//加锁
- (void)lock;
//解锁
- (void)unlock;
- (BOOL)tryLock; //尝试加锁,不阻塞线程
- (BOOL)lockBeforeDate:(NSDate *)limit; // 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。会阻塞线程
NSRecursiveLock是对pthread_mutex 递归锁的封装
NSCondition是对pthread_mutex和pthread_cond的封装
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
NS_CLASS_AVAILABLE(10_5, 2_0)
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
- NSConditionLock是对NSCondition的封装
- GCD 串行队列
- dispatch_semaphore 信号量
//最大并发数是2
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
//判断当前信号量的值是否 > 0, 大于0,将信号量-1,执行下面的。否则一直休眠等待。 如果是DISPATCH_TIME_NOW,则大于0,将信号量-1,往下执行,或者小于等于0,则不等,往下执行
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
//给信号量+1
dispatch_semaphore_signal(self.semaphore);
- @synchronized是对mutex递归锁的封装
@synchronized (<#token#>) {
<#statements#>
}
1.打断点得到图2汇编代码调用
2.红框类似于加锁解锁操作
3.在objc源码objc-sync.mm中找到这两个方法实现
4.找到结构体SyncData的定义
5.跟进成员recursive_mutex_t
6.跟进recursive_mutex_tt,找到构造函数,PTHREAD_RECURSIVE_MUTEX_INITIALIZER证明该锁是递归锁
性能排序
自旋锁和互斥锁比较
自旋锁OSSpinLock已经废弃
选用自旋锁
(1).预计线程等待锁的时间很短
(2).加锁的代码(临界区)经常被调用,但竞争情况很少发生
(3).CPU资源不紧张时,多核处理器
选用互斥锁
(1).预计线程等待锁的时间较长
(2).单核处理器
(3).临界区有IO操作
(4).临界区代码复杂
(5).临界区竞争激烈
atomic和nonatomic
atomic
(1).给属性加上atomic,可以保证属性的set和get都是原子性操作,保证线程同步,查看objc4源码objc-accessors.mm如下
(2).不能保证使用属性的过程是线程安全的
(3).太耗性能
读写安全方案(多读单写)
(1).同一时间,只能有1个线程进行写的操作
(2).同一时间,允许有多个线程进行读的操作
(3).同一时间,不允许既有写的操作,又有读的操作
- pthread_rwlock 读写锁
等待锁的线程会进入休眠
pthread_rwlock_t rwLock ;
//初始化
pthread_rwlock_init(&rwLock, NULL);
//读加锁
pthread_rwlock_rdlock(&rwLock);
//写加锁
pthread_rwlock_wrlock(&rwLock);
//读写解锁
pthread_rwlock_unlock(&rwLock);
//锁销毁
pthread_rwlock_destroy(&rwLock);
- dispatch_barrier_async 异步栅栏调用
这个函数传入的queue必须是自定义的并发队列,如果传入一个串行或者全局并发队列,则等同于dispatch_async的效果
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//read
dispatch_async(queue, ^{
});
//write
dispatch_barrier_async(queue, ^{
});