iOS多线程-线程安全

/ 0

多线程安全隐患

多个线程同时访问同一个资源(对象/变量/文件等),就可能出现数据错乱和数据安全问题。

比如下面代码,多个线程同时修改一个变量,最终导致数据错乱:

@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中的线程同步方案

性能 从高到低 排序:

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条件)

NSConditionLockNSCondition 多了设置具体的条件值的功能。

@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;
}

自旋锁、互斥锁比较

什么情况使用自旋锁比较划算?

什么情况使用互斥锁比较划算?

atomic

atomic 用于保证属性 settergetter原子性操作,相当于在 gettersetter 内部 赋值和取值加了线程同步的锁。

atomic 并不能保证使用属性的过程是线程安全的,因为它只是对赋值和取值进行加锁和解锁。

可以参考源码 objc4objc-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中的读写安全方案

上面的场景就是典型的 多读单写,经常用于文件等数据的读写操作,iOS 中的实现方案有:

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