iOS-锁

一 、线程安全

1.1 什么是线程安全

线程操作共享数据的时候不会出现意想不到的结果就叫线程安全,否则,就是线程不安全

1.2 原子属性是一定是线程安全的?

原子属性只能保障 set 或者 get的读写安全,但我们在使用属性的时候,往往既有set又有get,所以说原子属性并不是线程安全的。

二、 iOS中的三种锁

2.1 自旋锁

在访问被锁的资源的时候,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。(忙等)。

优点:因为自旋锁不会引起调用者线程休眠,所以不会进行线程调度,cpu时间片轮转等一些耗时的操作。所以如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁

缺点:自旋锁一直占用CPU,在未获得锁的情况下,一直自旋,相当于死循环,会一直 占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。 而且自旋锁不能实现递归调用

存在的bug:(优先级反转):

当多个线程有优先级的时候,如果一个优先级低的线程先去访问某个数据,此时使用自旋锁进行了加锁,然后一个优先级高的线程又去访问这个数据,那么优先级高的线程因为优先级高会一直占着 CPU资源,此时优先级低的线程无法与优先级高的线程争夺 CPU 时间,从而导致任务迟迟无法完成、锁无法释放。由于自旋锁本身存在的这个问题,所以苹果在iOS10以后已经废弃了OSSpinLock

注意点:如果我们无法保证锁的线程全部处于同一个优先级,就不要使用自旋锁。

2.2 互斥锁(多线程编程中)(又分为递归锁和非递归锁)

互斥锁的出现解决了自旋锁优先级反转的问题。互斥锁是指在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁的资 源释放锁。然后再唤醒休眠线程。(闲等)

互斥锁又分为递归锁和非递归锁
1. 递归锁是可重复加锁,在锁释放之前可再次获取锁(NSRecursiveLock)
2. 非递归锁不能重复加锁,必须等锁释放后才能再次获取锁 (NSLock)

2.3 读写锁

1. 多读单写:在同一时刻可以被多条线程进行读取数据的操作,但是在同一时刻只能有一条线程 在写入数据。
2. 读写互斥:在同以时刻,读和写不能同时进行。

2.4 锁的运行效率对比

锁在运行的时候的效率也各不相同,以下是对比。其中@synchronized在真机条件下效率会有所提升。在开发时,我们可以根据不同情况,选择适合的锁,

来保证我们的代码线程安全。

 

三、详细介绍iOS中的各种锁

3.1 案例

在下面的这段代码中因为开启了异步线程导致count并不能顺序的执行下去,也就是前面说提到的是线程不安全的,那么我们可以借助iOS中的各种锁来解决这个问题。

- (void)test {
    for (int i = 0; i < 10; i ++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            //初始值为50
            self.count --;
            NSLog(@"%d",self.count);
        });
    }
}

3.2 各种锁的初始化定义

@property (nonatomic ,assign) int count;
@property (nonatomic ,assign) os_unfair_lock unfairLock;
@property (nonatomic ,strong) NSLock *iLock;
@property (nonatomic ,strong) NSCondition *iCondition;
@property (nonatomic ,strong) NSConditionLock *iConditionLock;
@property (nonatomic ,strong) NSRecursiveLock *iRecursiveLock;
@property (nonatomic ,strong) dispatch_queue_t iQueue;
@property (nonatomic ,strong) NSMutableDictionary *dataDic;

self.count = 50;
    
self.unfairLock = OS_UNFAIR_LOCK_INIT;
    
self.iLock = [[NSLock alloc] init];
    
self.iCondition = [[NSCondition alloc] init];
        
self.iRecursiveLock = [[NSRecursiveLock alloc] init];
    
self.iQueue = dispatch_queue_create("lg", DISPATCH_QUEUE_CONCURRENT);
    
self.dataDic = [NSMutableDictionary new];
    
    [self lg_write:@"LG"];

3.3 各种锁的结局方案

3.3.1  OSSpinLock

1. OSSpinLock 是自旋锁,会出现优先级反转的问题,在iOS10之后被弃用,需要引用头文件<libkern/OSAtomic.h>
//一些基本操作

OS_SPINLOCK_INIT  
//初始化锁 

OSSpinLockLock(&spinlock)  
//加锁,参数为OSSPINLOCK地址

OSSpinLockUnlock(&spinlock) 
//解锁,参数是OSSpinLock地址

OSSpinLockTry(&spinlock) 
//尝试上锁,参数是OSSpinLock地址。如果返回false,表示上锁失败,锁正在被其他线程持有。如果返回true,表示上锁成功
- (void)OSSpinLock_test {
    OSSpinLockLock(&_spinLock);
    self.count --;
    NSLog(@"%d",self.count);
    OSSpinLockUnlock(&_spinLock);
}

3.3.2  os_unfair_lock

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地址。如果当前线程持有指定的锁,则触发崩溃
//使用
-(void)unfairLock_test {
    os_unfair_lock_lock(&_unfairLock);
    self.count --;
    NSLog(@"%d",self.count);
    os_unfair_lock_unlock(&_unfairLock);
}

3.3.3 NSLock

NSLock基于pthread封装,需要注意的一点是NSLock不能重复加锁解锁,否则会导致程序crash
//一些基本操作

- (void)lock 
//加锁

- (void)unlock 
//解锁

- (BOOL)tryLock 
//尝试加锁。成功返回YES,失败返回NO

- (BOOL)lockBeforeDete:(NSDate *)limit 
//在指定时间点之前获取锁,能够获取返回YES,获取不到返回NO

@property (nullable ,copy) NSString *name
// 锁名称
-(void)nslock_test {
    [self.iLock lock];
    self.count --;
    NSLog(@"%d",self.count);
    [self.iLock unlock];
}

3.3.4 NSCondition

NSCondition存在虚假唤醒的问题
//一些基本操作
- (void)lock 
//加锁

- (void)unlock 
//解锁

- (void)wait
// 阻塞当前线程,使线程进入休眠,等待唤醒信号。调用前必须已加锁

- (void)waitUntilDate 
//阻塞当前线程,使线程进入休眠,等待唤醒信号或者超时。调用前必须已加锁

- (void)signal 
//唤醒一个正在休眠的线程,如果要唤醒多个,需要调用多次。如果没有线程在等待,则什么也不做。调用前必须已加锁

- (void)broadcast 
//唤醒所有在等待的线程。如果没有线程在等待,则什么也不做。调用前必须已加锁

@property (nullable ,copy) NSString *name 
//锁名称

NSCondition存在的虚假唤醒的问题:

当线程从等待已发出信号的条件变量中醒来,却发现它等待的条件不满足时,就会发生虚假唤醒。 
之所以称为虚假,是因为该线程似乎无缘无故地被唤醒了。
原因:signal唤醒时系统将其理解为了broadcast
在许多系统上,尤其是多处理器系统上,虚假唤醒的问题更加严重,因为如果有多个线程在条件变量发出信号时等待它,
系统可能会决定将它们全部唤醒,将每个signal( )唤醒一个线程视为 broadcast( )唤醒所有这些,
从而打破了信号和唤醒之间任何可能预期的
1:1 关系。如果有 10 个线程在等待,那么只有一个会获胜,另外9个会经历虚假唤醒。
//例子
- (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) {//if是虚假唤醒,可以改为while就不会虚假唤醒 [self.iCondition wait]; } self.count --; NSLog(@"消费了一个产品,现有产品: %d个",self.count); [self.iCondition unlock]; }

3.3.5 NSConditionLock

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)lg_testConditonLock{
    //如何让123变的顺序
    //第一种方法:使用信号量
    //第二种方法:使用NSConditonLock
    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];
    });
}

3.3.6 NSRecursiveLock

NSRecursiveLock是递归锁,在递归函数的时候使用
//一些操作

- (void)lock 
//加锁

- (void)unlock 
//解锁

- (BOOL)tryLock 
//尝试加锁。成功返回YES,失败返回NO

- (BOOL)lockBeforeDete:(NSDate *)limit 
//在指定时间点之前获取锁,能够获取返回YES,获取不到返回NO

@property (nullable ,copy) NSString *name 
//锁名称。
-(void)recursiveLock_test {
    
    [self.iRecursiveLock lock];
    self.count --;
    NSLog(@"%d",self.count);
    [self.iRecursiveLock unlock];
}
//注意点 递归锁不可以在多个线程下递归调用
- (void)recursiveTest {
    //如果这里加for循环async时
    //因为这个锁是递归锁,他可以在同一时刻能够被多个线程所拥有
    //但解锁的时候需要保证该锁不被其他线程所拥有,那么就会导致多个线程此时就会产生相互等待彼此解锁的死锁情况
    //所以递归锁不能在多个线程下递归调用 此时就可以用@synchronized
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void (^recursiveMethod)(int);
        recursiveMethod = ^(int value){
            if (value > 0) {
                //@synchronized(self)
                [self.iRecursiveLock lock];
                NSLog(@"%d",value);
                recursiveMethod(value - 1);
                [self.iRecursiveLock unlock];
            }
        };
        recursiveMethod(10);
    });
}

3.3.7 @synchronized

@synchronized是非常简单的一把锁
//使用

- (void)synchronizedTest {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void (^recursiveMethod)(int);
        recursiveMethod = ^(int value){
            if (value > 0) {
                @synchronized(self)
                NSLog(@"%d",value);
                recursiveMethod(value - 1);
            }
        };
        recursiveMethod(10);
    });
}

3.3.8 信号量

信号量锁
//一些基本操作

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操作
//使用

- (void)lg_dispatch_semaphore_t {
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务1");
        dispatch_semaphore_signal(sem);
    });
    
    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");
    });
}

3.3.9 pthread_mutex

pthread_mutex是C语言实现的锁,需要开发人员自己实现锁生命周期的管理
//一些基本操作

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()
//获取互斥锁的范围
//使用

- (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);
}

3.3.10 读写锁(栅栏函数+异步函数实现)

//多读单写,读写互斥

@property (nonatomic ,strong) dispatch_queue_t iQueue;
self.iQueue = dispatch_queue_create("lg", DISPATCH_QUEUE_CONCURRENT);

- (NSString *)lg_read {
    // 异步读取
    __block NSString *ret;
   
    dispatch_sync(self.iQueue, ^{
        // 读取的代码
        ret = self.dataDic[@"name"];
    });
    NSLog(@"%@",ret);
    return ret;
}
-(void)multiRead{
//    多读可以用for循环
    for(int i = 0 ; i < 10 ;i++){
        dispatch_async(self.iQueue, ^{
            // 读取的代码
            [self lg_read];
        });
    }
}

- (void)lg_write: (NSString *)name {
    // 写操作
    //栅栏函数保证了读写互斥
    dispatch_barrier_async(self.iQueue, ^{
        [self.dataDic setObject:name forKey:@"name"];
    });
}
posted on 2022-06-04 11:21  suanningmeng98  阅读(220)  评论(0编辑  收藏  举报