(搜题笔记)如何实现一个线程安全的 NSMutableArray?

2,258 阅读4分钟

NSMutableArray是线程不安全的,当有多个线程同时对数组进行操作的时候可能导致崩溃或数据错误

1.线程锁:使用线程锁对数组读写时进行加锁

1.1 锁是什么以及为什么需要?
    锁是一种保证多线程并发执行安全的方式,避免同一时间,多个线程同时读取并修改一个值而产 生混乱。
1.2  iOS中都有哪些锁?
    1.2.1 OSSpinLock (自旋锁)
        测试中效率最高的锁, 不过经YYKit作者确认, OSSpinLock已经不再线程安全,OSSpinLock有潜在的优先级反转问题.不再安全的 OSSpinLock;
        使用方法:
            1)://需要导入头文件   #import <libkern/OSAtomic.h>
            2):// 初始化   OSSpinLock spinLock = OS_SPINLOCK_INIT;
            3):// 加锁      OSSpinLockLock(&spinLock);
            4):// 解锁    OSSpinLockUnlock(&spinLock);
            5):// 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO  OSSpinLockTry(&spinLock)
            优缺点:苹果已经在iOS10.0以后废弃了这种锁机制,使用os_unfair_lock 替换,顾名思义能够保证不同优先级的线程申请锁的时候不会发生优先级反转问题.
            
    1.2.2 os_unfair_lock(自旋锁)
        这是苹果iOS10之后推出的新的取代OSSpinLock的锁。虽然是替代OSSpinLock的,但os_unfair_lock并不是自旋锁,根据苹果的官方文档可以看到其实它是一个互斥锁。
        使用方法:
            1)://需要导入头文件    #import <os/lock.h>
            2)://初始化    os_unfair_lock theLoc = OS_UNFAIR_LOCK_INIT; 
            3)://加锁      os_unfair_lock_lock(&theLoc);
            4)://解锁      os_unfair_lock_unlock(&theLock);
            优缺点:解决不同优先级的线程申请锁的时候不会发生优先级反转问题,不过相对于 OSSpinLock , os_unfair_lock性能方面减弱了许多.
            
    1.2.3 dispatch_semaphore (信号量)
        dispatch_semaphore 是信号量,但当信号总量设为1时也可以当作锁来。
        使用方法:
            1):// 初始化  dispatch_semaphore_t semaphore_t = dispatch_semaphore_create(1);
            2):// 加锁
            dispatch_semaphore_wait(semaphore_t,DISPATCH_TIME_FOREVER);
            3):// 解锁 dispatch_semaphore_signal(semaphore_t);
            
    1.2.4 pthread_mutex(互斥锁))
        pthread_mutex是c底层的线程锁
        使用方法:
            1)://导入头文件   #import <pthread/pthread.h>
            2):// 两种初始化方法
                * 普通初始化  
                    pthread_mutex_t mutex_t;
                    pthread_mutex_init(&mutex_t, NULL); 
                * 宏初始化
                    pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
            3):// 加锁  pthread_mutex_lock(&mutex_t);
            4):// 解锁  pthread_mutex_unlock(&mutex_t);
            5):// 尝试加锁,可以加锁时返回的是 0,否则返回一个错误  pthread_mutex_trylock(& mutex_t)
            
    1.2.5 NSLock(互斥锁、对象锁)
        基于pthread_mutex封装的面向对象的锁,遵守NSCopying协议,此协议中提供了加锁和解锁方法
        使用方法:
            1):// 初始化    NSLock *lock = [[NSLock alloc]init];
            2):// 加锁      [lock lock];
            3):// 解锁      [lock unlock];
            4)://尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO [lock tryLock];
            
    1.2.6 NSCondition(条件锁、对象锁)
         基于pthread_mutex封装的面向对象的锁,遵守NSCopying协议,此协议中提供了加锁和解锁方法
         使用方法:
            1):// 初始化 NSCondition *condition= [[NSCondition alloc]init];
            2)://加锁   [condition lock];
            3)://解锁   [conditaion unlock];
            4):// 其他功能接口
                4.1):// 线程等待    - (void)wait;
                4.2)://设置线程等待时间,过了这个时间就会自动执行后面的代码  - (BOOL)waitUntilDate:(NSDate *)limit;	
                4.3):// 唤醒一个设置为wait等待的线程  - (void)signal;	
                4.4):// 唤醒所有设置为wait等待的线程  - (void)broadcast;	
    1.2.7 NSConditionLock(条件锁、对象锁)
        基于pthread_mutex封装的面向对象的锁,遵守NSCopying协议,此协议中提供了加锁和解锁方法
         使用方法:
            1):// 初始化    NSConditionLock *conditionLock = [[NSConditionLock alloc]init];
            2)://加锁       [conditionLock lock];
            3)://解锁       [conditionLock unlock];
            4)://尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO [conditionLock tryLock];
            5)://其他方法接口
                5.1): //初始化传入条件   -(instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
                5.2)://条件成立触发锁   - (void)lockWhenCondition:(NSInteger)condition;
                5.3)://尝试条件成立触发锁   - (BOOL)tryLockWhenCondition:(NSInteger)condition;
                5.4)://条件成立解锁        - (void)unlockWithCondition:(NSInteger)condition;
                5.5)://触发锁 在等待时间之内    - (BOOL)lockBeforeDate:(NSDate *)limit;
                5.6)://触发锁 条件成立 并且在等待时间之内   - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
                
    1.2.8 NSRecursiveLock(递归锁、对象锁)
        NSRecursiveLock实际上定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中.
        使用方法:
            1)://初始化 NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc]init];
            2):// 加锁  [recursiveLock lock];
            3)://解锁   [recursiveLock unlock];
            4)://尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO [recursiveLock tryLock];
            5)://触发锁 在等待时间之内  - (BOOL)lockBeforeDate:(NSDate *)limit;
            
    1.2.9 @synchronized(条件锁)
        synchronized关键字,我们一般称之为”同步锁“,用它来修饰需要同步的方法和需要同步代码块,默认是当前对象作为锁的对象。在修饰类时(或者修饰静态方法),默认是当前类的Class对象作为所的对象故存在着方法锁、对象锁、类锁 这样的概念
        使用方法:
            1):修饰在方法上,多个线程调用同一个对象的同步方法会阻塞,调用不同对象的同步方法不会阻塞。
            2):Synchronized修饰静态的方法 
            3):@synchronized(条件){}

2.派发队列:多用派发队列,少用同步锁中指出:使用“串行同步队列”(serial synchronization queue),将读取操作及写入操作都安排在同一个队列里,即可保证数据同步。派发队列可用来表述同步语义, 这种做法要比使用 @synchronized 块 或者 NSLock 对象变得更简单.

而通过并发队列,结合GCD的栅栏块(barrier)来不仅实现数据同步线程安全,还比串行同步队列方式更高效。