iOS底层探索(二十一)锁(下)
iOS底层探索(二十一)锁(下)
NSLock
自定义NSLock
代码,如下:
NSLock *lock = [[NSLock alloc] init];
[lock lock];
[lock unlock];
查看lock
与unlock
方法。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
打符号断点,打开汇编,可知lock
方法来自Foundation
框架。
因为我们知道Objective-C
的Foundation
是闭源且没有源码的,因此,我们下载Swift
的Foundation
的源码,因为Swift
与Objective-C
的源码大体相似.
查看NSLock
的源码,如下
open class NSLock: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
#if os(macOS) || os(iOS) || os(Windows)
private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
#endif
public override init() {
#if os(Windows)
InitializeSRWLock(mutex)
InitializeConditionVariable(timeoutCond)
InitializeSRWLock(timeoutMutex)
#else
pthread_mutex_init(mutex, nil)
#if os(macOS) || os(iOS)
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
#endif
#endif
}
deinit {
#if os(Windows)
// SRWLocks do not need to be explicitly destroyed
#else
pthread_mutex_destroy(mutex)
#endif
mutex.deinitialize(count: 1)
mutex.deallocate()
#if os(macOS) || os(iOS) || os(Windows)
deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
#endif
}
open func lock() {
#if os(Windows)
AcquireSRWLockExclusive(mutex)
#else
pthread_mutex_lock(mutex)
#endif
}
open func unlock() {
#if os(Windows)
ReleaseSRWLockExclusive(mutex)
AcquireSRWLockExclusive(timeoutMutex)
WakeAllConditionVariable(timeoutCond)
ReleaseSRWLockExclusive(timeoutMutex)
#else
pthread_mutex_unlock(mutex)
#if os(macOS) || os(iOS)
// Wakeup any threads waiting in lock(before:)
pthread_mutex_lock(timeoutMutex)
pthread_cond_broadcast(timeoutCond)
pthread_mutex_unlock(timeoutMutex)
#endif
#endif
}
open func `try`() -> Bool {
#if os(Windows)
return TryAcquireSRWLockExclusive(mutex) != 0
#else
return pthread_mutex_trylock(mutex) == 0
#endif
}
open func lock(before limit: Date) -> Bool {
#if os(Windows)
if TryAcquireSRWLockExclusive(mutex) != 0 {
return true
}
#else
if pthread_mutex_trylock(mutex) == 0 {
return true
}
#endif
#if os(macOS) || os(iOS) || os(Windows)
return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
#else
guard var endTime = timeSpecFrom(date: limit) else {
return false
}
return pthread_mutex_timedlock(mutex, &endTime) == 0
#endif
}
open var name: String?
}
NSLock必须调用init
方法,因为在init
中做了一些初始化操作。
NSRecursiveLock
自定义如下代码,
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
};
testMethod(10);
});
}
这是一个嵌套递归的调用,我们需要加锁,可我们应该加在哪里呢?
源码如下:
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[lock lock];
};
testMethod(10);
[lock unlock];
});
}
这种加锁是正确的,如果是在调用方法之前加锁,在方法调用之后解锁的话,将会出现锁住的情况。因为NSLock
不是递归锁,只是简单的互斥锁。
- 在同一个线程中,可能出现多次
lock
的情况。而无法执行unlock
。 - 在不同线程中,即在
2线程
中执行了lock
并执行了3线程
的任务,而3线程
中也存在lock
,因此这是出现了2线程
与3线程
出现了相互等待。
使用@synchronized
也可以进行加锁,源码如下
@synchronized (self) {
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
}
但是使用这种锁会出现性能问题。即肉眼可见的卡顿。这个时候我们可以使用NSRecursiveLock
进行加锁。源码如下
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
[recursiveLock lock];
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[recursiveLock unlock];
};
testMethod(10);
});
}
使用NSRecursiveLock
可以正常加锁,堵塞性能会比NSLock
好一些。
搜索查看NSRecursiveLock
的底层封装。
open class NSRecursiveLock: NSObject, NSLocking {
internal var mutex = _RecursiveMutexPointer.allocate(capacity: 1)
#if os(macOS) || os(iOS) || os(Windows)
private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
#endif
public override init() {
super.init()
#if os(Windows)
InitializeCriticalSection(mutex)
InitializeConditionVariable(timeoutCond)
InitializeSRWLock(timeoutMutex)
#else
#if CYGWIN
var attrib : pthread_mutexattr_t? = nil
#else
var attrib = pthread_mutexattr_t()
#endif
withUnsafeMutablePointer(to: &attrib) { attrs in
pthread_mutexattr_init(attrs)
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
pthread_mutex_init(mutex, attrs)
}
#if os(macOS) || os(iOS)
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
#endif
#endif
}
deinit {
#if os(Windows)
DeleteCriticalSection(mutex)
#else
pthread_mutex_destroy(mutex)
#endif
mutex.deinitialize(count: 1)
mutex.deallocate()
#if os(macOS) || os(iOS) || os(Windows)
deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
#endif
}
open func lock() {
#if os(Windows)
EnterCriticalSection(mutex)
#else
pthread_mutex_lock(mutex)
#endif
}
open func unlock() {
#if os(Windows)
LeaveCriticalSection(mutex)
AcquireSRWLockExclusive(timeoutMutex)
WakeAllConditionVariable(timeoutCond)
ReleaseSRWLockExclusive(timeoutMutex)
#else
pthread_mutex_unlock(mutex)
#if os(macOS) || os(iOS)
// Wakeup any threads waiting in lock(before:)
pthread_mutex_lock(timeoutMutex)
pthread_cond_broadcast(timeoutCond)
pthread_mutex_unlock(timeoutMutex)
#endif
#endif
}
open func `try`() -> Bool {
#if os(Windows)
return TryEnterCriticalSection(mutex)
#else
return pthread_mutex_trylock(mutex) == 0
#endif
}
open func lock(before limit: Date) -> Bool {
#if os(Windows)
if TryEnterCriticalSection(mutex) {
return true
}
#else
if pthread_mutex_trylock(mutex) == 0 {
return true
}
#endif
#if os(macOS) || os(iOS) || os(Windows)
return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
#else
guard var endTime = timeSpecFrom(date: limit) else {
return false
}
return pthread_mutex_timedlock(mutex, &endTime) == 0
#endif
}
open var name: String?
}
对比NSLock
的代码,查看区别,
区别在于pthread_mutex_init
方法初始化的内容。用于区别递归锁。
但是在使用时不如@synchronized
方便,因为一旦出现加锁失败时,或者加错位置时,就会引发其他问题。比如崩溃。但是他的性能比较好。
互斥锁
在Posix Thread中定义有一套专门用于线程同步的mutex函数
mutex用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。
互斥锁分为递归锁和非递归锁。
NSCondition
NSCondition
的对象实际上作为一个锁和一个线程检查其:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。
-
[condition lock]
一般用于多线程同时访问、修改同一个数据源,保证在同一个时间内数据源只被访问、修改一次,其他线程的命令需要在lock
外等待,直到unlock
才可访问 -
[condition unlock]
与lock
同时使用 -
[condition wait]
让当前线程处于等待状态 -
[condition signal]
CPU发信号告诉线程不用再等待,可以继续执行 - 含义在于需要等待条件满足才能执行,不满足不执行
- 模型:
生产-消费者模型
,
自定义源码如下:
- (void)lg_testConditon{
_testCondition = [[NSCondition alloc] init];
//创建生产-消费者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
}
}
- (void)lg_producer{
[_testCondition lock]; // 操作的多线程影响
self.ticketCount = self.ticketCount + 1;
NSLog(@"生产一个 现有 count %zd",self.ticketCount);
[_testCondition signal]; // 信号
[_testCondition unlock];
}
- (void)lg_consumer{
[_testCondition lock]; // 加锁的目的为 操作的多线程影响
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait];
}
//注意消费行为,要在等待条件判断之后
self.ticketCount -= 1;
NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
即当ticketCount
大于1
时才能执行-1
操作。但是这种写法过于麻烦,难道没有简单的吗
NSConditionLock
-
NSConditionLock
是锁,一旦一个线程获得锁,其他线程一定等待 -
[xxxx lock]
表示xxxx
期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition)那它能执行此行一下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁),则等待,直至其他线程解锁 -
[xxxx lockWhenCondition:A条件]
表示如果没有其他线程获得该锁,但是该锁内部的condition
不等于A条件,它依然不能获得锁,仍然等待。如果内部的conditon
等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它的代码完成,直至它解锁。 -
[xxxx unlockWithCondition:A条件]
表示释放锁,同时把内部的condition
设置为A条件 -
return = [xxxx lockWhenCondition:A条件 beforeDate:A时间]
表示如果被锁定(没有获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO
,它没有改变锁的状态,这个函数的目的在于可以实现两种状态下的处理 - 所谓的
condition
就是整数,内部通过整数比较条件
自定义代码如下:
- (void)testConditionLock {
// 信号量
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1]; // conditoion = 1 内部 Condition 匹配
NSLog(@"线程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
sleep(0.1);
NSLog(@"线程 2");
[conditionLock unlockWithCondition:1]; // _value = 2 -> 1
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"线程 3");
[conditionLock unlock];
});
}
条件变量与条件锁有什么关系
想要真正了解条件锁,需要进行汇编查看,当然也要明白你要探索的是什么,因为我们知道NSConditionLock
是对NSCondition
的封装,而NSCondition
又对pthread
进行了封装。
进入汇编调试。断点断住后,读取寄存器信息。
读取寄存器的x0
信息,x0
为调用objc_msgSend
的第一个参数,即消息的接收者,也就是当前当前方法的对象。
读取寄存器的x1
信息,x1
为对象的调用方法,即SEL
。方法为_dispatch_call_block_and_release
过断点到objc_msgSend
位置。再次查看x0
信息。
查看x1
信息,查看调用方法。
接下来进行符号断点进行查看,符号断点为-[NSConditionLock lockWhenCondition:]
,再次运行进行查看。
我们只需要查看一些跳转等操作及bl
等操作。
先查看一下当前寄存器的内容。
然后过断点。直接过到b
指令处
到这里就没有东西了,查看不到底层的锁是怎么操作的。
那怎么办呢?
重新运行我们在ldr
指令执行前后做寄存器打印,
执行之前
执行之后
我们在执行之后打印x0
以及x1
的数据。
从打印结果可知,调用的方法没有变化,只是对调用对象进行了优化。这里设置了时间让其等待。从NSConditionLock
的方法中可知,存在lockWhenCondition: beforeDate:
方法,在这个方法中可知存在NSDate
的对象,猜测肯定是在某个地方做了优化调用,我们重新设置符号断点,查看是否调用该方法。
发现是会调用到该方法中。然后查看swift
源码,
open class NSConditionLock : NSObject, NSLocking {
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
}
open func lock() {
let _ = lock(before: Date.distantFuture)
}
open func unlock() {
_cond.lock()
#if os(Windows)
_thread = INVALID_HANDLE_VALUE
#else
_thread = nil
#endif
_cond.broadcast()
_cond.unlock()
}
open var condition: Int {
return _value
}
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)
}
open func unlock(withCondition condition: Int) {
_cond.lock()
#if os(Windows)
_thread = INVALID_HANDLE_VALUE
#else
_thread = nil
#endif
_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
}
}
#if os(Windows)
_thread = GetCurrentThread()
#else
_thread = pthread_self()
#endif
_cond.unlock()
return true
}
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
}
}
#if os(Windows)
_thread = GetCurrentThread()
#else
_thread = pthread_self()
#endif
_cond.unlock()
return true
}
open var name: String?
}
swift
的代码还是比较易懂的。