美文网首页Ios@IONIC程序员iOS
iOS线程同步方案总结

iOS线程同步方案总结

作者: YY_Lee | 来源:发表于2019-03-20 15:58 被阅读17次

    demo: ThreadSynchronization

    多线程技术使得执行任务的效率得到提升,但多线程也是一个易发生各种问题的编程技术。如数据竞争(多个线程更新相同资源导致数据不一致)、死锁(多个线程相互等待)、太多线程会销毁大量内存等问题。数据竞争需要通过线程同步方案来解决,一般使用锁解决对某项资源的互斥使用,下面列举些iOS中的线程同步技术。

    一、OSSpinLock

    使用OSSpinLock,等待锁的线程会处于忙等状态,一直占用CPU资源;
    现在已经不再安全,苹果已经弃用,可能会出现优先级翻转问题;(如果等待锁的线程优先级高,它会一直占用CPU资源,导致优先级低的线程无法释放锁)
    使用时需导入头文件 #import <libkern/OSAtomic.h>

    // 初始化
    OSSpinLock lock = OS_SPINLOCK_INIT;
    // 加锁
    OSSpinLockLock(&_lock);
    // 尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回true)
    bool result = OSSpinLockTry(&_lock);
    //解锁
    OSSpinLockUnlock(&_lock);
    

    二、os_unfair_lock

    os_unfair_lock用于取代不安全的OSSpinLock,iOS10之后开始支持;
    等待os_unfair_lock的线程处于休眠状态而非忙等,使用时需导入头文件 #import <os/lock.h>

    // 初始化
    os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
    // 加锁
    os_unfair_lock_lock(&_lock);
    // 尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回true)
    bool result = os_unfair_lock_trylock(&_lock);
    //解锁
    os_unfair_lock_unlock(&_lock);
    

    三、pthread_mutex_t

    mutex叫做“互斥锁”,等待锁的线程会处于休眠状态;
    使用时需导入头文件 #import <pthread.h>

    初始化属性pthread_mutexattr_t时,Mutex type设为PTHREAD_MUTEX_NORMAL和PTHREAD_MUTEX_DEFAULT时代表是互斥锁,设置为PTHREAD_MUTEX_RECURSIVE代表递归锁(加锁代码递归调用时使用,pthread_mutexattr_t的递归锁只有当同一线程中才能重复加锁,不同线程仍需等待解锁后才能加锁);初始化锁方法pthread_mutex_init中attr字段传入NULL代表默认属性PTHREAD_MUTEX_DEFAULT;

    /*  Mutex type attributes
    *
    *  #define PTHREAD_MUTEX_NORMAL        0
    *  #define PTHREAD_MUTEX_ERRORCHECK    1
    *  #define PTHREAD_MUTEX_RECURSIVE     2
    *  #define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL
    */
    
       // 初始化属性
       pthread_mutexattr_t attr;
       pthread_mutexattr_init(&attr);
       pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
       // 初始化锁
       pthread_mutex_init(&mutex, &attr);
       // 销毁属性
       pthread_mutexattr_destroy(&attr);
    
       // 或
       // attr传入NULL代表 PTHREAD_MUTEX_DEFAULT
       //pthread_mutex_init(&mutex, NULL);
       
       // 尝试加锁
       // pthread_mutex_trylock(&mutex);
       // 加锁
       pthread_mutex_lock(&_mutex);
       // 解锁
       pthread_mutex_unlock(&_mutex);
       // 销毁锁
       pthread_mutex_destroy(&(_mutex));
    

    四、pthread_cond_t

    pthread_cond_t配合pthread_mutex_t使用

    // 初始化条件
    pthread_cond_init(&_cond, NULL);
    // 等待条件cond,线程进入休眠状态并解锁mutex,待线程唤醒后会重新加锁线程往下继续执行
    pthread_cond_wait(&_cond, &_mutex);
    // 激活一个等待条件cond的线程,
    pthread_cond_signal
    // 激活所有等待条件cond的线程
    pthread_cond_broadcast
    

    下面举个例子,如下代码假设线程1执行removeItem,但初始时self.dataArray为空,于是等待条件并解锁,线程2执行addItem调用后,self.dataArray不再为空且激活等待cond的线程1,线程1加锁执行removeItem。

    - (void)operateDataArray {
        [[[NSThread alloc]initWithTarget:self selector:@selector(removeItem) object:nil] start];
        sleep(.2);
        [[[NSThread alloc]initWithTarget:self selector:@selector(addItem) object:nil] start];
    }
    
    - (void)addItem {
        pthread_mutex_lock(&_mutex);
        [self.dataArray addObject:@"item"];
        NSLog(@"添加后:%@",self.dataArray);
        pthread_cond_signal(&_cond);
    //    pthread_cond_broadcast(&_cond);
        pthread_mutex_unlock(&_mutex);
    }
    
    - (void)removeItem {
        pthread_mutex_lock(&_mutex);
        if (self.dataArray.count == 0) {
            NSLog(@"等待");
            pthread_cond_wait(&_cond, &_mutex);
        }
        [self.dataArray removeObject:@"item"];
        NSLog(@"移除后:%@",self.dataArray);
        pthread_mutex_unlock(&_mutex);
    }
    

    四、NSLock

    NSLock是对普通pthread_mutex_t的封装,使用非常简单;

    @protocol NSLocking
    - (void)lock;// 加锁
    - (void)unlock;// 解锁
    @end
    
    @interface NSLock : NSObject <NSLocking> {// NSLock 遵循NSLocking协议
    @private
        void *_priv;
    }
    
    - (BOOL)tryLock;
    - (BOOL)lockBeforeDate:(NSDate *)limit;//在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。
    
    @property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    
    @end
    
    // 初始化
    NSLock  *lock = [[NSLock alloc]init];
    

    五、NSRecursiveLock

    NSRecursiveLock是对pthread_mutex_t递归锁的封装,API跟NSLock类似。

    @interface NSRecursiveLock : NSObject <NSLocking> {// 同样遵循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
    // 初始化
    self.recursiveLock = [[NSRecursiveLock alloc]init];
    

    六、NSCondition

    NSCondition是对mutex和cond的封装

    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

    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_t

    dispatch_semaphore_t通过控制信号量也可以实现线程同步。对dispatch_semaphore_t的说明可查看多线程总结.

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        self.semaphore  = dispatch_semaphore_create(1);
        
        __weak typeof(DispatchSemaphoreController) *weakSelf = self;
        dispatch_queue_t queue =  dispatch_queue_create("XL.LockPractice.ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            for (int i = 0 ; i < 10; i++) {
                [weakSelf saveMoney];
            }
        });
        
        dispatch_async(queue, ^{
            for (int i = 0 ; i < 10; i++) {
                [weakSelf withDrayMoney];
            }
        });
        
    }
    
    - (void)saveMoney {
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        [super saveMoney];
        dispatch_semaphore_signal(self.semaphore);
    }
    
    - (void)withDrayMoney {
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        [super withDrayMoney];
        dispatch_semaphore_signal(self.semaphore);
    }
    

    九、GCD串行队列

    GCD串行队列,保证事件按顺序一个个执行,GCD队列使用参考多线程总结

    十、 @synchronized

    @synchronized是对mutex递归锁的封装,可以查看Runtime源码objc-sync.mm文件。@synchronized(obj)内部会根据objc生成对应的递归锁,并进行加锁解锁操作。注意需要保证obj的唯一性,不同obj生成的锁是不同的。

    @synchronized (obj) {// objc_sync_enter
      // task
    } // objc_sync_exit
    

    下面是源码中objc_sync_enter和objc_sync_exit的实现。从源码和注释中可以看出,objc_sync_enter是根据obj生成一个对象的mutex递归锁;

    typedef struct alignas(CacheLineSize) SyncData {
        struct SyncData* nextData;
        DisguisedPtr<objc_object> object;
        int32_t threadCount;  // number of THREADS using this block
        recursive_mutex_t mutex;
    } SyncData;
    
    // 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) {//obj存在,根据obj生成一个锁,并调用加锁方法lock(), SyncData
            SyncData* data = id2data(obj, ACQUIRE);
            assert(data);
            data->mutex.lock();
        } else {// 如果obj为空,doing nothing
            // @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) {// 根据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;
    }
    

    相关文章

      网友评论

        本文标题:iOS线程同步方案总结

        本文链接:https://www.haomeiwen.com/subject/gwaamqtx.html