多线程安全隐患
多个线程同时访问同一个资源(对象/变量/文件等),就可能出现数据错乱和数据安全问题。
比如下面代码,多个线程同时修改一个变量,最终导致数据错乱:
@interface ViewController ()
@property (nonatomic, assign) int ticketsCount;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self saleTickets];
}
- (void)saleTicket
{
int oldTicketsCount = self.ticketsCount;
sleep(0.2f);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d", self.ticketsCount);
}
- (void)saleTickets
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
@end
线程同步
使用 线程同步
技术(同步,就是协同步调,按预定的先后次序进行),常见的线程同步技术就是 加锁
。
iOS中的线程同步方案
性能 从高到低 排序:
os_unfair_lock
:互斥锁,替代OSSpinLock
,不盲等,更安全OSSpinLock
:自旋锁,盲等,不安全dispatch_semaphore
:信号量,控制同时访问资源的线程数,保证线程同步pthread_mutex
:跨平台的锁,苹果对其封装了很多便于使用的类dispatch_queue(DISPATCH_QUEUE_SERIAL)
:GCD
的串行队列
NSLock
:对pthread_mutex
普通锁的封装NSCondition
:对pthread_mutex cond
的封装pthread_mutex(recursive)
:直接使用pthread_mutex
递归锁NSRecursiveLock
:对pthread_mutex
递归锁的封装NSConditionLock
:对NSCondition
进一步封装@synchronized
:对pthread_mutex
递归锁的封装
OSSpinLock(iOS10过期)-自旋锁
OSSpinLock
叫做 自旋锁
,等待锁的线程会处于忙等
(busy-wait
)状态,一直占用着CPU资源。
目前已经 不再安全
,可能会出现 优先级反转问题
。如果等待锁的线程优先级较高,它会一直占用着 CPU 资源,优先级低的线程就无法释放锁。
利用 OSSpinLock
解决上面例子中的线程安全问题(加锁解锁需要使用同一把锁
):
#import <libkern/OSAtomic.h>
@interface ViewController ()
@property (nonatomic, assign) int ticketsCount;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self saleTickets];
}
- (void)saleTicket
{
static OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
int oldTicketsCount = self.ticketsCount;
sleep(0.2f);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d", self.ticketsCount);
OSSpinLockUnlock(&lock);
}
- (void)saleTickets
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
@end
os_unfair_lock(iOS10开始)-互斥锁
os_unfair_lock
用于取代不安全的 OSSpinLock
,从 iOS10
开始才支持。
从底层调用看,等待 os_unfair_lock
锁的线程会处于 休眠
状态,并 非忙等
。
#import <os/lock.h>
@interface ViewController ()
@property (nonatomic, assign) int ticketsCount;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self saleTickets];
}
- (void)saleTicket
{
static os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&lock);
int oldTicketsCount = self.ticketsCount;
sleep(0.2f);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d", self.ticketsCount);
os_unfair_lock_unlock(&lock);
}
- (void)saleTickets
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
@end
pthread_mutex(跨平台)
mutex
叫做 互斥锁
,等待锁的线程会处于 休眠
状态。
pthread_mutex
的 普通锁
和 递归锁
基本使用:
#import <pthread/pthread.h>
@interface ViewController ()
@property (nonatomic, assign) int ticketsCount;
@property (nonatomic, assign) pthread_mutex_t mutex;
@property (nonatomic, assign) pthread_mutexattr_t attr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self saleTickets];
}
- (void)dealloc
{
// 销毁资源
pthread_mutexattr_destroy(&_attr);
pthread_mutex_destroy(&_mutex);
}
- (void)saleTicket
{
// 加锁
pthread_mutex_lock(&_mutex);
int oldTicketsCount = self.ticketsCount;
sleep(0.2f);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d", self.ticketsCount);
// 解锁
pthread_mutex_unlock(&_mutex);
}
- (void)saleTickets
{
// PTHREAD_MUTEX_NORMAL: 普通锁
// PTHREAD_MUTEX_ERRORCHECK: 递归锁
// 初始化锁的属性和锁
pthread_mutexattr_init(&_attr);
pthread_mutexattr_settype(&_attr, PTHREAD_MUTEX_NORMAL);
pthread_mutex_init(&_mutex, &_attr);
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
@end
mutex 递归锁
允许同一个线程对同一把锁进行重复加锁,其他线程会休眠等待加锁的线程解锁后才能加锁。
多个线程进行联动加锁,可以使用 条件
:
// 表示一个条件
pthread_cond_t cond;
// 初始化cond
pthread_cond_init(&cond, NULL);
// 等待激活(唤醒)cond(进入休眠,放开 mutex 锁;被唤醒后,会再次对 mutex 加锁)
pthread_cond_wait(&cond, &_mutex);
// 激活(唤醒)一个等待该cond的线程。不要在锁内调用,防止pthread_cond_wait无法重新加锁。
pthread_cond_signal(&cond);
// 激活(唤醒)所有等待该cond的线程。不要在锁内调用,防止pthread_cond_wait无法重新加锁。
pthread_cond_broadcast(&cond);
// 销毁条件
pthread_cond_destroy(&cond);
pthread_mutex封装的OC类
pthread_mutex
是一个跨平台的锁,使用比较麻烦,苹果使用 OC 封装了一些便于使用的锁类。
所有封装的 OC 锁类都实现了 NSLocking
协议,实现了加锁
和解锁
功能。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
基本使用:
@interface ViewController ()
@property (nonatomic, assign) int ticketsCount;
@property (nonatomic, strong) NSLock *lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self saleTickets];
}
- (void)saleTicket
{
[self.lock lock];
int oldTicketsCount = self.ticketsCount;
sleep(0.2f);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d", self.ticketsCount);
[self.lock unlock];
}
- (void)saleTickets
{
self.lock = [[NSLock alloc] init];
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
@end
NSLock(pthread_mutex普通锁)
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSRecursiveLock(pthread_mutex递归锁)
@interface NSRecursiveLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSCondition(pthread_mutex条件)
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.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(pthread_mutex条件)
NSConditionLock
比 NSCondition
多了设置具体的条件值的功能。
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
dispatch_semaphore
semaphore
叫做 信号量
。
信号量
的初始值,可以用来控制线程并发访问
的最大数量。
信号量
的初始值为1
,代表同时只允许1
条线程访问资源,保证线程同步。
@interface ViewController ()
@property (nonatomic, assign) int ticketsCount;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self saleTickets];
}
- (void)saleTicket
{
// 如果信号量的值<=0,当前线程就会进入休眠等待,直到信号量的值>0
// 如果信号量的值>0,让信号量的值-1,然后继续执行后面的代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
int oldTicketsCount = self.ticketsCount;
sleep(0.2f);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d", self.ticketsCount);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
}
- (void)saleTickets
{
// 初始化信号量,信号量的值为1
self.semaphore = dispatch_semaphore_create(1);
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
@end
@synchronized(pthread_mutex递归锁)
@synchronized
是对 pthread_mutex
递归锁
的封装,性能差 但 使用简单
。
@synchronized(obj)
内部会生成 obj
对应的 递归锁
,然后进行加锁、解锁操作。
基本用法:
@synchronized (锁对象) {
// 任务代码
}
示例:
@interface ViewController ()
@property (nonatomic, assign) int ticketsCount;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self saleTickets];
}
- (void)saleTicket
{
@synchronized (self) {
int oldTicketsCount = self.ticketsCount;
sleep(0.2f);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩 %d", self.ticketsCount);
}
}
- (void)saleTickets
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
@end
在 objc4
源码中的 objc-sync.mm
文件,可以看到 @synchronized
的实现源码:
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
// End synchronizing on 'obj'.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
自旋锁、互斥锁比较
什么情况使用自旋锁
比较划算?
- 预计线程等待锁的时间很短
- 多核处理器
- CPU资源不紧张
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
什么情况使用互斥锁
比较划算?
- 预计线程等待锁的时间较长
- 单核处理器
- 加锁的代码(临界区)有IO操作
- 加锁的代码(临界区)复杂或者循环量大
- 加锁的代码(临界区)竞争非常激烈
atomic
atomic
用于保证属性 setter
、getter
的 原子性
操作,相当于在 getter
、setter
内部 赋值和取值
加了线程同步的锁。
atomic
并不能保证使用属性的过程是线程安全的,因为它只是对赋值和取值进行加锁和解锁。
可以参考源码 objc4
的 objc-accessors.mm
:
// getter
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// 加锁解锁
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
// setter
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
// 加锁解锁
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
示例:
@interface ViewController ()
@property (atomic, strong) NSMutableArray *datas;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 线程安全
self.datas = [NSMutableArray array];
// 线程不安全 self.datas有线程同步,addObject方法没有线程同步
[self.datas addObject:@"1"];
}
@end
iOS中的读写安全方案
- 同一时间,只能有1个线程进行写的操作
- 同一时间,允许有多个线程进行读的操作
- 同一时间,不允许既有写的操作,又有读的操作
上面的场景就是典型的 多读单写
,经常用于文件等数据的读写操作,iOS 中的实现方案有:
pthread_rwlock
:读写锁dispatch_barrier_async
:异步栅栏调用
pthread_rwlock
#import <pthread/pthread.h>
@interface ViewController ()
@property (nonatomic, assign) pthread_rwlock_t lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
pthread_rwlock_init(&_lock, NULL);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self read];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self read];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self write];
}
});
}
- (void)dealloc
{
pthread_rwlock_destroy(&_lock);
}
- (void)read
{
pthread_rwlock_rdlock(&_lock);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
- (void)write
{
pthread_rwlock_wrlock(&_lock);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
@end
dispatch_barrier_async
这个函数传入的并发队列必须是自己通过 dispatch_queue_cretate
创建的。
如果传入的是一个串行
或是一个全局的并发队列
,那这个函数便等同于 dispatch_async
函数的效果。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self read];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self read];
}
});
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self write];
}
});
}
- (void)read
{
NSLog(@"%s", __func__);
}
- (void)write
{
NSLog(@"%s", __func__);
}
@end
大佬牛逼