美文网首页
锁分析(下)

锁分析(下)

作者: 浅墨入画 | 来源:发表于2021-09-25 17:30 被阅读0次

@synchronized补充答疑

@synchronized底层原理主要是一个哈希结构,这里有几个疑问?

  • 对于lockCount的理解
  • 对于threadCount的理解
  • 对于整个拉链的理解
  • 对于@synchronized数据结构的理解
  • SyncList对应的SyncData是怎么创建的?或者说不同对象不同线程要如何处理?
SyncData的创建
<!-- objc4-818.2源码 id2data方法 -->
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;

下面探索什么情况下才会创建SyncData?

  • 上节课我们打印sDataList发现有64个,下面修改源码减少个数进行调试,这样就增加了线程冲突的概率
<!-- 修改前 -->
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

<!-- 修改后 -->
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 1 };
#endif
  • main.m文件添加代码进行调试
image.png image.png
  • *listp = result;sDataList进行赋值
image.png
  • 下面两个地址不同,说明形成了SyncData的拉链,链表出现哈希冲突调节出来
image.png image.png
  • 执行到@synchronized (p2),当前p2只被锁了一次
image.png
总结
  • 哈希冲突才会导致拉链的行程
  • 每个SyncData里面都会有一个threadCount,来标记被锁了多少次
  • lockCount就是在同一线程下被锁的次数

锁的分类讲解

⾃旋锁

线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显示释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。

  • OSSpinLock
  • os_unfair_lock
互斥锁

是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成。

  • NSLock
  • pthread_mutex
  • @synchronized
条件锁

就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。

  • NSCondition
  • NSConditionLock
递归锁

同一个线程可以加锁N次而不会引发死锁

  • NSRecursiveLock
  • pthread_mutex(recursive)
信号量(semaphore)

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

  • dispatch_semaphore
读写锁

一种特殊的自旋锁。它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。

  • 读写锁相对于自旋锁而言,能提高并发性。因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。但写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数先关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的;
  • 如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者;
  • 如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。

其实基本的锁就包括三类:⾃旋锁互斥锁读写锁。其他的⽐如条件锁递归锁信号量都是上层的封装和实现

  • 互斥锁 = 互斥 + 同步,互斥保证线程安全,当一条线程执行时,其他线程休眠。同步保证执行顺序,多线程串行执行
    NSLock、pthread_mutex、@synchronized、NSCondition、NSConditionLock、NSRecursiveLock、pthread_mutex(recursive)、dispatch_semaphore

  • 自旋锁 = 互斥 + 忙等,例如do...while循环。它的优点在于不会引起调用者睡眠,所以不会进行线程调度,CPU时间片轮转等耗时操作。而缺点是当等待时会消耗大量CPU资源,所以自旋锁不适用较长时间的任务:
    OSSpinLock、os_unfair_lock

NSLock和NSRecursiveLock的区别

案例一 NSLock

- (void)lg_testRecursive{
    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);
    });
}

// 控制台打印
2021-09-25 10:44:42.627284+0800 003-NSLock分析[21514:21767277] current value = 10
2021-09-25 10:44:42.627476+0800 003-NSLock分析[21514:21767277] current value = 9
2021-09-25 10:44:42.627603+0800 003-NSLock分析[21514:21767277] current value = 8
2021-09-25 10:44:42.627729+0800 003-NSLock分析[21514:21767277] current value = 7
2021-09-25 10:44:42.627847+0800 003-NSLock分析[21514:21767277] current value = 6
2021-09-25 10:44:42.627977+0800 003-NSLock分析[21514:21767277] current value = 5
2021-09-25 10:44:42.628104+0800 003-NSLock分析[21514:21767277] current value = 4
2021-09-25 10:44:42.628239+0800 003-NSLock分析[21514:21767277] current value = 3
2021-09-25 10:44:42.628383+0800 003-NSLock分析[21514:21767277] current value = 2
2021-09-25 10:44:42.628504+0800 003-NSLock分析[21514:21767277] current value = 1

如果对上面代码添加for循环,修改代码如下

- (void)lg_testRecursive{
    for (int i= 0; i<10; 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);
        });
    }
}

// 控制台打印
2021-09-25 10:48:15.895123+0800 003-NSLock分析[21529:21769715] current value = 10
2021-09-25 10:48:15.895167+0800 003-NSLock分析[21529:21769720] current value = 10
2021-09-25 10:48:15.895196+0800 003-NSLock分析[21529:21769714] current value = 10
2021-09-25 10:48:15.895317+0800 003-NSLock分析[21529:21769715] current value = 9
2021-09-25 10:48:15.895323+0800 003-NSLock分析[21529:21769720] current value = 9
2021-09-25 10:48:15.895334+0800 003-NSLock分析[21529:21769714] current value = 9
2021-09-25 10:48:15.895460+0800 003-NSLock分析[21529:21769715] current value = 8
2021-09-25 10:48:15.895477+0800 003-NSLock分析[21529:21769720] current value = 8
2021-09-25 10:48:15.895537+0800 003-NSLock分析[21529:21769714] current value = 8
2021-09-25 10:48:15.895663+0800 003-NSLock分析[21529:21769715] current value = 7
......

通过上面打印发现数据错乱,如果数据不断变化,来回切换产生递归,会引起多线程安全问题。现在希望顺序打印,要怎么解决?如果添加锁,又该如何添加呢?下面进行尝试解决......

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

// 控制台打印
2021-09-25 10:59:53.087625+0800 003-NSLock分析[21623:21778908] current value = 10
产生死锁

执行完testMethod(10)会进入block回调,然后加锁,递归执行testMethod(value - 1);无限加锁,产生死锁。需要把[lock lock];加到testMethod回调block上面才行,如下所示

- (void)lg_testRecursive{
    NSLock *lock = [[NSLock alloc] init];
    for (int i= 0; i<10; 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];
        });
    }
}

案例二 NSRecursiveLock无法多线程递归

- (void)lg_testRecursive{
    self.recursiveLock = [[NSRecursiveLock alloc] init];
    for (int i= 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                [self.recursiveLock lock];
                if (value > 0) {
                    NSLog(@"current value = %d",value);
                    testMethod(value - 1);
                }
                [self.recursiveLock unlock];
            };
            testMethod(10);
        });
    } 
}

// 控制台打印
2021-09-25 11:13:54.098546+0800 003-NSLock分析[21778:21792829] current value = 10
2021-09-25 11:13:54.098705+0800 003-NSLock分析[21778:21792829] current value = 9
2021-09-25 11:13:54.098840+0800 003-NSLock分析[21778:21792829] current value = 8
2021-09-25 11:13:54.098972+0800 003-NSLock分析[21778:21792829] current value = 7
2021-09-25 11:13:54.099094+0800 003-NSLock分析[21778:21792829] current value = 6
2021-09-25 11:13:54.099214+0800 003-NSLock分析[21778:21792829] current value = 5
2021-09-25 11:13:54.099332+0800 003-NSLock分析[21778:21792829] current value = 4
2021-09-25 11:13:54.099455+0800 003-NSLock分析[21778:21792829] current value = 3
2021-09-25 11:13:54.099863+0800 003-NSLock分析[21778:21792829] current value = 2
2021-09-25 11:13:54.100512+0800 003-NSLock分析[21778:21792829] current value = 1
(lldb) 产生崩溃

NSRecursiveLock递归锁应用在多线程中,无法实现多线程的可递归,从而引起崩溃。这就要使用到@synchronized锁来解决多线程问题。

案例三 @synchronized(self)

- (void)lg_testRecursive{
    for (int i= 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                @synchronized (self) {
                    if (value > 0) {
                        NSLog(@"current value = %d",value);
                        testMethod(value - 1);
                    }
                }
            };
            testMethod(10);
        });
    }
}

// 控制台打印
2021-09-25 11:19:59.301839+0800 003-NSLock分析[21809:21798006] current value = 10
2021-09-25 11:19:59.302010+0800 003-NSLock分析[21809:21798006] current value = 9
2021-09-25 11:19:59.302155+0800 003-NSLock分析[21809:21798006] current value = 8
2021-09-25 11:19:59.302294+0800 003-NSLock分析[21809:21798006] current value = 7
2021-09-25 11:19:59.302449+0800 003-NSLock分析[21809:21798006] current value = 6
2021-09-25 11:19:59.302591+0800 003-NSLock分析[21809:21798006] current value = 5
2021-09-25 11:19:59.302729+0800 003-NSLock分析[21809:21798006] current value = 4
2021-09-25 11:19:59.302899+0800 003-NSLock分析[21809:21798006] current value = 3
2021-09-25 11:19:59.303055+0800 003-NSLock分析[21809:21798006] current value = 2
2021-09-25 11:19:59.303185+0800 003-NSLock分析[21809:21798006] current value = 1
2021-09-25 11:19:59.303390+0800 003-NSLock分析[21809:21798007] current value = 10
......

NSRecursiveLock锁解决了递归性,但是@synchronized锁才会解决多线程行。

NSCondition的分析

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

  • [condition lock];//⼀般⽤于多线程同时访问、修改同⼀个数据源,保证在同⼀时间内数据源只被访问、修改⼀次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问
  • [condition unlock];//与lock 同时使⽤
  • [condition wait];//让当前线程处于等待状态
  • [condition signal];//CPU发信号告诉线程不⽤在等待,可以继续执⾏
#pragma mark -- NSCondition

- (void)lg_testConditon{
    self.ticketCount = 0;
    _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{
    self.ticketCount = self.ticketCount + 1;
    NSLog(@"生产一个 现有 count %zd",self.ticketCount);
}

- (void)lg_consumer{
    if (self.ticketCount == 0) {
        NSLog(@"等待 count %zd",self.ticketCount);
    }
    self.ticketCount -= 1;
    NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
}

// 控制台打印
2021-09-25 11:41:24.904076+0800 004-NSCondition[21959:21814557] 生产一个 现有 count 1
2021-09-25 11:41:24.905145+0800 004-NSCondition[21959:21814481] 生产一个 现有 count 1
2021-09-25 11:41:24.906589+0800 004-NSCondition[21959:21814518] 消费一个 还剩 count 1 
2021-09-25 11:41:24.905742+0800 004-NSCondition[21959:21814559] 消费一个 还剩 count 0 
2021-09-25 11:41:24.904336+0800 004-NSCondition[21959:21814547] 生产一个 现有 count 2
2021-09-25 11:41:24.905349+0800 004-NSCondition[21959:21814478] 生产一个 现有 count 2
2021-09-25 11:41:24.904900+0800 004-NSCondition[21959:21814519] 消费一个 还剩 count 0 
2021-09-25 11:41:24.905543+0800 004-NSCondition[21959:21814482] 消费一个 还剩 count 1 
2021-09-25 11:41:25.025766+0800 004-NSCondition[21959:21814563] 消费一个 还剩 count -1
......

通过上面打印我们发现,可消费的数量已经为0了,还在继续消费。这样就会产生错乱,因为生产数量消费数量对应,允许消费的数量大于0才能消费。解决方案是添加NSCondition

#pragma mark -- NSCondition

- (void)lg_testConditon{
    self.ticketCount = 0;
    _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];  // 操作的多线程影响
    // 消费为0的话要进行等待
    if (self.ticketCount == 0) {
        NSLog(@"等待 count %zd",self.ticketCount);
        [_testCondition wait];
    }
    //注意消费行为,要在等待条件判断之后
    self.ticketCount -= 1;
    NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
    [_testCondition unlock];
}

// 控制台打印
2021-09-25 11:46:57.842489+0800 004-NSCondition[21988:21818035] 生产一个 现有 count 1
2021-09-25 11:46:57.842663+0800 004-NSCondition[21988:21818037] 消费一个 还剩 count 0 
2021-09-25 11:46:57.842831+0800 004-NSCondition[21988:21818038] 等待 count 0
2021-09-25 11:46:57.842973+0800 004-NSCondition[21988:21818034] 生产一个 现有 count 1
2021-09-25 11:46:57.843119+0800 004-NSCondition[21988:21818038] 消费一个 还剩 count 0 
2021-09-25 11:46:57.843284+0800 004-NSCondition[21988:21818039] 等待 count 0
2021-09-25 11:46:57.843417+0800 004-NSCondition[21988:21818037] 等待 count 0
2021-09-25 11:46:57.843546+0800 004-NSCondition[21988:21818035] 生产一个 现有 count 1
2021-09-25 11:46:57.843882+0800 004-NSCondition[21988:21818039] 消费一个 还剩 count 0 
2021-09-25 11:46:57.845138+0800 004-NSCondition[21988:21818039] 等待 count 0
......

foundation源码关于锁的封装

NSLock底层
@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

swift-corelibs-foundation源码

  • NSLock遵循NSLocking协议,在swift的foundation源码中可查看
public protocol NSLocking {
    func lock()
    func unlock()
}

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
    }

底层是对pthread_mutex_initpthread_mutex_lockpthread_mutex_unlock的封装

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
    }
......
NSCondition底层
open class NSCondition: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
    internal var cond = _ConditionVariablePointer.allocate(capacity: 1)

    public override init() {
#if os(Windows)
        InitializeSRWLock(mutex)
        InitializeConditionVariable(cond)
#else
        pthread_mutex_init(mutex, nil)
        pthread_cond_init(cond, nil)
#endif
    }
    
    deinit {
#if os(Windows)
        // SRWLock do not need to be explicitly destroyed
#else
        pthread_mutex_destroy(mutex)
        pthread_cond_destroy(cond)
#endif
        mutex.deinitialize(count: 1)
        cond.deinitialize(count: 1)
        mutex.deallocate()
        cond.deallocate()
    }
    
    open func lock() {
#if os(Windows)
        AcquireSRWLockExclusive(mutex)
#else
        pthread_mutex_lock(mutex)
#endif
    }
......

NSConditionLock分析上

请问下面代码执行顺序是什么?

- (void)lg_testConditonLock{
    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];
        sleep(0.1);
        NSLog(@"线程 2");
        [conditionLock unlockWithCondition:1];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       [conditionLock lock];
       NSLog(@"线程 3");
       [conditionLock unlock];
    });
}

// 控制台打印
2021-09-25 15:21:33.617561+0800 004-NSCondition[23246:21892559] 线程 3
2021-09-25 15:21:33.621515+0800 004-NSCondition[23246:21892559] 线程 2
2021-09-25 15:21:33.624639+0800 004-NSCondition[23246:21892563] 线程 1

执行结果,大概率为线程3线程2线程1
NSConditionLock初始化,设置的条件为2。按照线程优先级顺序:

  • 线程1优先级最高,但不符合条件,代码块无法执行,进入等待状态;
  • 线程3默认优先级,同时属于无条件锁,可以执行代码块;
  • 线程2优先级最低,但符合条件,也可以执行代码块;

所以,大概率线程3会在线程2之前打印,而线程1必须等待线程2执行完,释放条件锁之后才能执行。

这里我们有一些疑问?
  • NSConditionLockNSCondition有关系吗?
  • initWithCondition:2中传的参数2是做什么的?
  • [conditionLock lockWhenCondition:1];传的参数是如何控制的?
  • unlockWithCondition解锁方法做了什么?

基于上面提出的疑问?下面进行探索...

  • 添加断点调试
image.png
  • 添加符号断点-[NSConditionLock initWithCondition:]
image.png
  • 断点查看所有带bl跳转相关的汇编,进行打印调试
image.png
  • 又调用一次init方法,这一次是NSConditionLock调用了init方法
image.png
  • 调用zone方法,进行内存开辟
image.png
  • NSCondition调用allocWithZone方法
image.png
  • NSCondition调用init方法
image.png

上面对[NSConditionLock initWithCondition:]的探索步骤如下

  • [? init:2]
  • -[NSConditionLock init]
  • -[NSConditionLock zone]
  • [NSCondition allocWithZone]
  • [NSCondition init]

NSConditionLock中封装了一个NSCondition,并且把传入的值为2的value保存在NSCondition

NSConditionLock分析下

下面探索lockWhenConditionunlockWithCondition底层原理

  • 添加断点调试
image.png
  • 添加符号断点-[NSConditionLock lockWhenCondition:]-[NSConditionLock unlockWithCondition:]
image.png
  • NSDate调用distantFuture方法
image.png
  • -[NSConditionLock lockWhenCondition: beforeDate:]方法调用,Condition参数为传入的1,Date参数传入的就是上一步的[NSDate distantFuture]
image.png
  • 添加符号断点-[NSConditionLock lockWhenCondition: beforeDate:]
image.png
  • NSCondition调用lock方法
image.png
  • 调用WaitUntilDate方法
image.png
  • 0x000000000000001-> 返回true不在等待
image.png
  • NSCondition调用lock方法
image.png
  • NSCondition调用broadcast方法
image.png
  • NSCondition调用unlock方法
image.png

上面对[NSConditionLock lockWhenCondition:]的探索步骤如下

  • [NSDate distantFuture]
  • -[NSConditionLock lockWhenCondition: beforeDate:]
  • -[NSCondition lock]
  • [WaitUntilDate ]
  • 0x000000000000001 -> 返回true 不在等待
  • [NSCondition unlock]

[NSConditionLock unlockWhenCondition:]的探索步骤如下

  • [NSCondition lock]
  • [NSCondition broadcast]
  • [NSCondition unlock]
查看源码流程与上面探索流程对比
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?
}
NSConditionLock总结
  • 线程 1 调用[NSConditionLock lockWhenCondition:],此时此刻因为不满足当前条件,所以会进入waiting状态,当前进入到waiting 时,会释放当前的互斥锁。
  • 此时当前的线程 3 调用[NSConditionLock lock:],本质上是调用 [NSConditionLock lockBeforeDate:],这里不需要比对条件值,所以线程 3 会打印
  • 接下来线程 2 执行[NSConditionLock lockWhenCondition:],因为满足条件值,所以线程2会打印,打印完成后会调用[NSConditionLock unlockWithCondition:],这个时候将value设置为 1,并发送boradcast, 此时线程 1 接收到当前的信号,唤醒执行并打印。
  • 自此当前打印为 线程 3->线程 2 -> 线程 1
  • [NSConditionLock lockWhenCondition:]:这里会根据传入的condition 值和 Value 值进行对比,如果不相等,这里就会阻塞,进入线程池,否则的话就继续代码执行
  • [NSConditionLock unlockWithCondition:]: 这里会先更改当前的 value值,然后进行广播,唤醒当前的线程。

相关文章

  • 锁分析(下)

    @synchronized补充答疑 @synchronized底层原理主要是一个哈希结构,这里有几个疑问? 对于l...

  • 深入Thread.sleep

    实战分析 一直都说,Threed.sleep是不会释放锁,而wait是释放锁的(对象锁),现理论上来分析一下啊。 ...

  • 重入锁的源码解析

    今天来分析一下重入锁的源码 ReentranLock定义 重入锁ReentranLock是一种支持重进入的锁,表示...

  • java java.util.concurrent.locks包

    上一篇 文章中我们分析了ReentrantLock的实现原理,今天在分析一下条件锁。条件锁的具体实现在Abstra...

  • Java 源码分析-Condition

      前面对Java中的锁进行了简单的分析,锁的使用和原理整体来说还是比较简单。今天我们来分析一下Condition...

  • linux 2.6 互斥锁的实现-源码分析

    Linux 2.6 kernel的源码,下面结合代码来分析一下在X86体系结构下,互斥锁的实现原理。 代码分析: ...

  • Android锁屏下启动应用卡屏5秒的原因分析

    Android锁屏下启动应用卡屏5秒的原因分析 最近分析一个问题,在锁屏窗口中启动应用会出现卡顿5秒,比如拨打电话...

  • JUC源码循序渐进

    目录 必读篇 JUC源码分析—CAS和Unsafe JUC源码分析—AQS JUC锁篇 JUC源码分析-JUC锁(...

  • Android10.0 锁屏分析——KeyguardPINVie

    学习笔记: PIN 解锁流程跟Android10.0 锁屏分析——KeyguardPatternView图案锁分析...

  • synchronized 以及java内置锁

    Java中锁大致上分为两类:一类是显示锁,一类是隐式锁;今天我们重点来分析一下java中隐式锁的实现: java中...

网友评论

      本文标题:锁分析(下)

      本文链接:https://www.haomeiwen.com/subject/pdlagltx.html