欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS底层探索(二十一)锁(下)

程序员文章站 2024-03-24 21:29:58
...

iOS底层探索(二十一)锁(下)

iOS底层探索(二十二)锁(上)

NSLock

自定义NSLock代码,如下:

NSLock *lock = [[NSLock alloc] init];
[lock lock];
[lock unlock];

查看lockunlock方法。

@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框架。
iOS底层探索(二十一)锁(下)

因为我们知道Objective-CFoundation是闭源且没有源码的,因此,我们下载SwiftFoundation的源码,因为SwiftObjective-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的代码,查看区别,
iOS底层探索(二十一)锁(下)

区别在于pthread_mutex_init方法初始化的内容。用于区别递归锁。
但是在使用时不如@synchronized方便,因为一旦出现加锁失败时,或者加错位置时,就会引发其他问题。比如崩溃。但是他的性能比较好。

互斥锁

在Posix Thread中定义有一套专门用于线程同步的mutex函数
mutex用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。
互斥锁分为递归锁和非递归锁。

NSCondition

NSCondition的对象实际上作为一个锁和一个线程检查其:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。

  1. [condition lock]一般用于多线程同时访问、修改同一个数据源,保证在同一个时间内数据源只被访问、修改一次,其他线程的命令需要在lock外等待,直到unlock才可访问
  2. [condition unlock]lock同时使用
  3. [condition wait]让当前线程处于等待状态
  4. [condition signal] CPU发信号告诉线程不用再等待,可以继续执行
  5. 含义在于需要等待条件满足才能执行,不满足不执行
  6. 模型:生产-消费者模型,

自定义源码如下:

- (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

  1. NSConditionLock是锁,一旦一个线程获得锁,其他线程一定等待
  2. [xxxx lock]表示xxxx期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition)那它能执行此行一下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁),则等待,直至其他线程解锁
  3. [xxxx lockWhenCondition:A条件]表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的conditon等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它的代码完成,直至它解锁。
  4. [xxxx unlockWithCondition:A条件]表示释放锁,同时把内部的condition设置为A条件
  5. return = [xxxx lockWhenCondition:A条件 beforeDate:A时间]表示如果被锁定(没有获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函数的目的在于可以实现两种状态下的处理
  6. 所谓的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进行了封装。
进入汇编调试。断点断住后,读取寄存器信息。
iOS底层探索(二十一)锁(下)

读取寄存器的x0信息,x0为调用objc_msgSend的第一个参数,即消息的接收者,也就是当前当前方法的对象。
iOS底层探索(二十一)锁(下)

读取寄存器的x1信息,x1为对象的调用方法,即SEL。方法为_dispatch_call_block_and_release
iOS底层探索(二十一)锁(下)

过断点到objc_msgSend位置。再次查看x0信息。
iOS底层探索(二十一)锁(下)

查看x1信息,查看调用方法。
iOS底层探索(二十一)锁(下)

接下来进行符号断点进行查看,符号断点为-[NSConditionLock lockWhenCondition:],再次运行进行查看。
iOS底层探索(二十一)锁(下)

我们只需要查看一些跳转等操作及bl等操作。
先查看一下当前寄存器的内容。
iOS底层探索(二十一)锁(下)

然后过断点。直接过到b指令处
iOS底层探索(二十一)锁(下)

到这里就没有东西了,查看不到底层的锁是怎么操作的。
那怎么办呢?
重新运行我们在ldr指令执行前后做寄存器打印,
执行之前
iOS底层探索(二十一)锁(下)

执行之后
iOS底层探索(二十一)锁(下)

我们在执行之后打印x0以及x1的数据。
iOS底层探索(二十一)锁(下)

从打印结果可知,调用的方法没有变化,只是对调用对象进行了优化。这里设置了时间让其等待。从NSConditionLock的方法中可知,存在lockWhenCondition: beforeDate:方法,在这个方法中可知存在NSDate的对象,猜测肯定是在某个地方做了优化调用,我们重新设置符号断点,查看是否调用该方法。
iOS底层探索(二十一)锁(下)

发现是会调用到该方法中。然后查看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的代码还是比较易懂的。