阅读 291

ios 锁的应用-读写锁

前言

上一篇博客底层分析-锁我们主要探索了@synchronized底层实现原理,知道了这把锁为什么可以多线程递归加锁。同时也浅尝辄止了每把锁都是不同的,如果使用不好会造成死锁,下面继续探索锁的种类以及实现一把读写锁。

锁的归类

其实基本的锁就包括了三类 自旋锁、互斥锁、读写锁,其他的比如条件锁,递归锁,信号量都是上层的封装和实现!

  • 自旋锁:线程反复检查锁变量是否可用,由于线程在这一过程中保持执行,因此是一种忙等状态。一旦获取了自旋锁,线程会一直保持该锁,直至显示释放自旋锁。自旋锁避免了线程上下文的调度开销,因此对于线程只会阻塞很短的场合是有效的。如OSSpinLockatomic

  • 互斥锁:防止多条线程对同一公共资源(比如全局变量)进行读写机制。该目的是通过将代码切片成一个个临界区而达成。其实简单的说同一时刻保证有一条线程执行任务,其他线程会处在睡眠状态。如NSLockpthread_mutex@synchronized

  • 条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。如NSCondition、NSConditionLock

  • 递归锁:就是同一个线程可以加锁N次而不会引发死锁。如NSRecursiveLock、pthread_mutex

  • 信号量:semaphore是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥

  • 读写锁:读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。读写锁适合于对数据结构的读次数比写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁又叫共享-独占锁。

自旋锁和互斥锁异同

共同点

都能保证同一时刻只能有一个线程操作锁住的代码

不同点

  • 互斥锁:当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入   睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。

  • 自旋锁:当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。

  • 自旋锁应用场景:比较适合做一些不耗时的操作

锁的性能对比

通过开源框架LockPerformance对比锁的开锁和解锁的性能如下 image.png OSSpinLock(自旋锁) ->  os_unfair_lock(互斥锁) ->dispatch_semaphore_t(信号量) ->  pthread_mutex(互斥锁) -> NSLock(互斥锁) -> NSCondition(条件锁) -> pthread_mutex_recursive(互斥递归锁) -> NSRecursiveLock(递归锁) -> NSConditionLock(条件锁) -> synchronized(互斥锁)

自旋锁线程安全吗?

  @property (atomic, strong) NSArray *array; //Thread A     dispatch_async(dispatch_get_global_queue(0, 0), ^{         for (int i = 0; i < 100000; i ++) {             if (i % 2 == 0) {                 self.array = @[@"Hank", @"CC", @"Cooci"];             }             else {                 self.array = @[@"Kody"];             }         }     });     //Thread B     dispatch_async(dispatch_get_global_queue(0, 0), ^{         for (int i = 0; i < 100000; i ++) {             if (self.array.count >= 2) {                 NSString* str = [self.array objectAtIndex:1];             }         }     }); 复制代码

分析:array是一个用自旋锁atomic修饰的属性,在多线程环境下会发生数组越界的奔溃。线程B调用[self.array objectAtIndex:1]时很有可能线程A已经调用了self.array = @[@"Kody"],此时就会发生数组越界的奔溃,这里就需要使用读写锁了。

如何实现一个读写锁

首先分析一下读写锁的特性

  • 多读单写:多条线程读,单条线程写

  • 写入和写入互斥,不能同时写,在分析gcd的时候我们使用了栅栏函数保证写的唯一性

  • 写和读互斥,写的时候不能读。

  • 写不能堵塞主线程

- (void)viewDidLoad {     [super viewDidLoad];     self.dic=[[NSMutableDictionary alloc]init];     //自定义并发队列,栅栏函数必须自定义并发     queue=dispatch_queue_create("ttt", DISPATCH_QUEUE_CONCURRENT);     [self gy_safeSetter:@"gg"];     [self gy_safeSetter:@"bb"];     [self gy_safeSetter:@"tt"];     [self gy_safeSetter:@"mm"]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{     //模拟多线程读     for(int i=0;i<20;i++){         dispatch_async(dispatch_get_global_queue(0, 0), ^{             [self gy_safeGetter];         });     } } //栅栏函数堵塞写,保证每次只有一个写入 -(void)gy_safeSetter:(NSString*)name{     __weak typeof(self) weakself=self;     dispatch_barrier_async(queue, ^{         [weakself.dic setValue:name forKey:@"gy"];         NSLog(@"写入成功%@",name);     }); } //多线程多读,dispatch_sync堵塞的是当前线程,没法堵塞另外一条线程,如果换成dispatch_async,那么当前线程就是异步,result还没设置值就return了,为什么不直接 result=[weakself.dic objectForKey:@"gy"],为了读写互斥 -(NSString*)gy_safeGetter{     __weak typeof(self) weakself=self;     __block NSString* result;     dispatch_sync(queue, ^{         result=[weakself.dic objectForKey:@"gy"];     });     NSLog(@"%@",result);     return result; } 复制代码

分析:

  • 首先看gy_safeGetter读的方法是如何实现多线程读的。使用dispatch_sync堵塞的是当前线程,没法堵塞另外一条线程,如果换成dispatch_async,那么当前线程就是异步,result还没设置值就return了,为什么不直接result=[weakself.dic objectForKey:@"gy"]返回,那是为了保证在栅栏函数中实现读写互斥,栅栏函数的意义就是等待前面的事务完成再实现栅栏里面的事务。

  • 然后看gy_safeSetter写方法,使用栅栏函数实现写写互斥,保证只有一个在写,其他线程等待写完之后再写。注意栅栏函数必须是自定义的并发队列,否则这个栅栏函数的作用等同于一个同步函数的作用


作者:顶风尿一丈
链接:https://juejin.cn/post/7021431016866709518

文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐