ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

iOS-锁

2022-06-04 11:32:55  阅读:166  来源: 互联网

标签:lock self iOS dispatch 线程 pthread void


一 、线程安全

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"];
    });
}

标签:lock,self,iOS,dispatch,线程,pthread,void
来源: https://www.cnblogs.com/SNMX/p/16341507.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有