文章目录

  • 自旋锁
    • 1.OSSpinLock
    • 2.os_unfair_lock
    • 3.atomic
  • 互斥锁
    • pthread_mutex
    • @synchronized
      • objc_sync_enter
      • objc_sync_exit
      • 注意事项
    • NSLock
    • NSRecursiveLock
    • 信号量
    • 条件锁
      • NSCondition
      • NSConditionLock
  • 读写锁
  • 总结

锁作为一种非强制的机制,被用来保证线程安全。每一个线程在访问数据或者资源前,要先获取(Acquire)锁,并在访问结束之后释放(Release)锁。如果锁已经被占用,其它试图获取锁的线程会等待,直到锁重新可用
注:不要将过多的其他操作代码放到锁里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了
iOS中锁的基本种类只有三种:互斥锁、自旋锁、读写锁,其他的可能比如:条件锁、递归锁、信号量都是上层的封装和实现

在之前学习中了解过一些锁的知识,很多地方也用到过,在上周看AFNetworking源码的时候也看到了NSLock@synchronized这两种锁用了好多次。

自旋锁

我们在weak的实现原理中有学习过自旋锁,对于每个SideTable中间都有自旋锁,同时也使用了分离锁给单个SideTable上锁。

自旋锁:线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直到显式释放自旋锁。

1.OSSpinLock

自从OSSpinLock出现了安全问题之后就废弃了。自旋锁之所以不安全,是因为自旋锁由于获取锁时,线程会一直处于忙则等待的死锁状态,造成了任务优先级的反转

OSSpinLock忙等的机制就可能造成高优先级一直running等待,占用CPU时间片,而低优先级任务无法抢占时间片,变成迟迟完不成,不释放锁的情况。

// 初始化
spinLock = OS_SPINKLOCK_INIT;
// 加锁
OSSpinLockLock(&spinLock);
// 解锁
OSSpinLockUnlock(&spinLock);

2.os_unfair_lock

weak实现部分的自旋锁就使用的是这个,自旋锁已经不安全了,苹果推出了os_unfair_lock,这个锁解决了优先级反转的问题

//创建一个锁os_unfair_lock_t unfairLock;
//初始化unfairLock = &(OS_UNFAIR_LOCK_INIT);//加锁os_unfair_lock_lock(unfairLock);//解锁os_unfair_lock_unlock(unfairLock);

3.atomic

自旋锁的实际应用,自动生成的setter方法会根据修饰符不同调用不同方法,最后统一调用reallySetProperty方法,其中就有一段关于atomic修饰词的代码。

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{if (offset == 0) {object_setClass(self, newValue);return;}id oldValue;id *slot = (id*) ((char*)self + offset);if (copy) {newValue = [newValue copyWithZone:nil];} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];} else {if (*slot == newValue) return;newValue = objc_retain(newValue);}if (!atomic) {oldValue = *slot;*slot = newValue;} else {spinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();oldValue = *slot;*slot = newValue;        slotlock.unlock();}objc_release(oldValue);
}

比对一下atomic的逻辑分支:

  • 原子性修饰的属性进行了spinlock加锁处理
  • 非原子性的属性除了没加锁,其他逻辑与atomic一般无二

前面提到了os_unfair_lock替代了OSSpinLock,所以在上面还用到了OSSpinLock。

using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {os_unfair_lock mLock;...
}

getter方法也是如此:atomic修饰的属性进行加锁处理。

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {if (offset == 0) {return object_getClass(self);}// Retain release worldid *slot = (id*) ((char*)self + offset);if (!atomic) return *slot;// Atomic retain release worldspinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();id value = objc_retain(*slot);slotlock.unlock();// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.return objc_autoreleaseReturnValue(value);
}

atomic只能保证setter、getter方法的线程安全,并不能保证数据安全。

如果有多个线程同时调用setter的话,不会出现某一个线程执行setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样。 nonatomic不保证setter/getter的原语行,所以你可能会取到不完整的东西。 比如setter函数里面改变两个成员变量,如果你用nonatomic的话,getter可能会取到只更改了其中一个变量时候的状态。 atomic是线程安全的,nonatomic是线程不安全的。如果只是单线程操作的话用nonatomic最好,因为后者效率高一些。

互斥锁

进行互斥操作的锁,防止两条线程同时对同一公共资源(比如全局变量)进行读写操作。

  • 互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
  • 自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待,一旦被访问的资源被解锁,则等待资源的线程会立即执行
  • 自旋锁的效率高于互斥锁。但是我们要注意由于自旋时不释放CPU,因而持有自旋锁的线程应尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,浪费CPU时间。

互斥锁又分为:

  • 递归锁:可重入锁,同一个线程在锁匙放钱可再次获取锁,即可以递归调用
  • 非递归锁:不可重入,必须等锁释放后才能再次获取锁

对于递归锁我们要注意使用时死锁问题,前后代码相互等待就会死锁
对于非递归锁,我们强行使用递归就会造成堵塞而非死锁。

pthread_mutex

pthread_mutex就是互斥锁本身——当锁被占用,而其他线程申请锁时,不是使用忙等,而是阻塞线程并睡眠

// 导入头文件
#import <pthread.h>
// 全局声明互斥锁
pthread_mutex_t _lock;
// 初始化互斥锁
pthread_mutex_init(&_lock, NULL);
// 加锁
pthread_mutex_lock(&_lock);
// 这里做需要线程安全操作
// ...
// 解锁
pthread_mutex_unlock(&_lock);
// 释放锁
pthread_mutex_destroy(&_lock);

@synchronized

@synchronized可能是日常开发中用的比较多的一种互斥锁,因为它的使用比较简单,但并不是在任意场景下都能使用@synchronized,且它的性能较低。
@synchronized需要一个参数,这个参数相当于信号量

// 初始化
@synchronized(锁对象){}
/*
底层封装的pthread_mutex的PTHREAD_MUTEX_RECURSIVE 模式,
锁对象来表示是否为同一把锁
*/

我们来浅看一下它的底层实现:

static void _I_MyPerson_run(MyPerson * self, SEL _cmd) {{ id _rethrow = 0; id _sync_obj = (id)self; objc_sync_enter(_sync_obj);try {struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}~_SYNC_EXIT() {objc_sync_exit(sync_exit);}id sync_exit;} _sync_exit(_sync_obj);NSLog((NSString *)&__NSConstantStringImpl__var_folders_rx_h53wjns9787gpxxz8tg94y6r0000gn_T_MyPerson_9b8773_mi_3);} catch (id e) {_rethrow = e;}{ struct _FIN { _FIN(id reth) : rethrow(reth) {}~_FIN() { if (rethrow) objc_exception_throw(rethrow); }id rethrow;} _fin_force_rethow(_rethrow);}}
}

synchronized调用了try catch,内部调用了objc_sync_enterobjc_sync_exit

objc_sync_enter

// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
//开始同步'obj'。
//如果需要,分配与'obj'关联的递归互斥。
//获取锁后返回OBJC_SYNC_SUCCESS。
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;
}
BREAKPOINT_FUNCTION(void objc_sync_nil(void)
);
  • 首先从它的注释中recursive mutex可以得出@synchronized是递归锁
  • 如果加锁的对象obj不存在时分别会走objc_sync_nil()和不做任何操作。这也是@synchronized作为递归锁但能防止死锁的原因所在:在不断递归的过程中如果对象不存在了就会停止递归从而防止死锁。
  • 正常情况下(obj存在)会通过id2data方法生成一个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;
    • nextData指的是链表中下一个SyncData
    • object指的是当前加锁的对象
    • threadCount表示使用该对象进行加锁的线程数
    • mutex即对象所关联的锁

objc_sync_exit

// End synchronizing on 'obj'.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{int result = OBJC_SYNC_SUCCESS;if (obj) {SyncData* data = id2data(obj, RELEASE); if (!data) {result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;} else {bool okay = data->mutex.tryUnlock();if (!okay) {result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;}}} else {// @synchronized(nil) does nothing}return result;
}

注意事项

  • 不能使用非OC对象作为加锁条件——id2data中接收参数为id类型
  • 多次锁同一个对象会有什么后果吗——会从高速缓存中拿到data,所以只会锁一次对象
  • 都说@synchronized性能低——是因为在底层增删改查消耗了大量性能
  • 加锁对象不能为nil,否则加锁无效,不能保证线程安全

NSLock

NSLock是非递归锁;NSLock是对互斥锁的简单封装.
NSLock在AFNetworking的AFURLSessionManager中有使用到

如果对非递归锁强行使用递归调用,就会在调用时发生堵塞,并非死锁,第一次加锁之后还没出锁就进行递归调用,第二次加锁就堵塞了线程。(因为不会查询缓存)

- (void)test {self.testArray = [NSMutableArray array];NSLock *lock = [[NSLock alloc] init];for (int i = 0; i < 200000; i++) {dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];self.testArray = [NSMutableArray array];[lock unlock];});}
}


从官方文档的解释里看的更清楚,在同一线程上调用NSLock的两次lock方法将永久锁定线程。同时官方文档重点提醒向NSLock对象发送解锁消息时,必须确保该消息是从发送初始锁定消息的同一线程发送的。

NSRecursiveLock

NSRecursiveLock是递归锁

- (void)test {NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];dispatch_async(dispatch_get_global_queue(0, 0), ^{static void (^block)(int);block = ^(int value) {[lock lock];if (value > 0) {NSLog(@"value——%d", value);block(value - 1);}[lock unlock];};block(10);});
}

如果我们在外层添加for循环

- (void)test {NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];for (int i = 0; i < 10; i++) {dispatch_async(dispatch_get_global_queue(0, 0), ^{static void (^block)(int);block = ^(int value) {[lock lock];if (value > 0) {NSLog(@"value——%d", value);block(value - 1);}[lock unlock];};block(10);});}
}

程序就崩了。

因为for循环在block内部对同一个对象进行了多次锁操作,直到这个资源身上挂着N把锁,最后大家都无法一次性解锁,也就是找不到解锁的出口。

即线程1中加锁1、同时线程2中加锁2-> 解锁1等待解锁2 -> 解锁2等待解锁1 -> 无法结束解锁——形成死锁

此时我们可以通过@synchronized对对象进行锁操作,会先从缓存查找是否有锁syncData存在。如果有,直接返回而不加锁,保证锁的唯一性。

同一线程可以多次获取而不会导致死锁的锁。

信号量

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

// 初始化
dispatch_semaphore_t semaphore_t = dispatch_semaphore_create(1);
// 加锁
dispatch_semaphore_wait(semaphore_t,DISPATCH_TIME_FOREVER);
// 解锁
dispatch_semaphore_signal(semaphore_t);
/*
注: dispatch_semaphore  其他两个功能
1.还可以起到阻塞线程的作用.
2.可以实现定时器功能,这里不做过多介绍.
*/

条件锁

就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。
在一定条件下,让其等待休眠,并放开锁,等接收到信号或者广播,会从新唤起线程,并重新加锁,像NSCondition封装了pthread_mutex的以上几个函数,NSConditionLock封装了NSCondition

NSCondition

NSCondition是一个条件锁,可能平时用的不多,但与信号量相似:线程1需要等到条件1满足才会往下走,否则就会堵塞等待,直至条件满足。

// 初始化
NSCondition *_condition= [[NSCondition alloc]init];
// 加锁
[_condition lock];
// 解锁
[_condition unlock];
/*
其他功能接口
wait 进入等待状态
waitUntilDate:让一个线程等待一定的时间
signal 唤醒一个等待的线程
broadcast 唤醒所有等待的线程
*/

我们可以看出:

  • NSCondition是对mutex和cond的一种封装(cond就是用于访问和操作特定类型数据的指针)
  • wait操作会阻塞线程,使其进入休眠状态,直至超时
  • signal操作是唤醒一个正在休眠等待的线程
  • broadcast会唤醒所有正在等待的线程

NSConditionLock

// 初始化
NSConditionLock *_conditionLock = [[NSConditionLock alloc]init];
// 加锁
[_conditionLock lock];
// 解锁
[_conditionLock unlock];
// 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
[_conditionLock tryLock];
/*
其他功能接口
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER; //初始化传入条件
- (void)lockWhenCondition:(NSInteger)condition;//条件成立触发锁
- (BOOL)tryLockWhenCondition:(NSInteger)condition;//尝试条件成立触发锁
- (void)unlockWithCondition:(NSInteger)condition;//条件成立解锁
- (BOOL)lockBeforeDate:(NSDate *)limit;//触发锁 在等待时间之内
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//触发锁 条件成立 并且在等待时间之内
*/
  • NSConditionLock是NSCondition加线程数的封装
  • NSConditionLock可以设置锁条件,而NSCondition只是通知信号

读写锁

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的CPU数
写者是排他性的,⼀个读写锁同时只能有⼀个写者或多个读者(与CPU数相关),但不能同时既有读者⼜有写者。在读写锁保持期间也是抢占失效的
如果读写锁当前没有读者,也没有写者,那么写者可以⽴刻获得读写锁,否则它必须⾃旋在那⾥,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以⽴即获得该读写锁,否则读者必须⾃旋在那⾥,直到写者释放该读写锁。

// 导入头文件
#import <pthread.h>
//普通初始化
// 全局声明读写锁
pthread_rwlock_t lock;
// 初始化读写锁
pthread_rwlock_init(&lock, NULL);
//宏定义初始化
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;// 读操作-加锁
pthread_rwlock_rdlock(&lock);
// 读操作-尝试加锁
pthread_rwlock_tryrdlock(&lock);
// 写操作-加锁
pthread_rwlock_wrlock(&lock);
// 写操作-尝试加锁
pthread_rwlock_trywrlock(&lock);
// 解锁
pthread_rwlock_unlock(&lock);
// 释放锁
pthread_rwlock_destroy(&lock);

总结

  • OSSpinLock不再安全,底层用os_unfair_lock替代
  • atomic只能保证setter、getter时线程安全,所以更多的使用nonatomic来修饰
  • 读写锁更多使用栅栏函数来实现
  • @synchronized在底层维护了一个哈希链表进行data的存储,使用recursive_mutex_t进行加锁
  • NSLock、NSRecursiveLock、NSCondition和NSConditionLock底层都是对pthread_mutex的封装
  • NSCondition和NSConditionLock是条件锁,当满足某一个条件时才能进行操作,和信号量dispatch_semaphore类似
  • 普通场景下涉及到线程安全,可以用NSLock
  • 循环调用时用NSRecursiveLock
  • 循环调用且有线程影响时,请注意死锁,如果有死锁问题请使用@synchronized

【iOS】—— iOS中的相关锁相关推荐

  1. [iOS开发]iOS中的相关锁

    锁作为一种非强制的机制,被用来保证线程安全.每一个线程在访问数据或者资源前,要先获取(Acquire)锁,并在访问结束之后释放(Release)锁.如果锁已经被占用,其它试图获取锁的线程会等待,直到锁 ...

  2. iOS开发中视图相关的小笔记:push、modal、popover、replace、custom

    在storyboard中,segue有几种不同的类型,在iphone和ipad的开发中,segue的类型是不同的. 在iphone中,segue有:push,modal,和custom三种不同的类型, ...

  3. 如何深入理解 iOS 开发中的锁?

    摘要 本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大量的实战经验,另一方面这样的文章相当多,比如 iOS中保证线程安全的几种方式与性能对比.iOS 常见知识点(三):Lock.本文也不 ...

  4. 理解:iOS开发中锁的实现原理

    摘要 本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大量的实战经验,另一方面这样的文章相当多,比如 iOS中保证线程安全的几种方式与性能对比.iOS 常见知识点(三):Lock.本文也不 ...

  5. 深入理解 iOS 开发中的锁

    深入理解 iOS 开发中的锁 摘要 本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大量的实战经验,另一方面这样的文章相当多,比如 iOS中保证线程安全的几种方式与性能对比.iOS 常见知 ...

  6. iOS开发中的锁实现猜测

    本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大量的实战经验,另一方面这样的文章相当多,比如 iOS中保证线程安全的几种方式与性能对比.iOS 常见知识点(三):Lock.本文也不会详细 ...

  7. iOS系统库头文件中NS_AVAILABLE相关

    原文链接: iOS系统库头文件中NS_AVAILABLE相关 简书主页:http://www.jianshu.com/users/37f2920f6848 Github主页:https://githu ...

  8. iOS开发中使用UILabel设置字体的相关技巧小结

    这篇文章主要介绍了iOS开发中UILabel设置字体的相关技巧小结,代码基于传统的Objective-C,需要的朋友可以参考下 一.初始化 复制代码代码如下: UILabel *myLabel = [ ...

  9. iOS 开发中的多线程

    线程.进程 什么是线程.进程   有的人说进程就像是人的脑袋,线程就是脑袋上的头发~~.其实这么比方不算错,但是更简单的来说,用迅雷下载文件,迅雷这个程序就是一个进程,下载的文件就是一个线程,同时下载 ...

最新文章

  1. SQL Server 索引和表体系结构(聚集索引)
  2. erlang精要(17)-匿名函数多子句-多个激活函数实现
  3. Android 用户信息管理程序【SQLite数据库、多选框、单选按钮】
  4. 2021年安徽高考英语口试成绩查询,2021高考英语口语考试成绩
  5. 03-对图像进行数值计算和加权融合
  6. 墨奇科技:生物识别进入可信发展驱动的新阶段
  7. java 水印 位置_Java实现图片加水印且控制位置和透明度
  8. java内部类的作用_java 内部类的好处和缺点(上)
  9. 如何快速学习数据挖掘、机器学习、人工智能?(附资料包)
  10. 题目 1885: 2017 分巧克力
  11. oracle手动 建库_Oracle Create the Database for 11g(手动创建数据库)
  12. Linux上层应用--Shell scripts基础规范
  13. 易语言Note:酷Q插件开发起航
  14. CAD中怎么将Z轴归零?CADZ轴归零教程
  15. 【老骥伏枥-原创】制作黑威联通启动盘:进阶篇
  16. 毕业论文可能用到的一些东西
  17. webgl存本地文件_Unity发布WebGL后加载本地文件
  18. 有没有测试牙齿需不需要修正的软件,小虎正畸:测一测你到底需不需要进行牙齿矫正?...
  19. C语言结构体struct的语法解析
  20. 【UI】常见基础知识整理

热门文章

  1. agx 安装ros opencv_【树莓派ROS开源机器人】阿克曼转向机器人,最接近无人自动驾驶的人工智能机器人...
  2. 数据库 网状模型和层次模型
  3. Win11:Windows无法访问指定设备,路径或文件,您可能没有合适的权限访问这个项目。
  4. uni-app uni-fab 修改图标
  5. 【NOIP2010】【Luogu1199】三国游戏
  6. 从5G到区块链,IPFS将颠覆未来!
  7. 南京理工大学机械考研考情与难度、参考书及上岸前辈备考经验指导
  8. 汉王E典笔——一扫就翻译的笔
  9. Qt 安装包官方下载地址
  10. 031:Mapbox GL实现地图导航功能,可选择起始点、路线、通行方式