iOS线程同步方案总结

demo: ThreadSynchronization

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

一、OSSpinLock

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

// 初始化
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_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_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  {// 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协议
@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  {
@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  {
@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 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线程同步方案总结)