iOS中的八大锁解析与使用

线程安全:多线程操作共享数据,不会造成意想不到的问题
锁的意义:为了线程安全

锁的分类
  • 自旋锁 busy-waiting(忙等状态):线程获取锁,发现锁被占用,不断进行锁请求,直到获取。等待锁的线程处于忙等状态线程并会占用cpu资源;
  • 互斥锁 sleep-waiting(休眠状态):线程获取锁,发现锁被占用,就向系统申请锁空闲时唤醒他并立刻休眠。等待锁的线程处于休眠状态,并不会占用cpu资源;互斥锁分为递归(可递归调用)、非递归;
  • 特殊锁读写锁;
原子属性Atomic

原子属性只对setter 或getter线程安全,在获取、赋值时添加了spinlock_t锁(os_unfair_lock)
setter底层实现

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

getter底层实现

    // 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();
八大锁底层原理
  • OSSpinLock -- 自旋锁 iOS10及以后弃用
  • os_unfair_lock -- 互斥锁 iOS10开始替代OSSpinLock
  • NSLock -- 互斥锁
  • NSCondition -- 互斥锁
  • NSConditionLock -- 互斥锁
  • dispatch_semaphore -- 互斥锁
  • pthread_mutex -- 互斥锁(可设置:递归)
  • NSRecursiveLock -- 递归锁
  • @synchronized -- 递归锁


    锁大致性能

OSSpinLock -- 自旋锁

  1. 优点:不会引起调度者的睡眠,不会进行线程的调度以及CPU时间片的轮转等耗时操作;
  2. 缺点:在很短来时间 未获得锁的情况下,会一直占用cpu资源,会自旋死循环中,不能实现递归调用。
  3. 优先级反转 -- 造成死锁在iOS10被移除
    • eg:如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。导致陷入死锁。

系统维护了 5 个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。而互斥锁不会受此影响

4.api使用

  • OS_SPINLOCK_INIT 初始化锁 。
  • OSSpinLockLock(&spinlock) 加锁,参数为OSSPINLOCK地址。
  • OSSpinLockUnlock(&spinlock) 解锁,参数是OSSpinLock地址。
  • OSSpinLockTry(&spinlock) 尝试上锁,参数是OSSpinLock地址。如果返回false,表示上锁失败,锁正在被其他线程持有。如果返回true,表示上锁成功。

os_unfair_lock -- 互斥锁

  • iOS10之后开始支持,用于取代OSSpinLock。
  • OS_UNFAIR_LOCK_INIT 初始化锁。
  • os_unfair_lock_lock 加锁。参数为os_unfair_lock地址。
  • os_unfair_lock_unlock 解锁。参数为os_unfair_lock地址。
  • os_unfair_lock_trylock 尝试加锁。参数为os_unfair_lock地址。如果成功返回true。如果锁已经被锁定则返回false。
  • os_unfair_lock_assert_owner 参数为os_unfair_lock地址。如果当前线程未持有指定的锁或者锁已经被解锁,则触发崩溃。
  • os_unfair_lock_assert_not_owner 参数为os_unfair_lock地址。如果当前线程持有指定的锁,则触发崩溃。
//代码实现
#pragma mark - 相关的api的参数获取锁的地址
-(void)un_fair_lockTest{
    self.unfarilock = OS_UNFAIR_LOCK_INIT;
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            os_unfair_lock_lock(&self->_unfarilock);
            
            self.count --;
            NSLog(@"%d", self.count);
            os_unfair_lock_unlock(&self->_unfarilock);
            
        });
    }
}

NSLock -- 互斥锁

# Swift的Foundation源码实现,NSLock基于pthread_mutex的实现

open class NSLock: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
#if os(macOS) || os(iOS) || os(Windows)
    private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
    private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
#endif

    public override init() {
#if os(Windows)
        InitializeSRWLock(mutex)
        InitializeConditionVariable(timeoutCond)
        InitializeSRWLock(timeoutMutex)
#else
        pthread_mutex_init(mutex, nil)
#if os(macOS) || os(iOS)
        pthread_cond_init(timeoutCond, nil)
        pthread_mutex_init(timeoutMutex, nil)
#endif
#endif
    }
}
  • -(void)lock 加锁。
  • -(void)unlock 解锁。
  • -(BOOL)tryLock 尝试加锁。成功返回YES,失败返回NO。
  • -(BOOL)lockBeforeDete:(NSDate *)limit 在指定时间点之前获取锁,能够获取返回YES,获取不到返回NO。
  • @property (nullable ,copy) NSString *name 锁名称。
//代码实现
-(void)nslockTest{
    self.nsLock = [[NSLock alloc] init];
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.nsLock lock];
            self.count --;
            NSLog(@"%d", self.count);
            [self.nsLock unlock];
        });
    }
}

NSCondition -- 互斥锁 生产 -- 消费
与NSLock同样遵守NSLocking的协议,有同样的lock、unlock的api。

  • -(void)lock 加锁。
  • -(void)unlock 解锁。
  • -(void)wait 阻塞当前线程,使线程进入休眠,等待唤醒信号。调用前必须已加锁。
  • -(void)waitUntilDate 阻塞当前线程,使线程进入休眠,等待唤醒信号或者超时。调用前必须已加锁。
  • -(void)signal 唤醒一个正在休眠的线程,如果要唤醒多个,需要调用多次。如果没有线程在等待,则什么也不做。调用前必须已加锁。存在虚假唤醒,即与broadcast的效果
  • -(void)broadcast 唤醒所有在等待的线程。如果没有线程在等待,则什么也不做。调用前必须已加锁。
  • @property (nullable ,copy) NSString *name 锁名称。
//代码实现
#pragma mark - 经典事例 -- 生产消费  signal(可能出现虚假唤醒)
self.nsCondition = [[NSCondition alloc] init];

-(void)nsconditionTest{
    self.count = 50;
    for (int i = 0; i < 50; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self production];
        });
    }
    for (int i = 0; i < 100; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self consumption];
        });
    }
}

///生产
-(void)production{
    
    [self.nsCondition lock];
    self.count ++;
    NSLog(@"生产:%d", self.count);
    if (self.count == 1) {
        //实际上会出现虚假等待,出现broadcast的效果
        [self.nsCondition signal];
    }
    [self.nsCondition unlock];
}

///消费
-(void)consumption{
    
    [self.nsCondition lock];
    while (self.count == 0) {
        [self.nsCondition wait];
    }
    self.count --;
    NSLog(@"消费:%d", self.count);
    [self.nsCondition unlock];
}


NSConditionLock -- 互斥锁是基于NSCondition进一步封装,自带条件。设置线程执行顺序

  • -(void)lock 加锁。
  • -(void)unlock 解锁。
  • -(instancetype)initWithCondition:(NSinteger)初始化一个NSConditionLock对象。带条件(标识)
  • @property(readonly) NSInteger condition 锁的条件。
  • -(void)lockWhenCondition:(NSInteger)conditio满足条件时加锁。
  • -(BOOL)tryLock尝试加锁。
  • -(BOOL)tryLockWhenCondition如果接受对象的condition与给定的condition相等,则尝试获取锁,不足塞线程。
  • -(void)unlockWithCondition:(NSInteger)condition解锁,重置锁的条件。
  • -(BOOL)lockBeforDate:(NSDate *)limit在指定时间点之前获取锁。
  • -(BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit在指定的时间前获取锁。
  • @property (nullable ,copy) NSString *name 锁名称。
//代码实现 线程执行顺序
- (void)nsconditionLockTest {
    //condition: 3 一个标识
    self.conditionLock = [[NSConditionLock alloc] initWithCondition:3];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.conditionLock lockWhenCondition:3];
        NSLog(@"%@ 1", [NSThread currentThread]);
        [self.conditionLock unlockWithCondition:5];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.conditionLock lockWhenCondition:5];
        NSLog(@"%@ 2", [NSThread currentThread]);
        [self.conditionLock unlockWithCondition:2];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.conditionLock lockWhenCondition:2];
        NSLog(@"%@ 3", [NSThread currentThread]);
        [self.conditionLock unlockWithCondition:0];
    });
    
}

pthread_mutex -- 互斥锁
c语言实现

//代码实现
#pragma mark -- pthread_mutex
- (void)lg_pthread_mutex {
    
    //非递归
    pthread_mutex_t lock0;
    pthread_mutex_init(&lock0, NULL);
    pthread_mutex_lock(&lock0);
    pthread_mutex_unlock(&lock0);
    pthread_mutex_destroy(&lock0);
    
    //递归
    pthread_mutex_t lock;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&lock, &attr);
    pthread_mutexattr_destroy(&attr);
    pthread_mutex_lock(&lock);
    pthread_mutex_unlock(&lock);
    pthread_mutex_destroy(&lock);
}

dispatch_semaphore 控制线程并发数

  • dispatch_semaphore_create(intptr_t value)创建信号量,并且创建的时候需要指定信号量的大小

  • dispatch_semaphore_wait(dispatch_semaphore_t dsema, diapatch_time_t timeout) 等待信号量,如果信号量值为0,那么该函数就会一直等待(相当于阻塞当前线程),直到该函数等待的信号量的值大于等于1,该函数会对信号量的值进行减1操作,然后返回。dispatch_semaphore_signal(dispatch_semaphore_t dsema) 发送信号量,该函数会对信号量的值进行加1操作

  • dispatch_semaphore_signal 底层实现

自加1

dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
  long value = os_atomic_inc2o(dsema, dsema_value, release);//自加1
  if (likely(value > 0)) {
      return 0;
  }
  if (unlikely(value == LONG_MIN)) {
      DISPATCH_CLIENT_CRASH(value,
              "Unbalanced call to dispatch_semaphore_signal()");
  }
  return _dispatch_semaphore_signal_slow(dsema);
}
  • dispatch_semaphore_wait 底层实现

自减1
执行顺序
dispatch_semaphore_wait ~> _dispatch_semaphore_wait_slow ~>_dispatch_sema4_wait(do-while阻塞线程,很像自旋锁)

dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
  long value = os_atomic_dec2o(dsema, dsema_value, acquire); //自减1,
  if (likely(value >= 0)) {
      return 0;
  }
  return _dispatch_semaphore_wait_slow(dsema, timeout);
 
//_dispatch_semaphore_wait_slow 部分实现
//case DISPATCH_TIME_FOREVER:_dispatch_sema4_wait(&dsema-dsema_sema);      
  }
}

_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
  kern_return_t kr;
  do {
      kr = semaphore_wait(*sema);
  } while (kr == KERN_ABORTED);
  DISPATCH_SEMAPHORE_VERIFY_KR(kr);//有两个方法一个ret 另一个kr
}
  • _dispatch_semaphore_dispose 注意 wait方法数量不能多于signal方法,一般开发使用中wait 和 signal成对存在
_dispatch_semaphore_dispose(dispatch_object_t dou,
        DISPATCH_UNUSED bool *allow_free)
{
    dispatch_semaphore_t dsema = dou._dsema;
    "//[注意]: create 不能小于0,且释放时当前发value不能小于create的值
    //(即:wait方法数量不能多于signal方法)"
    if (dsema->dsema_value < dsema->dsema_orig) {
        DISPATCH_CLIENT_CRASH(dsema->dsema_orig - dsema->dsema_value,
                "Semaphore object deallocated while in use");
    }

    _dispatch_sema4_dispose(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
}
///代码实现
- (void)semaphoreTest {
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSLog(@"%@ 1", [NSThread currentThread]);
        dispatch_semaphore_signal(sem);//+1
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSLog(@"%@ 2", [NSThread currentThread]);
        dispatch_semaphore_signal(sem);//+1
    });
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSLog(@"%@ 3", [NSThread currentThread]);
        dispatch_semaphore_signal(sem);//+1
    });
    
    //换行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"\n");
    });
}

NSRecursiveLock -- 递归锁 保证同一线程下重复加锁;在多线程环境下,递归调用会造成死锁,多线程在加锁和解锁中,会出现互相等待解锁的情况。与NSLock一样都是基于pthread_mutex_init实现,只是设置type为递归类型。

/*
 * Mutex type attributes
 */
public var PTHREAD_MUTEX_NORMAL: Int32 { get }
public var PTHREAD_MUTEX_ERRORCHECK: Int32 { get }
public var PTHREAD_MUTEX_RECURSIVE: Int32 { get }
public var PTHREAD_MUTEX_DEFAULT: Int32 { get }
    public override init() {
        super.init()
#if os(Windows)
        InitializeCriticalSection(mutex)
        InitializeConditionVariable(timeoutCond)
        InitializeSRWLock(timeoutMutex)
#else
#if CYGWIN
        var attrib : pthread_mutexattr_t? = nil
#else
        var attrib = pthread_mutexattr_t()
#endif
        withUnsafeMutablePointer(to: &attrib) { attrs in
            pthread_mutexattr_init(attrs)
            pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))//递归类型
            pthread_mutex_init(mutex, attrs)
        }
#if os(macOS) || os(iOS)
        pthread_cond_init(timeoutCond, nil)
        pthread_mutex_init(timeoutMutex, nil)
#endif
#endif
    }
//代码实现
-(void)test{
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self recursiveLockTest];
//            [self recursiveBlockTest]
        });
    }
}
//
-(void)recursiveLockTest {
    
    [self.recLock lock];
    [self.recLock lock];
    self.count--;
    NSLog(@"%@ %d", [NSThread currentThread], self.count);
    [self.recLock unlock];
    [self.recLock unlock];
}
///
- (void)recursiveBlockTest {
    
    static void(^recursiveBlock)(int);
    recursiveBlock = ^(int value){
        if (value > 0) {
//            [self.recursive lock];
//            value --;
//            NSLog(@"%@ %d", [NSThread currentThread], value);
//            recursiveBlock(value);
//            [self.recursive unlock];
            @synchronized (self) {

                value --;
                NSLog(@"%@ %d", [NSThread currentThread], value);
                recursiveBlock(value);
            }
        }
    };
    recursiveBlock(5);
}

@synchronized -- 递归锁
加锁时,在缓存获取,不会重复创建。可以在多线程下递归调用。如性能方面要求不是非常高的话,使用该锁还更简便。

读写锁 -- 栅栏函数

读方法中的同步sync是为了栅栏函数读写互斥。读写互斥,多读单写

#pragma mark -- [注意]: lockQueue保证栅栏函数读写互斥
self.iQueue = dispatch_queue_create(@"myqueue",DISPATCH_QUEUE_CONCURRENT);

- (void)myWrite: (NSString *)name {
    // 写操作
    dispatch_barrier_async(self.lockQueue, ^{
        [self.dataDic setObject:name forKey:@"myTest"];
    });
}
- (NSString *)myRead {

    __block NSString *ret;
   
    dispatch_sync(self.lockQueue, ^{
        // 读取的代码
        ret = self.dataDic[@"myTest"];
    });
    NSLog(@"%@",ret);
    return ret;
}

[注意] 死锁:当前线程在串行队列中,gcd同步(dispatch_sync)同一串行队列,导致线程死锁。

  • GCD的源码 自行下载
  • Swift的Fundation源码 自行下载

你可能感兴趣的:(iOS中的八大锁解析与使用)