锁的种类


互斥锁 自旋锁

  • 互斥锁:保证在任何时候,都只有一个线程访问对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒;
  • 自旋锁:与互斥锁有点类似,只是自旋锁 不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环尝试,直到该自旋锁的保持者已经释放了锁;因为不会引起调用者睡眠,所以效率高于互斥锁;
  • 自旋锁缺点:
  1. 调用者在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时间内获得锁,会使CPU效率降低。所以自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下
  2. 在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁

两种锁的加锁原理

  • 互斥锁:线程会从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常用的几种锁详解以及用法相关推荐

  1. 常用的几种设计模式详解

    设计模式的概述 设计模式分类 创建型模式 特点是将对象的创建与使用分离(解耦),有 单例.原型.工厂方法.抽象工厂.建造者等5种. 结构型模式 用于描述如何将类或对象按某种布局组成更大的结构,代理.适 ...

  2. ios 获取html的高度,iOS Webview自适应实际内容高度的4种方法详解

    //第一种方法 - (void)webViewDidFinishLoad:(UIWebView *)webView { CGFloat webViewHeight=[webView.scrollVie ...

  3. python gil 解除_详解Python中的GIL(全局解释器锁)详解及解决GIL的几种方案

    先看一道GIL面试题: 描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因. GIL:又叫全局解 ...

  4. iOS 开发:『Runtime』详解(二)Method Swizzling

    本文用来介绍 iOS 开发中『Runtime』中的黑魔法Method Swizzling. 通过本文,您将了解到: Method Swizzling(动态方法交换)简介 Method Swizzlin ...

  5. 并发编程-04线程安全性之原子性Atomic包的4种类型详解

    文章目录 线程安全性文章索引 脑图 概述 原子更新基本类型 Demo AtomicBoolean 场景举例 原子更新数组 Demo 原子更新引用类型 Demo 原子更新字段类型 使用注意事项: Dem ...

  6. iOS中的HotFix方案总结详解

    iOS中的HotFix方案总结详解 相信HotFix大家应该都很熟悉了,今天主要对于最近调研的一些方案做一些总结.iOS中的HotFix方案大致可以分为四种: WaxPatch(Alibaba) Dy ...

  7. [redis] 10 种数据结构详解

    [redis] 10 种数据结构详解 简介 5种常见数据结构 string: 最常见的 string key value list: 双向链表 set: 集合- zset: 有序集合 hash: 类似 ...

  8. 多线程锁详解之【临界区】

    更多的锁介绍可以先看看这篇文章:多线程锁详解之[序章] 正文: 一般锁的类型可分为两种:用户态锁和内核态锁.用户态锁是指这个锁的不能够跨进程使用.而内核态锁就是指能够跨进程使用的锁.一般书中会说,wi ...

  9. 计算机编程种常见的几种编码详解

    计算机编程种常见的几种编码详解 其实计算机编程离不开编码 但是大多数都不能真正全面了解各种编码 今天就来好好和几位编码熟悉熟悉 一.字符.字符集和字符编码方式 字符:字符是抽象的最小文本单位.它没有固 ...

  10. Redis分布式锁详解

    Redis分布式锁详解 1. 分布式所概述 1.1 分布式锁 2. 缓存数据库Redis 2.1 redis简介 2.2 Springboot整合Redis两种方式 3. 实现验证 3.1 环境准备 ...

最新文章

  1. Selenium的延迟等待
  2. 一文读懂TOF深度相机技术原理--TI-Tintin-OPT8241二次开发和应用系列--Theory Level
  3. python3数据类型:Dictionary(字典)
  4. C语言实现List实现(附完整源码)
  5. python linux 优化_Linux性能优化(一)
  6. python-字母与ascii码的转换-利用数字转字母-利用字母转数字
  7. 基于JSP的蛋糕销售系统设计与实现答辩ppt模板
  8. android中函数的直接使用用import就可以了吗各种类不用创建对象吗_React Hooks 如何安全地使用state...
  9. 参与到开源项目中乐趣
  10. js读取服务器excel文件是否存在,js读取Excel文件
  11. VeriSign SSL证书产品及服务_VeriSign证书|SSL证书|EVSSL证书|服务器证书|数字证书
  12. java企业级进销存管理系统源码
  13. 虚幻引擎4崩溃?10个UE4崩溃解决方法来了
  14. Python分析上证指数历史数据,发现估值还不够低……
  15. python3中的@abstractmethod的用法
  16. 最大公约数(GCD)和最小公倍数(LCM)
  17. scrapy 爬取腾讯招聘网
  18. Android仿QQ微信开场导航以及登陆界面
  19. 微信支付商户证书cert.zip中缺少rootca.pem文件解决方法
  20. java开发程序员培训班,成功跳槽阿里!

热门文章

  1. 这届打工人,回家过年都这么难
  2. CSS实现文字描边效果
  3. 优化算法——差分进化算法(DE)
  4. 软件测试(三)--标准的测试用例模板
  5. 测试用例(等价类划分法)
  6. 禁忌搜索算法TS求解连续函数最值
  7. 手机python安装教程_Python安装不用愁,Python安装教程来了(2021)
  8. Git小乌龟添加忽略文件
  9. 2021-0(C++)输入一个字符串,判断其是否是回文字符串(回文字符串就是正序与反序是相同的字符串)5-27
  10. 神经网络算法开篇——逻辑回归