网络知识 娱乐 iOS常用的几种锁详解以及用法

iOS常用的几种锁详解以及用法

锁的种类


互斥锁 自旋锁

  • 互斥锁:保证在任何时候,都只有一个线程访问对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒;
  • 自旋锁:与互斥锁有点类似,只是自旋锁 不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环尝试,直到该自旋锁的保持者已经释放了锁;因为不会引起调用者睡眠,所以效率高于互斥锁;
  • 自旋锁缺点:
  1. 调用者在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时间内获得锁,会使CPU效率降低。所以自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下
  2. 在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁

两种锁的加锁原理

  • 互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。
  • 自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

递归锁


特殊的互斥锁,加了递归功能

ios中常见的几种锁


ios中常见的几种锁包括OSSpinLock、信号量(Semaphore)、pthread_mutex、NSLock、NSCondition、NSConditionLock、pthread_mutex(recursive)、NSRecursiveLock、synchronized

如下所示,测试锁性能的案例图(实际可能会略有偏差):

 

我们再选锁的时候,如果只是使用互斥锁的效果,那么按照性能排序选择靠前的即可,如果需要锁的一些其他功能,那么根据需要选择,不必过于局限于性能,毕竟实现功能与项目的维护也是非常重要的

1.OSSpinLock/os_unfair_lock

由于OSSpinLock目前已经不再安全,存在bug,官方已放弃,iOS10之后os_unfair_lock取代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地址。如果当前线程持有指定的锁,则触发崩溃。

基本用法

//创建锁,    
self.unfairLock = OS_UNFAIR_LOCK_INIT;

#pragma mark -- os_unfair_lock
-(void)unfairLock_test {
    os_unfair_lock_lock(&_unfairLock);
    self.count --;
    NSLog(@"%d",self.count);
    os_unfair_lock_unlock(&_unfairLock);
}

/*! @abstract Data type for a spinlock.
    @discussion
	You should always initialize a spinlock to {@link OS_SPINLOCK_INIT} before
	using it.
 */
typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock);

2.dispatch_semaphore

dispatch_semaphore 是 GCD 用来同步的一种方式,与他相关的只有三个函数,一个是创建信号量,一个是等待信号,一个是发送信号。

dispatch_semaphore_create(long value);
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

dispatch_semaphore 的核心是 dispatch_semaphore_t 类型的信号量。 dispatch_semaphore_create(1) 方法可以创建一个 dispatch_semaphore_t 类型的信号量,设定信号量的初始值为 1。注意,这里的传入的参数必须大于或等于 0,否则 dispatch_semaphore_create 会返回 NULL。 dispatch_semaphore_wait(signal, overTime); 方法会判断 signal 的信号值是否大于 0。大于 0 不会阻塞线程,消耗掉一个信号,执行后续任务。如果信号值为 0,该线程会和 NSCondition 一样直接进入 waiting 状态,等待其他线程发送信号唤醒线程去执行后续任务,或者当 overTime 时限到了,也会执行后续任务。 dispatch_semaphore_signal(signal); 发送信号,如果没有等待的线程接受信号,则使 signal 信号值加一(做到对信号的保存)

 //如果信号量值<0,那么该函数就会一直等待(相当于阻塞当前线程)
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务1");
        dispatch_semaphore_signal(sem);//+1不在阻塞线程继续执行
    });
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//阻塞不在向下执行代码,阻塞到当前

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务2");
        dispatch_semaphore_signal(sem);
    });
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);// 阻塞当前一下代码,等待上面
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务3");
    });
    NSLog(@"任务4");

3.pthread_mutex

pthread互斥锁是 pthread 库中的一员,linux系统中中常用的库,使用时需要手动import导入 #import ,其中有 pthread_mutex_trylock为尝试加锁,如果没被加锁,则会加锁成功,并返回0,适用于一些优先级比较低,间歇性调用的功能

注意:其他部分锁也有trylock这个功能,例如 NSLock、NSRecursiveLock、NSConditionLock

pthread_mutex_init(pthread_mutex_t mutex,const pthread_mutexattr_t attr)初始化锁,pthread_mutexattr_t可用来设置锁的类型。
pthread_mutex_lock(pthread_mutex_t mutex);//加锁
pthread_mutex_trylock(*pthread_mutex_t *mutex);//加锁,但是上面方法不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待,成功返回0.失败返回错误信息
pthread_mutex_unlock(pthread_mutex_t *mutex);//释放锁
pthread_mutex_destroy(pthread_mutex_t* mutex);//使用完锁之后释放锁
pthread_mutexattr_setpshared();//设置互斥锁的范围
pthread_mutexattr_getpshared() //获取互斥锁的范围

 //非递归
    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);

4.NSLock

NSLock 遵循 NSLocking协议,是常见的互斥锁之一,为 OC 框架中的 API,使用方便,据说是 pthread 封装的锁

- (void)lock 加锁。
- (void)unlock 解锁。
- (BOOL)tryLock 尝试加锁。成功返回YES,失败返回NO。
- (BOOL)lockBeforeDete:(NSDate *)limit 在指定时间点之前获取锁,能够获取返回YES,获取不到返回NO。
@property (nullable ,copy) NSString *name 锁名称。

 [self.iLock lock];
    self.count --;
    NSLog(@"%d",self.count);
    [self.iLock unlock];

5.NSCondition

NSCondition 算是一个稍微重量级的锁了,我理解为情景锁(另一个原因区分条件锁 NSConditionLock),适用于一些特殊场景,其也遵循 NSLocking协议,也属于互斥锁

- (void)lock 加锁。
- (void)unlock 解锁。
- (void)wait 阻塞当前线程,使线程进入休眠,等待唤醒信号。调用前必须已加锁。
- (void)waitUntilDate 阻塞当前线程,使线程进入休眠,等待唤醒信号或者超时。调用前必须已加锁。
- (void)signal 唤醒一个正在休眠的线程,如果要唤醒多个,需要调用多次。如果没有线程在等待,则什么也不做。调用前必须已加锁。
- (void)broadcast 唤醒所有在等待的线程。如果没有线程在等待,则什么也不做。调用前必须已加锁。
@property (nullable ,copy) NSString *name 锁名称。

#pragma mark -- NSCondition
- (void)nscondition_test {
    for (int i = 0; i < 50; i ++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self lg_production];
        });
    }
    for (int i = 0; i < 100; i ++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self lg_consumption];
        });
    }
}

- (void)lg_production {
    [self.iCondition lock];
    self.count ++;
    NSLog(@"生产了一个产品,现有产品 : %d个",self.count);
    [self.iCondition signal];
    [self.iCondition unlock];
}
- (void)lg_consumption {
    [self.iCondition lock];
    while (self.count == 0) {
        [self.iCondition wait];
    }
    self.count --;
    NSLog(@"消费了一个产品,现有产品: %d个",self.count);
    [self.iCondition unlock];
}

 

6.NSConditionLock

NSConditionLock 被称为条件锁,其遵循 NSLocking 协议,即具备正常的互斥锁功能

此外加入了 条件语句,为其核心功能,即满足指定条件才会解锁,因此算是一个重量级的锁了,其同时可以理解为 NSCondition 进化版 ,如果你理解了 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 锁名称。 


    self.iConditionLock = [[NSConditionLock alloc] initWithCondition:3];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.iConditionLock lockWhenCondition:3];
        NSLog(@"线程 1");
        [self.iConditionLock unlockWithCondition:2];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.iConditionLock lockWhenCondition:2];
        NSLog(@"线程 2");
        [self.iConditionLock unlockWithCondition:1];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.iConditionLock lockWhenCondition:1];
        NSLog(@"线程 3");
        [self.iConditionLock unlockWithCondition:0];
    });

执行结果:

 2022-06-02 11:06:16.814961+0800 DemoTest2022[32077:1507071] 线程 1
2022-06-02 11:06:16.815228+0800 DemoTest2022[32077:1507072] 线程 2
2022-06-02 11:06:16.815422+0800 DemoTest2022[32077:1507066] 线程 3

#pragma mark --条件锁NSConditionLock,实现了NSLocking协议,支持默认的互斥锁lock、unlock
- (void)NSConditionLock {
    _conditionLock = [[NSConditionLock alloc] initWithCondition:1]; //可以更改值测试为0测试结果
    //加锁,当条件condition为传入的condition时,方能解锁
    //lockWhenCondition:(NSInteger)condition
    //更新condition的值,并解锁指定condition的锁
    //unlockWithCondition:(NSInteger)condition
}

//多个队列执行条件锁
//通过案例可以看出,通过条件锁conditionLock可以设置线程依赖关系
//可以通过GCD设置一个具有依赖关系的任务队列么
- (void)NSConditionLockUpdate {
    //创建并发队列
    dispatch_queue_t queue = 
        dispatch_queue_create("测试NSConditionLock", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        if ([self->_conditionLock tryLockWhenCondition:1]) {
            NSLog(@"第一个");
            //默认初始conditon位1,所有能走到这里
            //然后解锁后,并设置初始值为4,解锁condition设定为4的线程
            [self->_conditionLock unlockWithCondition:4];
        }else {
            [self->_conditionLock lockWhenCondition:0];
            NSLog(@"第一个other");
            [self->_conditionLock unlockWithCondition:4];
        }
    });
    //由于开始初始化的conditon值为1,所以后面三个线程都不满足条件
    //锁定后直到condition调整为当前线程的condition时方解锁
    dispatch_async(queue, ^{
        //condition设置为3后解锁当前线程
        [self->_conditionLock lockWhenCondition:2];
        NSLog(@"第二个");
        //执行完毕后解锁,并设置condition为1,设置初始化默认值,以便于下次使用
        [self->_conditionLock unlockWithCondition:1];
    });
    dispatch_async(queue, ^{
        //condition设置为3后解锁当前线程
        [self->_conditionLock lockWhenCondition:3];
        NSLog(@"第三个");
        //执行完毕后解锁,并设置condition为3,解锁3
        [self->_conditionLock unlockWithCondition:2];
    });
    dispatch_async(queue, ^{
        //condition设置为4后解锁当前线程
        [self->_conditionLock lockWhenCondition:4];
        NSLog(@"第四个");
        //执行完毕后解锁,并设置condition为3,解锁3
        [self->_conditionLock unlockWithCondition:3];
    });
}

 

上面的流程可以大致简化为下面几步:

1.创建一个异步队列,以便于添加后续的任务依赖

2.逐步添加子任务模块,分别在不同线程中,其有明确的依赖关系,即执行顺序为 1、4、3、2

3.使用 lockWhenCondition:开始设置依赖,将其任务解锁的条件condition 设置为其特有的condition 号,以便于解锁

4.执行任务时,如果 NSCondition 中的 condition 参数,与本线程设置的tCondition不一样时,阻塞线程,等待 NSCondition 中的 condition 更改为指定值(通过 unlockWithCondition:更改condition值)解锁

即:默认初始化 condition 为 1,只有 任务1 能够执行,当 任务1 执行 unlockWithCondition:4时,condition被设置为4, 阻塞的任务4解锁,同理,任务4执行完毕后,将 condition 设置为 3 ,任务三解锁,依次类推

5.最终根据设置的依赖关系,分别执行 任务1、任务4、任务3、任务2

7.pthread_mutex(recursive)

其为基于 pthread框架 的递归锁,也是以 pthread互斥锁为基础实现的 递归锁,即:同一个线程下,递归调用时加锁,不会阻塞当前线程,当另一个线程到来时,会因为第一个线程加的锁而阻塞

#pragma mark --pthread递归锁
- (void)pthreadMutexRecursive {
    //初始化锁的递归功能
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    //互斥锁初始化时,绑定递归锁功能模块
    pthread_mutex_init(&_pMutexLock, &attr);

    //使用完毕后在合适的地方销毁,例如dealloc
//    pthread_mutexattr_destroy(&attr);
//    pthread_mutex_destroy(&_pMutexLock);
}

//使用递归锁,递归地时候回不停加锁,如果使用普通的锁早已经形成死锁,无法解脱
//递归锁的存在就是在同一个线程中的锁,不会互斥,只会互斥其他线程的锁,从而避免死锁
- (void)pthreadMutexRecursiveUpdate {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^recursiveBlock)(double count);
        recursiveBlock = ^(double count){
            pthread_mutex_lock(&self->_pMutexLock);
            if (count-- > 0) {
                self->_money++;
                recursiveBlock(count);
            }
            pthread_mutex_unlock(&self->_pMutexLock);
        };
        recursiveBlock(1000);
    });
}

8.NSRecursiveLock

pthread_mutex(recursive)一样,NSRecursiveLock 也是递归锁,其遵循 NSLocking 协议,即除了递归锁功能,还具备正常的互斥锁功能

- (void)lock 加锁。
- (void)unlock 解锁。
- (BOOL)tryLock 尝试加锁。成功返回YES,失败返回NO。
- (BOOL)lockBeforeDete:(NSDate *)limit 在指定时间点之前获取锁,能够获取返回YES,获取不到返回NO。
@property (nullable ,copy) NSString *name 锁名称。

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^recursiveBlock)(double count);
        recursiveBlock = ^(double count){
            [self.irecursiveLock lock];
            //tryLock就不多介绍了,和Pthread的类似,注意返回值即可
            //[self->_recursive tryLock];
            if (count-- > 0) {
                self.count++;
                NSLog(@"%d",self.count);
                recursiveBlock(count);
            }
            [self.irecursiveLock unlock];
        };
        recursiveBlock(1000);
    });

 9.synchronized

synchronized 同步锁,即同步执行,以此避免多线程同时操作同一块代码,基本上在各个平台都会有其身影,虽然效率最低,但由于使用使用简单,深得大家喜爱

objc_sync_enter
要锁的代码
objc_sync_exit

例子:

#pragma mark --同步锁synchronized
- (void)synchronized {
    //使用简单,直接对代码块加同步锁,此代码不会被多个线程直接执行
    //可以间接理解为里面的任务被放到了一个同步队列依次执行(实际实现未知)
    @synchronized (self) {
        self->_money++;
    }
}

objc源码:

######### objc_sync_enter
// 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;
}

######### SyncData
typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;
    DisguisedPtr object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex; //递归锁
} SyncData;

@synchronized结论:

  • 是对互斥锁的一种封装
  • 具体点是种特殊的互斥锁->递归锁,内部搭配 nil防止死锁
  • 通过的结构存要锁的对象
  • 表内部的对象又是通过哈希存储的