锁的种类
在OC中锁分为互斥锁
和自旋锁
两种。
互斥锁
-
用于保护临界区,确保
同一
时间,只有一条线程
能够执行 -
如果代码中只有一个地方需要加锁,大多都使用
self
,这样可以避免单独
再创建一个锁对象
-
加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入
休眠
针对互斥锁,还需要注意以下几点:
-
互斥锁的锁定范围,应该
尽量小
,锁定范围越大
,效率越差
-
能够加锁的任意
NSObject
对象 -
锁对象一定要保证
所有
的线程都能够访问
自旋锁
-
自旋锁
与互斥锁
类似,但它不
是通过休眠
使线程阻塞,而是在获取锁之前一直处于忙等(即原地打转,称为自旋)
阻塞状态 -
使用场景:锁
持有
的时间短
,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符atomic
,本身就有一把自旋锁
-
加入了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用
死循环
的方法,一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能
互斥锁与自旋锁的异同
-
同:
- 在同一时间,保证了只有一条线程执行任务,即保证了相应同步的功能
-
不同:
-
互斥锁
:发现其他线程执行,当前线程休眠
(即就绪状态),进入等待执行,即挂起。一直等其他线程打开之后,然后唤醒执行 -
自旋锁
:发现其他线程执行,当前线程一直询问
(即一直访问),处于忙等
状态,耗费
的性能比较高
-
-
使用场景:
-
根据任务
复杂度区分
,使用不同的锁,但判断不全时,更多是使用互斥锁去处理 当前的任务状态比较短小精悍
时,用自旋锁
-
反之的,用
互斥锁
-
八大锁
- 锁的性能数据
-
属于互斥锁:
- @synchronized
- pthread_mutex
- NSLock
- NSCondition
-
属于条件锁
- NSCondition
- NSConditionLock
-
属于自旋锁
- OSSpinLock
- atomic
图中锁的性能从高到底依次是:OSSpinLock(自旋锁) -> dispatch_semaphone(信号量) -> pthread_mutex(互斥锁) -> NSLock(互斥锁) -> NSCondition(条件锁) -> pthread_mutex(recursive 互斥递归锁) -> NSRecursiveLock(递归锁) -> NSConditionLock(条件锁) -> synchronized(互斥锁)
1. @synchronized
@synchronized是递归互斥锁
1.1 @synchronized的使用
- 新建三个异步线程分别调用
saleTicket
saleTicket
中对属性ticketCount
进行写操作。
- (void)lg_testSaleTicket{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self saleTicket];
}
});
}
- (void)saleTicket{
// 加锁 - 线程安全
@synchronized (self) {
if (self.ticketCount > 0) {
self.ticketCount--;
sleep(0.1);
NSLog(@"当前余票还剩:%ld张",self.ticketCount);
}else{
NSLog(@"当前车票已售罄");
}
}
}
- 打印输出
1.2 源码分析
1. 如何查找源码文件
- 在main.m文件中添加一个
@synchronized
,clang
查看一下源码
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
@synchronized (appDelegateClassName) {
}
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
- 就会发现编译成了
objc_sync_enter
和_sync_exit
- 在项目中
通过汇编
debug => debug workflow => always show … 跟流程,并在objc_sync_enter
打一个断点
- 摁住control + step into,进入底层汇编,就会发现底层的实现是在
libobjc.A.dylib
中
- 在官网下载objc4-718查看源码
objc_sync_enter分析
-
查看
objc_sync_enter
-
判断obj是否为空
- part1: 不为空执行
id2data(obj, ACQUIRE);
- part2: 为空执行
objc_sync_nil
- part1: 不为空执行
-
// 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 nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
part1: 不为空执行id2data(obj, ACQUIRE);
-
查看
SyncData
结构SyncData* nextData
=> 下一个节点object
=> 被锁对象threadCount
=> 几个线程被锁住recursive_mutex_t mutex
=> 封装的互斥锁
recursive_mutex_t
是recursive_mutex_tt
类型的结构体
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
👇
-
查看
id2data
,代码可分为四
部分第一部分
在TLS
线程局部存储缓存(栈
)中查找是否存在SyncData
第二部分
在全局线程缓存列表
中查找,执行第三部分
然后再执行第四部分done
第三部分
第一次进来,给SyncData
赋值,将lockCount和threadCount都设置为1,执行done第四部分
done部分
:并将对象存入TLS
和全局线程缓存列表
中。
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
//第一部分
//part1: 不是第一次进来,但是在同一个线程
//在TLS线程局部存储缓存(栈)中查找是否存在SyncData
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
//获取lockCount
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
//如果threadCount 和 lockCount都小于等于0,抛出异常
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
//why => 外部传入的ACQUIRE或者RELEASE
switch(why) {
//ACQUIRE lockCount + 1
case ACQUIRE: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
//RELEASE lockCount - 1
case RELEASE:
lockCount-