iOS 加锁的方式
本人已迁移博客至掘进,以后会在掘进平台更新最新的文章也会有更多的干货,欢迎大家关注!!!https://juejin.im/user/588993965333309
iOS多线程编程中,经常碰到多个线程访问共同的一个资源,在线程相互交互的情况下,需要一些同步措施,来保证线程之间交互的时候是安全的。下面我们一起看一下学一下iOS的几种常用的加锁方式,希望对大家有所帮助!!!
- @synchronized
- NSLock对象锁
- NSRecursiveLock递归锁
- NSConditionLock条件锁
- dispatch_semaphore 信号量实现加锁(也就是GCD)
- OSSpinLock 与 os_unfair_lock
- pthread_mutex
介绍与使用
1.@synchronized
@synchronized关键字加锁,互斥锁,性能较差不推荐在项目中使用。
@synchronized(这里添加一个OC对象,一般使用self) { 这里写要加锁的代码 }
注意点 1.加锁的代码要尽量少 2.添加的OC对象必须在多个线程中都是同一个对象 3.它的优点是不需要显式的创建锁对象,便可以实现锁的机制。 4. @synchronized块会隐式的添加异常处理例程来保护代码,该处理例程会在异常抛出的时候就会自动 的释放互斥锁。如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
下面我们以一个最经典的例子:卖票
//设置票的数量为5 _tickets = 5; //线程1 dispatch_async(self.concurrentQueue, ^{ [self saleTickets]; }); //线程2 dispatch_async(self.concurrentQueue, ^{ [self saleTickets]; }); - (void)saleTickets { while (1) { @synchronized(self) { [NSThread sleepForTimeInterval:1]; if (_tickets > 0) { _tickets--; NSLog(@"剩余票数= %ld, Thread:%@",_tickets,[NSThread currentThread]); } else { NSLog(@"票卖完了 Thread:%@",[NSThread currentThread]); break; } } } }
2.NSLock
基本所有锁的接口都是通过NSLocking协议定义的,定义了lock和unlock方法,通过这些方法获取和释放锁。NSLock是对mutex普通锁的封装
下面还是以卖票的例子讲述一下。
//设置票的数量为5 _tickets = 5; //创建锁 _mutexLock = [[NSLock alloc] init]; //线程1 dispatch_async(self.concurrentQueue, ^{ [self saleTickets]; }); //线程2 dispatch_async(self.concurrentQueue, ^{ [self saleTickets]; }); - (void)saleTickets { while (1) { [NSThread sleepForTimeInterval:1]; //加锁 [_mutexLock lock]; if (_tickets > 0) { _tickets--; NSLog(@"剩余票数= %ld, Thread:%@",_tickets,[NSThread currentThread]); } else { NSLog(@"票卖完了 Thread:%@",[NSThread currentThread]); break; } //解锁 [_mutexLock unlock]; } }
3.NSRecursiveLock递归锁
使用锁比较容易犯的错误是在递归或者循环中造成死锁。
如下代码锁会被多次lock,造成自己被阻塞。
//创建锁 _mutexLock = [[NSLock alloc]init]; //线程1 dispatch_async(self.concurrentQueue, ^{ static void(^TestMethod)(int); TestMethod = ^(int value) { [_mutexLock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; TestMethod(value--); } [_mutexLock unlock]; }; TestMethod(5); });
如果把这个NSLock换成NSRecursiveLock,就可以解决问题。
NSRecursiveLock类定义的锁,可以在同一线程多次lock,不会造成死锁。
//创建锁 _rsLock = [[NSRecursiveLock alloc] init]; //线程1 dispatch_async(self.concurrentQueue, ^{ static void(^TestMethod)(int); TestMethod = ^(int value) { [_rsLock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; TestMethod(value--); } [_rsLock unlock]; }; TestMethod(5); });
4.NSConditionLock条件锁
NSMutableArray *products = [NSMutableArray array]; NSInteger HAS_DATA = 1; NSInteger NO_DATA = 0; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { [lock lockWhenCondition:NO_DATA]; [products addObject:[[NSObject alloc] init]]; NSLog(@"produce a product,总量:%zi",products.count); [lock unlockWithCondition:HAS_DATA]; sleep(1); } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { NSLog(@"wait for product"); [lock lockWhenCondition:HAS_DATA]; [products removeObjectAtIndex:0]; NSLog(@"custome a product"); [lock unlockWithCondition:NO_DATA]; } });
在线程1中的加锁使用了lock,所以是不要条件的,也就锁住了。但在unlock的使用整型条件,它可以开启其他线程中正在等待钥匙的临界池,当线程1循环到一次的时候,打开了线程2的阻塞。
NSCoditionLock中lock,lockWhenCondition:与unlock,unlockWithCondition:是可以随意组合的,具体使用根据需求来区分。
NSCoditionLock 是对NSCodition的进一步封装,可以设置具体的条件值,而NSCodition是对mutex和cond的封装---看本篇博客7.3 条件锁
5.dispatch_semaphore信号量实现加锁
dispatch_semaphore_t signal = dispatch_semaphore_create(1); dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_wait(signal, overTime); NSLog(@"需要线程同步的操作1 开始"); sleep(2); NSLog(@"需要线程同步的操作1 结束"); dispatch_semaphore_signal(signal); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_semaphore_wait(signal, overTime); NSLog(@"需要线程同步的操作2"); dispatch_semaphore_signal(signal); });
dispatch_semaphore是GCD用于同步的方式,与之相关的共有三个函数,dispatch_semaphore_wait,dispatch_semaphore_signal,dispatch_semaphore_create。
(1)dispatch_semaphore_create的声明为:
dispatch_semaphore_t dispatch_semaphore_create(long value);
传入的参数是long类型,输出一个dispatch_semaphore_t类型值为Value的信号量(value传入值不能小于0,否则会报错NULL)
(2)dispatch_semaphore_signal声明为下面:
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
这个方法会使dsema加1;
(3)dispatch_semaphore_wait的声明为下面:
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
这个方法会使dsema减1。
整个逻辑如下:
如果dsema信号量值为大于0,该函数所在线程就会继续执行下面的语句,并将信号量的减去1;如果dsema为0时,函数就会阻塞当前的线程,如果等待的期间发现dsema的值被dispatch_semaphore_signal加1了,并且该函数得到了信号量,那么继续向下执行,并将信号量减1,如果等待期间没有获得信号量或者值一直为0,那么等到timeout,所处的线程也会自动执行下面的代码。
dispatch_semaphore,当信号量为1时,可以作为锁使用。如果没有出现等待的情况,它的性能比pthread_mutex还要高,当如果有等待情况的时候,性能就会下降很多,相比OSSpinLock(暂不讲解),它的优势在于等待的时侯不会消耗CPU资源。
针对上面代码,发现如果超时时间overTime>2,可完成同步操作,反之,在线程1还没有执行完的情况下,此时超时了,将自动执行下面的代码。
上面代码执行结果:
2018-09-18 15:40:52.324 SafeMultiThread[35945:579032] 需要线程同步的操作1 开始 2018-09-18 15:40:52.325 SafeMultiThread[35945:579032] 需要线程同步的操作1 结束 2018-09-18 15:40:52.326 SafeMultiThread[35945:579033] 需要线程同步的操作2
如果将overTime<2s的时候,执行为
2018-09-18 15:40:52.049 SafeMultiThread[30834:434334] 需要线程同步的操作1 开始 2018-09-18 15:40:52.554 SafeMultiThread[30834:434332] 需要线程同步的操作2 2018-09-18 15:40:52.054 SafeMultiThread[30834:434334] 需要线程同步的操作1 结束
6.OSSpinLock自旋锁与os_unfair_lock
6.1 OSSpinLock
OSSpinLock叫做“自旋锁”,自旋锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
但是目前是不再安全了,在iOS 10之后弃用啦,如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。
在使用的过程中需要导入头文件#import <libkern/OSAtomic.h>
//初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回True) bool result = OSSpinLockTry(&lock); //加锁 OSSpinLockLock(&lock); //解锁 OSSpinLockUnlock(&lock)
6.2 os_unfair_lock互斥锁
os_unfair_lock用于取代不安全的OSSpinLock,从iOS10开始才支持。从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
os_unfair_lock需要导入头文件#import<os/lock.h>
//初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //尝试加锁 os_unfair_lock_trylock(&lock); //加锁 os_unfair_lock_lock(&lock); //解锁 os_unfair_lock_unlock(&lock);
7.pthread_mutex
mutex叫做“互斥锁”,等待锁的线程处于休眠状态
需要导入头文件#import <pthread.h>
7.1
//初始化锁的属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); //初始化锁 pthread_mutex_t mutex; pthread_mutex_init(&mutex, &attr); //尝试加锁 pthread_mutex_trylock(&mutex); //加锁 pthread_mutex_lock(&mutex); //解锁 pthread_mutex_unlock(&mutex); //销毁相关资源 --pthread_mutex在对象类释放的时候要销毁,其他锁无此情况 pthread_mutexattr_destroy(&attr); pthread_mutex_destroy(&mutex);
7.2 pthread_mutex-递归锁
//初始化锁的属性 pthread_mutexattr_t attr; pthread_mutexattr_t_int(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//递归锁 //初始化锁 pthread_mutex_t mutex; pthread_mutex_init(&mutex, &attr);
7.3 pthread_mutex-条件
#import "MutexDemo3.h" #import <pthread.h> @interface MutexDemo3() @property (assign, nonatomic) pthread_mutex_t mutex; @property (assign, nonatomic) pthread_cond_t cond; @property (strong, nonatomic) NSMutableArray *data; @end @implementation MutexDemo3 - (instancetype)init { if (self = [super init]) { // 初始化属性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 初始化锁 pthread_mutex_init(&_mutex, &attr); // 销毁属性 pthread_mutexattr_destroy(&attr); // 初始化条件 pthread_cond_init(&_cond, NULL); self.data = [NSMutableArray array]; } return self; } - (void)otherTest { [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start]; } // 生产者-消费者模式 // 线程1 // 删除数组中的元素 - (void)__remove { pthread_mutex_lock(&_mutex); NSLog(@"__remove - begin"); if (self.data.count == 0) { // 等待 pthread_cond_wait(&_cond, &_mutex); } [self.data removeLastObject]; NSLog(@"删除了元素"); pthread_mutex_unlock(&_mutex); } // 线程2 // 往数组中添加元素 - (void)__add { pthread_mutex_lock(&_mutex); sleep(1); [self.data addObject:@"Test"]; NSLog(@"添加了元素"); // 信号 pthread_cond_signal(&_cond); // 广播 // pthread_cond_broadcast(&_cond); pthread_mutex_unlock(&_mutex); } - (void)dealloc { pthread_mutex_destroy(&_mutex); pthread_cond_destroy(&_cond); } @end
补充:自旋锁、互斥锁比较
1. 什么情况下使用自旋锁比较划算?(OSSpinLock,但被os_unfair_lock取代,但是os_unfair_lock不是自旋锁)
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
- CPU资源不紧张
- 多核处理器
2. 什么情况下使用互斥锁比较划算?(pthread_mutex, NSLock等)
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
以上就是自己在开发中所经常使用到的加锁方式,希望对大家有所帮助!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端