iOS 锁的底层原理
Posted WeaterMr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 锁的底层原理相关的知识,希望对你有一定的参考价值。
@synchronized(互斥锁) 原理
1.clang分析实现原理
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);
分析:
- 1.
objc_sync_enter
(_sync_obj); - 2.
_SYNC_EXIT
结构体 - 3.
_sync_exit
(_sync_obj);
objc_sync_enter 函数探索
int objc_sync_enter(id obj)
int result = OBJC_SYNC_SUCCESS;
if (obj)
// obj 存在
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock();
else
// obj 不存在 什么事情也不做
return result;
分析:
- 1.被锁对象
obj
为空的时候都是什么事情也不会做。 - 2.核心函数
id2data
(两个状态参数:ACQUIRE
与RELEASE
) - 3.
SyncData
的数据结构。
SyncData 数据结构
typedef struct alignas(CacheLineSize) SyncData
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // 使用此块的线程数
recursive_mutex_t mutex; //递归锁
SyncData;
分析:
- 1.
nextData
判断为单向链表,头插法。 - 2.
threadCount
多线程标记 - 3.
mutex
递归锁
id2data 函数实现分析
struct SyncList
SyncData *data;
spinlock_t lock;
constexpr SyncList() : data(nil), lock(fork_unsafe_lock)
;
// 使用多个并行列表来减少不相关对象之间的争用
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
// 全局静态变量 哈希表
static StripedMap<SyncList> sDataLists;
// 哈希函数 确定一个下标
// 可能会发生冲突 (之前通过在哈希来解决)
// 这里 使用 拉链法
...
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
//支持线程局部存储
// 检查每个线程的单条目快速缓存是否匹配对象
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
// 第一部分
if (data) ...
#endif
// 检查已经拥有的锁的每个线程缓存是否匹配对象
SyncCache *cache = fetch_cache(NO);
// 第二部分
if (cache) ...
//线程缓存没有找到任何东西
//遍历正在使用的列表以查找匹配的对象
//自旋锁防止多个线程创建多个
//锁定相同的新对象
//我们可以把节点保存在某个哈希表中,如果有的话
//有20多个不同的锁在工作中,但我们现在不这么做。
//这里的锁 是为了保证在开辟内存空间时候的安全, 和外面的锁不一样哦, 此处是一个 spinlock_t 在上面有定义
lockp->lock();
// 第三部分
// 代码块
...
// 分配一个新的SyncData并添加到列表中.
// XXX在持有全局锁的情况下分配内存是不好的做法,
// 可能值得释放锁,重新分配,再搜索一次.
// 但由于我们从不释放这些人我们不会经常陷入分配中.
// 第四部分
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
done:
lockp->unlock(); //这里的锁 是为了保证在开辟内存空间时候的安全, 和外面的锁不一样哦,此处是一个 spinlock_t 在上面有定义
// 第五部分
if (result) ...
return result;
- 1: 创建一张全局的
哈希表
(static StripedMap sDataLists;), 使用拉链法来存储SyncData
;这里的全局表中存放不同对象,拉链拉的是通过hash计算得到的相同索引与相同对象的不同线程SyncData
- 2:
sDataLists
, 中array
存储的是SyncList
(绑定的是objc
,我们加锁的对象) - 3:
objc_sync_enter
和objc_sync_exit
调用对称,封装的是递归锁 - 4: 两种存储 :
TLS
(线程局部存储) /cache
(线程缓存) - 5: 第一次
syncData
头插法 链表 标记threadCount
= 1 - 6: 判断是否同一个对象:如果是,
lockCount
++ - 7:
TLS
找到 ->lock
++ - 8: TLS 找不到 sync threadCount ++
- 9:
lock
— threadCount—
Synchronized
: 可重入 递归 多线程 - 1:
TLS
保障threadCount
多少条线程对这个锁对象加锁 - 2:
lock
++ 进来多少次
NSCondition和NSConditionLock
NSCondition
条件锁,顾名思义,就是满足某些条件才会开锁。NSCondition
,可以确保线程仅在满足特定条件时才能获取锁。一旦获得了锁并执行了代码的关键部分,线程就可以放弃该锁并将关联条件设置为新的条件。条件本身是任意的:可以根据应用程序的需要定义它们。
NSCondition
对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。通俗的说,也就是条件成立,才会执行锁住的代码。条件不成立时,线程就会阻塞,直到另一个线程向条件对象发出信号解锁为止。
下面我们看一个例子:
- (void)conditionTest
for (int i = 0; i < 50; i++)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
[self addTickets];
);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
[self addTickets];
);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
[self minusTickets];
);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
[self minusTickets];
);
- (void)addTickets
self.ticketCount += 1;
NSLog(@"加一个现有ticketCount==%zd",self.ticketCount);
- (void)minusTickets
while (self.ticketCount == 0)
NSLog(@"==没有ticketCount==");
return;
self.ticketCount -= 1;
NSLog(@"减一个剩下ticketCount==%zd",self.ticketCount);
运行程序,我们发现控制台的输出是有问题的
此时,我们就可以使用条件锁解决问题。我们只需要对程序作如下改动就可以正常执行:
- (void)addTickets
[self.condition lock];
self.ticketCount = self.ticketCount + 1;
NSLog(@"现有ticketCount==%zd",self.ticketCount);
[self.condition unlock];
[self.condition signal];
- (void)minusTickets
[self.condition lock];
while (self.ticketCount == 0)
NSLog(@"==没有ticketCount==");
[self.condition wait];
return;
self.ticketCount -= 1;
NSLog(@"减一个,剩下ticketCount==%zd",self.ticketCount);
[self.condition unlock];
需要使用swift源码进行查看:
open class NSCondition: NSObject, NSLocking
internal var mutex = _MutexPointer.allocate(capacity: 1)
internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
public override init()
pthread_mutex_init(mutex, nil)
pthread_cond_init(cond, nil)
deinit
pthread_mutex_destroy(mutex)
pthread_cond_destroy(cond)
mutex.deinitialize(count: 1)
cond.deinitialize(count: 1)
mutex.deallocate()
cond.deallocate()
// 一般用于多线程同时访问、修改同一个数据源,保证在同一 时间内数据源只被访问、修改一次,
// 其他线程的命令需要在lock 外等待,只到 unlock ,才可访问
open func lock()
pthread_mutex_lock(mutex)
// 释放锁,与lock成对出现
open func unlock()
pthread_mutex_unlock(mutex)
// 让当前线程处于等待状态,阻塞
open func wait()
pthread_cond_wait(cond, mutex)
// 让当前线程等待到某个时间,阻塞
open func wait(until limit: Date) -> Bool
guard var timeout = timeSpecFrom(date: limit) else
return false
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
// 发信号告诉线程可以继续执行,唤醒线程
open func signal()
pthread_cond_signal(cond)
open func broadcast()
pthread_cond_broadcast(cond) // wait signal
open var name: String?
可以看到,该对象还是对pthread_mutex
的一层封装,NSCondition
也是一种互斥锁。当我们需要等待某个条件的时候,也就是条件不满足的时候,就可以使用wait方法来阻塞线程,当条件满足了,使用signal方法发送信号唤醒线程。
NSConditionLock
对NSCondition
又做了一层封装,自带条件探测,能够更简单灵活的使用。
我们使用swift查看一下:
internal var _cond = NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?
public convenience override init()
self.init(condition: 0)
public init(condition: Int)
_value = condition
// 表示 xxx 期待获得锁,
// 如果没有其他线程获得锁(不需要判断内部的 condition) 那它能执行此行以下代码,
// 如果已经有其他线程获得锁(可能是条件锁,或者无条件 锁),则等待,直至其他线程解锁
open func lock()
let _ = lock(before: Date.distantFuture)
open func unlock()
_cond.lock()
_thread = nil
_cond.broadcast()
_cond.unlock()
open var condition: Int
return _value
// 表示如果没有其他线程获得该锁,但是该锁内部的 condition不等于A条件,它依然不能获得锁,仍然等待。
// 如果内部的condition等于A条件,并且没有其他线程获得该锁,则执行任务,同时设置它获得该锁
// 其他任何线程都将等待它代码的完成,直至它解锁。
open func lock(whenCondition condition: Int)
let _ = lock(whenCondition: condition, before: Date.distantFuture)
open func `try`() -> Bool
return lock(before: Date.distantPast)
open func tryLock(whenCondition condition: Int) -> Bool
return lock(whenCondition: condition, before: Date.distantPast)
// 表示释放锁,同时把内部的condition设置为A条件
open func unlock(withCondition condition: Int)
_cond.lock()
_thread = nil
_value = condition
_cond.broadcast()
_cond.unlock()
open func lock(before limit: Date) -> Bool
_cond.lock()
while _thread != nil
if !_cond.wait(until: limit)
_cond.unlock()
return false
_thread = pthread_self()
_cond.unlock()
return true
// 表示如果被锁定(没获得 锁),并超过该时间则不再阻塞线程。
// 需要注意的是:返回的值是NO,它没有改变锁的状态,这个函 数的目的在于可以实现两种状态下的处理
open func lock(whenCondition condition: Int, before limit: Date) -> Bool
_cond.lock()
while _thread != nil || _value != condition
if !_cond.wait(until: limit)
_cond.unlock()
return false
_thread = pthread_self()
_cond.unlock()
return true
open var name: String?
可以看出,触发的唤醒线程的条件是传入的condition
取值,和我们创建锁的时候值要相同,我们可以在释放当前线程锁的时候重新设置其他线程传入的condition
值,这样也就达到了唤醒其他线程的目的。如果创建锁的值和传入的值都不能匹配,则会进入阻塞状态。
下面我们来看个例子:
- (void)conditionLockTest
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
[conditionLock lockWhenCondition:1];
NSLog(@"线程1");
[conditionLock unlockWithCondition:0];
);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^
[conditionLock lockWhenCondition:2];
NSLog(@"线程2");
[conditionLock unlockWithCondition:1];
);
dispatch_async(dispatch_get_global_queue(0, 0), ^
[conditionLock lock];
NSLog(@"线程3");
[conditionLock unlock];
);
执行流程首先搞明白传入的2
的作用,这个值只是一个初始条件,
他的配套使用方法是lockWhenCondition
通过判断条件是否相等来决定是否等待,并且在使用过程中这个条件可以更改,从而达到按照一定顺序执行任务的目的。如果不使用条件将不受条件控制。
总结
相同点:
都是互斥锁
通过条件变量来控制加锁、释放锁,从而达到阻塞线程、唤醒线程的目的
不同点:
NSCondition
是基于对pthread_mutex
的封装,而NSConditionLock
是对NSCondition
做了一层封装
NSCondition
是需要手动让线程进入等待状态阻塞线程、释放信号唤醒线程,NSConditionLock
则只需要外部传入一个值,就会依据这个值进行自动判断是阻塞线程还是唤醒线程
以上是关于iOS 锁的底层原理的主要内容,如果未能解决你的问题,请参考以下文章