iOS常用的几种锁详解以及用法
锁的种类
互斥锁 自旋锁
- 互斥锁:保证在任何时候,都只有一个线程访问对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒;
- 自旋锁:与互斥锁有点类似,只是自旋锁 不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环尝试,直到该自旋锁的保持者已经释放了锁;因为不会引起调用者睡眠,所以效率高于互斥锁;
- 自旋锁缺点:
- 调用者在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时间内获得锁,会使CPU效率降低。所以自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下
- 在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁
两种锁的加锁原理
- 互斥锁:线程会从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.@discussionYou should always initialize a spinlock to {@link OS_SPINLOCK_INIT} beforeusing 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/pthread.h>
,其中有 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 nothingif (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<objc_object> object;int32_t threadCount; // number of THREADS using this blockrecursive_mutex_t mutex; //递归锁
} SyncData;
@synchronized结论:
- 是对互斥锁的一种封装
- 具体点是种特殊的互斥锁->递归锁,内部搭配
nil
防止死锁 - 通过
表
的结构存要锁的对象 - 表内部的对象又是通过哈希存储的
iOS常用的几种锁详解以及用法相关推荐
- 常用的几种设计模式详解
设计模式的概述 设计模式分类 创建型模式 特点是将对象的创建与使用分离(解耦),有 单例.原型.工厂方法.抽象工厂.建造者等5种. 结构型模式 用于描述如何将类或对象按某种布局组成更大的结构,代理.适 ...
- ios 获取html的高度,iOS Webview自适应实际内容高度的4种方法详解
//第一种方法 - (void)webViewDidFinishLoad:(UIWebView *)webView { CGFloat webViewHeight=[webView.scrollVie ...
- python gil 解除_详解Python中的GIL(全局解释器锁)详解及解决GIL的几种方案
先看一道GIL面试题: 描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因. GIL:又叫全局解 ...
- iOS 开发:『Runtime』详解(二)Method Swizzling
本文用来介绍 iOS 开发中『Runtime』中的黑魔法Method Swizzling. 通过本文,您将了解到: Method Swizzling(动态方法交换)简介 Method Swizzlin ...
- 并发编程-04线程安全性之原子性Atomic包的4种类型详解
文章目录 线程安全性文章索引 脑图 概述 原子更新基本类型 Demo AtomicBoolean 场景举例 原子更新数组 Demo 原子更新引用类型 Demo 原子更新字段类型 使用注意事项: Dem ...
- iOS中的HotFix方案总结详解
iOS中的HotFix方案总结详解 相信HotFix大家应该都很熟悉了,今天主要对于最近调研的一些方案做一些总结.iOS中的HotFix方案大致可以分为四种: WaxPatch(Alibaba) Dy ...
- [redis] 10 种数据结构详解
[redis] 10 种数据结构详解 简介 5种常见数据结构 string: 最常见的 string key value list: 双向链表 set: 集合- zset: 有序集合 hash: 类似 ...
- 多线程锁详解之【临界区】
更多的锁介绍可以先看看这篇文章:多线程锁详解之[序章] 正文: 一般锁的类型可分为两种:用户态锁和内核态锁.用户态锁是指这个锁的不能够跨进程使用.而内核态锁就是指能够跨进程使用的锁.一般书中会说,wi ...
- 计算机编程种常见的几种编码详解
计算机编程种常见的几种编码详解 其实计算机编程离不开编码 但是大多数都不能真正全面了解各种编码 今天就来好好和几位编码熟悉熟悉 一.字符.字符集和字符编码方式 字符:字符是抽象的最小文本单位.它没有固 ...
- Redis分布式锁详解
Redis分布式锁详解 1. 分布式所概述 1.1 分布式锁 2. 缓存数据库Redis 2.1 redis简介 2.2 Springboot整合Redis两种方式 3. 实现验证 3.1 环境准备 ...
最新文章
- Selenium的延迟等待
- 一文读懂TOF深度相机技术原理--TI-Tintin-OPT8241二次开发和应用系列--Theory Level
- python3数据类型:Dictionary(字典)
- C语言实现List实现(附完整源码)
- python linux 优化_Linux性能优化(一)
- python-字母与ascii码的转换-利用数字转字母-利用字母转数字
- 基于JSP的蛋糕销售系统设计与实现答辩ppt模板
- android中函数的直接使用用import就可以了吗各种类不用创建对象吗_React Hooks 如何安全地使用state...
- 参与到开源项目中乐趣
- js读取服务器excel文件是否存在,js读取Excel文件
- VeriSign SSL证书产品及服务_VeriSign证书|SSL证书|EVSSL证书|服务器证书|数字证书
- java企业级进销存管理系统源码
- 虚幻引擎4崩溃?10个UE4崩溃解决方法来了
- Python分析上证指数历史数据,发现估值还不够低……
- python3中的@abstractmethod的用法
- 最大公约数(GCD)和最小公倍数(LCM)
- scrapy 爬取腾讯招聘网
- Android仿QQ微信开场导航以及登陆界面
- 微信支付商户证书cert.zip中缺少rootca.pem文件解决方法
- java开发程序员培训班,成功跳槽阿里!